Claude commited on
Commit
de2327a
·
unverified ·
1 Parent(s): 6bb0e68

cli(workflows): générer le HTML automatiquement (Phase 4.5 chantier post-rewrite)

Browse files

Avant : les commandes ``picarones diagnose``/``economics``/``edition``
écrivaient un JSON mais l'utilisateur devait relancer manuellement
``picarones report --results foo.json`` pour obtenir le rapport HTML —
alors que les docstrings vendent justement les vues HTML correspondantes
(« Diagnostic approfondi », « Coût et performance », « Taxonomie avancée »).
Symptôme classique d'UX post-rewrite : la pipeline JSON était portée mais
le HTML automatique avait été oublié.

Modifications :
- ``_run_workflow`` accepte ``generate_html: bool = True`` et ``html_lang``.
Après écriture du JSON, instancie ``ReportGenerator`` directement sur le
``BenchmarkResult`` in-memory et écrit le HTML à côté
(``results.json`` → ``results.html`` via ``_html_path_from_json``).
- Échec HTML → warning stderr, pas exit code 1 : le JSON est déjà écrit
et l'utilisateur peut retenter avec ``picarones report``.
- Nouvelles options ``--no-html`` (CI/scripts) et ``--html-lang fr|en``
sur ``diagnose``, ``economics``, ``edition``.

Tests : ``TestCliWorkflows.test_command_exposes_html_options`` (paramétré
sur les 3 commandes) + ``test_run_workflow_generates_html_by_default``
(vérification statique du default ``generate_html=True``).

https://claude.ai/code/session_01ArfZ8kcgv7Cyda7VbJVmpn

picarones/interfaces/cli/_workflows.py CHANGED
@@ -205,6 +205,20 @@ def run_cmd(
205
  # L'option ``--profile`` reste disponible mais le défaut change pour
206
  # chaque commande.
207
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  def _run_workflow(
209
  *,
210
  corpus: str,
@@ -216,14 +230,25 @@ def _run_workflow(
216
  verbose: bool,
217
  profile: str,
218
  workflow_label: str,
 
 
219
  ) -> None:
220
  """Implémentation commune des commandes ``run``, ``diagnose``,
221
  ``economics`` et ``edition``.
222
 
223
  Les 4 commandes partagent le squelette : chargement corpus →
224
  instanciation moteurs → ``run_benchmark_via_service(profile=...)`` → affichage
225
- classement. Seul le profil par défaut et le message d'en-tête
226
- diffèrent.
 
 
 
 
 
 
 
 
 
227
  """
228
  _setup_logging(verbose)
229
 
@@ -281,7 +306,25 @@ def _run_workflow(
281
  f"CER={cer_pct:<8} WER={wer_pct}{failed_str}"
282
  )
283
 
284
- click.echo(f"\nRésultats écrits dans : {output}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
 
287
  @cli.command("diagnose")
@@ -307,9 +350,14 @@ def _run_workflow(
307
  help="Désactive la barre de progression")
308
  @click.option("--verbose", "-v", is_flag=True, default=False,
309
  help="Mode verbeux")
 
 
 
 
 
310
  def diagnose_cmd(
311
  corpus: str, engines: str, output: str, lang: str, psm: int,
312
- no_progress: bool, verbose: bool,
313
  ) -> None:
314
  """Workflow diagnostic : bench + leviers d'amélioration + image_predictive.
315
 
@@ -318,6 +366,9 @@ def diagnose_cmd(
318
  (chantier 3) : leviers, profil d'image, baseline, longitudinal.
319
  Idéal pour comprendre *pourquoi* un moteur produit ces résultats
320
  sur ce corpus, pas seulement *quel CER*.
 
 
 
321
  """
322
  _run_workflow(
323
  corpus=corpus, engines=engines, output=output,
@@ -325,6 +376,8 @@ def diagnose_cmd(
325
  no_progress=no_progress, verbose=verbose,
326
  profile="diagnostics",
327
  workflow_label="diagnose",
 
 
328
  )
329
 
330
 
@@ -351,9 +404,14 @@ def diagnose_cmd(
351
  help="Désactive la barre de progression")
352
  @click.option("--verbose", "-v", is_flag=True, default=False,
353
  help="Mode verbeux")
 
 
 
 
 
354
  def economics_cmd(
355
  corpus: str, engines: str, output: str, lang: str, psm: int,
356
- no_progress: bool, verbose: bool,
357
  ) -> None:
358
  """Workflow économique : bench + throughput effectif + (cost projection).
359
 
@@ -361,7 +419,8 @@ def economics_cmd(
361
  les métriques de décision budget : pages/h utilisable (intégrant
362
  la correction humaine HTR-United à 5 s/erreur), coût marginal par
363
  erreur évitée. La vue HTML « Coût et performance » (chantier 3)
364
- est ensuite branchée.
 
365
  """
366
  _run_workflow(
367
  corpus=corpus, engines=engines, output=output,
@@ -369,6 +428,8 @@ def economics_cmd(
369
  no_progress=no_progress, verbose=verbose,
370
  profile="economics",
371
  workflow_label="economics",
 
 
372
  )
373
 
374
 
@@ -395,9 +456,14 @@ def economics_cmd(
395
  help="Désactive la barre de progression")
396
  @click.option("--verbose", "-v", is_flag=True, default=False,
397
  help="Mode verbeux")
 
 
 
 
 
398
  def edition_cmd(
399
  corpus: str, engines: str, output: str, lang: str, psm: int,
400
- no_progress: bool, verbose: bool,
401
  ) -> None:
402
  """Workflow édition critique : bench + métriques philologiques.
403
 
@@ -407,6 +473,9 @@ def edition_cmd(
407
  vue HTML « Taxonomie avancée » (chantier 3) avec comparaison
408
  miroir leader vs runner-up. Cible : éditeurs de chartes,
409
  paléographes, archivistes.
 
 
 
410
  """
411
  _run_workflow(
412
  corpus=corpus, engines=engines, output=output,
@@ -414,6 +483,8 @@ def edition_cmd(
414
  no_progress=no_progress, verbose=verbose,
415
  profile="philological",
416
  workflow_label="edition",
 
 
417
  )
418
 
419
 
 
205
  # L'option ``--profile`` reste disponible mais le défaut change pour
206
  # chaque commande.
207
 
208
+ def _html_path_from_json(json_path: str) -> str:
209
+ """Convertit un chemin ``results.json`` en chemin ``results.html``.
210
+
211
+ Utilisé par les workflows pour générer automatiquement le rapport
212
+ HTML à côté du JSON (Phase 4.5 du chantier post-rewrite — auparavant
213
+ chaque workflow imprimait juste le chemin JSON et l'utilisateur
214
+ devait relancer ``picarones report --results …`` manuellement,
215
+ contre-intuitif vu que le workflow vendait un livrable HTML).
216
+ """
217
+ from pathlib import Path
218
+ p = Path(json_path)
219
+ return str(p.with_suffix(".html"))
220
+
221
+
222
  def _run_workflow(
223
  *,
224
  corpus: str,
 
230
  verbose: bool,
231
  profile: str,
232
  workflow_label: str,
233
+ generate_html: bool = True,
234
+ html_lang: str = "fr",
235
  ) -> None:
236
  """Implémentation commune des commandes ``run``, ``diagnose``,
237
  ``economics`` et ``edition``.
238
 
239
  Les 4 commandes partagent le squelette : chargement corpus →
240
  instanciation moteurs → ``run_benchmark_via_service(profile=...)`` → affichage
241
+ classement génération automatique du rapport HTML. Seul le profil
242
+ par défaut et le message d'en-tête diffèrent.
243
+
244
+ Phase 4.5 du chantier post-rewrite : ``generate_html=True`` par
245
+ défaut. Auparavant les workflows ne produisaient que du JSON, ce
246
+ qui forçait l'utilisateur à ré-exécuter ``picarones report``
247
+ manuellement — contre-intuitif (les docstrings vendaient une vue
248
+ HTML "Diagnostic", "Coût et performance", "Taxonomie avancée"
249
+ qui n'était jamais générée). Passer ``generate_html=False``
250
+ permet de désactiver pour les usages CI/scripts qui ne veulent
251
+ que le JSON.
252
  """
253
  _setup_logging(verbose)
254
 
 
306
  f"CER={cer_pct:<8} WER={wer_pct}{failed_str}"
307
  )
308
 
309
+ click.echo(f"\nRésultats JSON écrits dans : {output}")
310
+
311
+ if generate_html:
312
+ html_output = _html_path_from_json(output)
313
+ try:
314
+ from picarones.reports.html.generator import ReportGenerator
315
+ gen = ReportGenerator(result, lang=html_lang)
316
+ gen.generate(html_output)
317
+ click.echo(f"Rapport HTML généré : {html_output}")
318
+ except Exception as exc: # noqa: BLE001
319
+ # Le JSON est déjà écrit ; on logue l'échec HTML sans
320
+ # quitter avec un code d'erreur (l'utilisateur peut
321
+ # relancer ``picarones report`` manuellement).
322
+ click.echo(
323
+ f"Avertissement : génération HTML échouée ({exc}). "
324
+ f"Relancer ``picarones report --results {output}`` "
325
+ "pour réessayer.",
326
+ err=True,
327
+ )
328
 
329
 
330
  @cli.command("diagnose")
 
350
  help="Désactive la barre de progression")
351
  @click.option("--verbose", "-v", is_flag=True, default=False,
352
  help="Mode verbeux")
353
+ @click.option("--no-html", is_flag=True, default=False,
354
+ help="N'écrit que le JSON, pas le rapport HTML")
355
+ @click.option("--html-lang", default="fr", show_default=True,
356
+ type=click.Choice(["fr", "en"]),
357
+ help="Langue du rapport HTML")
358
  def diagnose_cmd(
359
  corpus: str, engines: str, output: str, lang: str, psm: int,
360
+ no_progress: bool, verbose: bool, no_html: bool, html_lang: str,
361
  ) -> None:
362
  """Workflow diagnostic : bench + leviers d'amélioration + image_predictive.
363
 
 
366
  (chantier 3) : leviers, profil d'image, baseline, longitudinal.
367
  Idéal pour comprendre *pourquoi* un moteur produit ces résultats
368
  sur ce corpus, pas seulement *quel CER*.
369
+
370
+ Phase 4.5 du chantier post-rewrite : génère désormais le HTML
371
+ automatiquement à côté du JSON (``--no-html`` pour skipper).
372
  """
373
  _run_workflow(
374
  corpus=corpus, engines=engines, output=output,
 
376
  no_progress=no_progress, verbose=verbose,
377
  profile="diagnostics",
378
  workflow_label="diagnose",
379
+ generate_html=not no_html,
380
+ html_lang=html_lang,
381
  )
382
 
383
 
 
404
  help="Désactive la barre de progression")
405
  @click.option("--verbose", "-v", is_flag=True, default=False,
406
  help="Mode verbeux")
407
+ @click.option("--no-html", is_flag=True, default=False,
408
+ help="N'écrit que le JSON, pas le rapport HTML")
409
+ @click.option("--html-lang", default="fr", show_default=True,
410
+ type=click.Choice(["fr", "en"]),
411
+ help="Langue du rapport HTML")
412
  def economics_cmd(
413
  corpus: str, engines: str, output: str, lang: str, psm: int,
414
+ no_progress: bool, verbose: bool, no_html: bool, html_lang: str,
415
  ) -> None:
416
  """Workflow économique : bench + throughput effectif + (cost projection).
417
 
 
419
  les métriques de décision budget : pages/h utilisable (intégrant
420
  la correction humaine HTR-United à 5 s/erreur), coût marginal par
421
  erreur évitée. La vue HTML « Coût et performance » (chantier 3)
422
+ est désormais générée automatiquement (Phase 4.5 chantier
423
+ post-rewrite — ``--no-html`` pour skipper).
424
  """
425
  _run_workflow(
426
  corpus=corpus, engines=engines, output=output,
 
428
  no_progress=no_progress, verbose=verbose,
429
  profile="economics",
430
  workflow_label="economics",
431
+ generate_html=not no_html,
432
+ html_lang=html_lang,
433
  )
434
 
435
 
 
456
  help="Désactive la barre de progression")
457
  @click.option("--verbose", "-v", is_flag=True, default=False,
458
  help="Mode verbeux")
459
+ @click.option("--no-html", is_flag=True, default=False,
460
+ help="N'écrit que le JSON, pas le rapport HTML")
461
+ @click.option("--html-lang", default="fr", show_default=True,
462
+ type=click.Choice(["fr", "en"]),
463
+ help="Langue du rapport HTML")
464
  def edition_cmd(
465
  corpus: str, engines: str, output: str, lang: str, psm: int,
466
+ no_progress: bool, verbose: bool, no_html: bool, html_lang: str,
467
  ) -> None:
468
  """Workflow édition critique : bench + métriques philologiques.
469
 
 
473
  vue HTML « Taxonomie avancée » (chantier 3) avec comparaison
474
  miroir leader vs runner-up. Cible : éditeurs de chartes,
475
  paléographes, archivistes.
476
+
477
+ Phase 4.5 du chantier post-rewrite : génère le HTML
478
+ automatiquement (``--no-html`` pour skipper).
479
  """
480
  _run_workflow(
481
  corpus=corpus, engines=engines, output=output,
 
483
  no_progress=no_progress, verbose=verbose,
484
  profile="philological",
485
  workflow_label="edition",
486
+ generate_html=not no_html,
487
+ html_lang=html_lang,
488
  )
489
 
490
 
tests/integration/test_chantier4.py CHANGED
@@ -280,3 +280,60 @@ class TestCliWorkflows:
280
  assert result.exit_code == 0, result.output
281
  assert "--corpus" in result.output
282
  assert "--engines" in result.output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
280
  assert result.exit_code == 0, result.output
281
  assert "--corpus" in result.output
282
  assert "--engines" in result.output
283
+
284
+ @pytest.mark.parametrize("cmd_name", ["diagnose", "economics", "edition"])
285
+ def test_command_exposes_html_options(self, cmd_name):
286
+ """Phase 4.5 du chantier post-rewrite : les 3 workflows
287
+ génèrent le HTML automatiquement à côté du JSON ; les options
288
+ ``--no-html`` (skip HTML pour CI/scripts) et ``--html-lang``
289
+ (fr/en) doivent être visibles dans ``--help``."""
290
+ try:
291
+ from click.testing import CliRunner
292
+
293
+ from picarones.interfaces.cli import cli as cli_group
294
+ except ImportError:
295
+ pytest.skip("click non installé")
296
+
297
+ runner = CliRunner()
298
+ result = runner.invoke(cli_group, [cmd_name, "--help"])
299
+ assert result.exit_code == 0, result.output
300
+ assert "--no-html" in result.output, (
301
+ f"{cmd_name} doit exposer --no-html (Phase 4.5)"
302
+ )
303
+ assert "--html-lang" in result.output, (
304
+ f"{cmd_name} doit exposer --html-lang (Phase 4.5)"
305
+ )
306
+
307
+ def test_run_workflow_generates_html_by_default(self):
308
+ """``_run_workflow(..., generate_html=True)`` doit appeler
309
+ ``ReportGenerator`` avec un path dérivé du JSON output."""
310
+ from pathlib import Path
311
+ import ast
312
+
313
+ cli_src = (
314
+ Path(__file__).parent.parent.parent
315
+ / "picarones" / "interfaces" / "cli" / "_workflows.py"
316
+ ).read_text(encoding="utf-8")
317
+ # Vérifications statiques.
318
+ assert "_html_path_from_json" in cli_src, (
319
+ "Le helper _html_path_from_json doit dériver "
320
+ "results.json → results.html"
321
+ )
322
+ assert "ReportGenerator" in cli_src, (
323
+ "Le workflow doit instancier ReportGenerator pour le HTML"
324
+ )
325
+ # Le default est ``generate_html=True``.
326
+ tree = ast.parse(cli_src)
327
+ for node in ast.walk(tree):
328
+ if isinstance(node, ast.FunctionDef) and node.name == "_run_workflow":
329
+ kwarg_defaults = node.args.kw_defaults
330
+ kwarg_names = [a.arg for a in node.args.kwonlyargs]
331
+ idx = kwarg_names.index("generate_html")
332
+ default = kwarg_defaults[idx]
333
+ assert isinstance(default, ast.Constant)
334
+ assert default.value is True, (
335
+ "generate_html doit être True par défaut "
336
+ "(Phase 4.5)"
337
+ )
338
+ return
339
+ raise AssertionError("_run_workflow introuvable")