Spaces:
Sleeping
feat(report): Phase 21C — dispatch Documents + Crosses (cer-doc + quality-cer + pareto + marginal-cost)
Browse filesTroisième commit Phase 21 : déplace les charts cross-dimensionnels et
doc-centriques depuis view_analyses.html vers leurs vues XerOCR
sémantiques. view_analyses.html ne contient plus que view_results_html
(Phase 23) et la corr-matrix interactive JS (Commit 21D pour
suppression).
Déplacés vers documents.html :
- chart-cer-doc — CER par document, tous moteurs (doc-centric)
Déplacés vers crosses.html :
- chart-quality-cer — qualité image × CER (cross-dimensional)
- pareto-chart — front Pareto interactif avec 3 toggles
(cost/speed/co2), liste d'hypothèses, note
pédagogique. Le wrapper passe de
`<div class="chart-card pareto-card">` à
`<section class="card xer-chart-card pareto-card">`
— convention XerOCR. `.pareto-card` (border
vert) et `.pareto-toolbar/.pareto-toggle`
inchangés en CSS, pas de régression visuelle.
- marginal_cost_html — coût marginal d'une erreur évitée entre
paires de moteurs (cost × quality cross-dim).
Commentaire d'en-tête de view_analyses.html mis à jour pour refléter
l'état post-21C : reliquat documenté (view_results pour Phase 23, JS
corr-matrix pour 21D).
Tests :
- 19 nouveaux tests dans test_phase21_dispatch.py (4 classes : Documents,
Crosses/Quality, Crosses/Pareto, Crosses/MarginalCost).
- Anti-theater validé par 3 sabotages distincts (canvas rename, toggle
removal, conditional wiring rename).
- test_english_locale_full_render (test_docs_case_studies.py) mis à jour
pour le nouveau wrapper Pareto (`pareto-card` au lieu de
`chart-card pareto-card` — l'ancien sélecteur exact serait cassé).
- test_report_contains_pareto_card (test_pareto_pricing.py) idem.
Lint propre, 5764 tests, 0 failed.
https://claude.ai/code/session_01WYDbfkhKPeBZ15BTP4e9Ye
- picarones/reports/html/templates/view_analyses.html +19 -83
- picarones/reports/html/templates/views/crosses.html +59 -0
- picarones/reports/html/templates/views/documents.html +13 -0
- tests/evaluation/metrics/test_pareto_pricing.py +5 -1
- tests/reports/test_docs_case_studies.py +5 -1
- tests/reports/test_phase21_dispatch.py +175 -0
|
@@ -1,94 +1,30 @@
|
|
| 1 |
|
| 2 |
-
<!-- ════ Vue 5 : Analyses (
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
error-clusters. Tant que ces 8 charts persistent ici, le vendor
|
| 16 |
-
Chart.js inliné dans base.html.j2 reste justifié. Migration
|
| 17 |
-
incrémentale vers SVG = chantier non bloquant. -->
|
| 18 |
<div id="view-analyses" class="view">
|
| 19 |
<div class="charts-grid">
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
<canvas id="chart-cer-doc" role="img" aria-label="CER par document" data-a11y-label="CER par document"></canvas>
|
| 25 |
-
</div>
|
| 26 |
-
</div>
|
| 27 |
-
|
| 28 |
-
<div class="chart-card">
|
| 29 |
-
<h3 data-i18n="h_quality_cer">Qualité image ↔ CER (scatter plot)</h3>
|
| 30 |
-
<div class="chart-canvas-wrap">
|
| 31 |
-
<canvas id="chart-quality-cer" role="img" aria-label="Corrélation qualité d'image / CER" data-a11y-label="Corrélation qualité d'image / CER"></canvas>
|
| 32 |
-
</div>
|
| 33 |
-
<div style="font-size:.72rem;color:var(--text-muted);margin-top:.4rem" data-i18n="quality_cer_note">
|
| 34 |
-
Chaque point = un document. Axe X = score qualité image [0–1]. Axe Y = CER. Corrélation négative attendue.
|
| 35 |
-
</div>
|
| 36 |
-
</div>
|
| 37 |
-
|
| 38 |
-
<div class="chart-card pareto-card" style="grid-column:1/-1">
|
| 39 |
-
<h3 data-i18n="h_pareto">Compromis qualité / coût</h3>
|
| 40 |
-
<div class="pareto-toolbar" role="group" aria-label="Axe d'analyse Pareto">
|
| 41 |
-
<button type="button" class="pareto-toggle active" data-axis="cost"
|
| 42 |
-
onclick="setParetoAxis('cost')"
|
| 43 |
-
aria-pressed="true"
|
| 44 |
-
data-i18n="pareto_axis_cost">Coût € / 1000 pages</button>
|
| 45 |
-
<button type="button" class="pareto-toggle" data-axis="speed"
|
| 46 |
-
onclick="setParetoAxis('speed')"
|
| 47 |
-
aria-pressed="false"
|
| 48 |
-
data-i18n="pareto_axis_speed">Vitesse (s / page)</button>
|
| 49 |
-
<button type="button" class="pareto-toggle pareto-experimental" data-axis="co2"
|
| 50 |
-
onclick="setParetoAxis('co2')"
|
| 51 |
-
aria-pressed="false"
|
| 52 |
-
data-i18n="pareto_axis_co2"
|
| 53 |
-
title="Estimation expérimentale">Carbone (g CO₂)</button>
|
| 54 |
-
</div>
|
| 55 |
-
<div class="chart-canvas-wrap"><canvas id="pareto-chart" role="img" aria-label="Front Pareto coût/qualité" data-a11y-label="Front Pareto coût/qualité"></canvas></div>
|
| 56 |
-
<div id="pareto-method-note" class="pareto-note" data-i18n="pareto_note">
|
| 57 |
-
Les moteurs sur la frontière de Pareto (en évidence) sont ceux pour
|
| 58 |
-
lesquels aucun autre moteur n'offre simultanément un meilleur CER ET
|
| 59 |
-
un meilleur coût. Prix indicatifs (table interne, datée). Le mode
|
| 60 |
-
carbone est expérimental.
|
| 61 |
-
</div>
|
| 62 |
-
<details class="pareto-assumptions">
|
| 63 |
-
<summary data-i18n="pareto_assumptions_summary">Hypothèses détaillées par moteur</summary>
|
| 64 |
-
<ul id="pareto-assumptions-list"></ul>
|
| 65 |
-
</details>
|
| 66 |
-
</div>
|
| 67 |
-
|
| 68 |
-
{# Sections déplacées vers les vues XerOCR (S13 cleanup) :
|
| 69 |
-
- calibration_summary_html, reliability_diagrams_html → Engines/Diagnostics
|
| 70 |
-
- ner_summary_html, ner_per_category_html → Engines/Diagnostics
|
| 71 |
-
- philological_profile_html → Engines/Diagnostics
|
| 72 |
-
- searchability_html, numerical_sequences_html → Engines/Diagnostics
|
| 73 |
-
- specialization_html → Crosses (passthrough)
|
| 74 |
-
- divergence_matrix_html, oracle_gap_html → Crosses (passthrough)
|
| 75 |
-
Doubler le rendu HTML coûtait ~120 lignes inutiles dans chaque rapport.
|
| 76 |
-
Les renderers eux-mêmes sont toujours invoqués côté générateur ;
|
| 77 |
-
seul le ``{% include %}`` legacy a été retiré. #}
|
| 78 |
-
|
| 79 |
-
{# Phase B6 — view_results (par EvaluationView) — pas encore migrée
|
| 80 |
-
vers les vues XerOCR. Conservée le temps de la migration. #}
|
| 81 |
{% if view_results_html %}
|
| 82 |
{{ view_results_html | safe }}
|
| 83 |
{% endif %}
|
| 84 |
|
| 85 |
-
{
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
</div>
|
| 89 |
-
{% endif %}
|
| 90 |
-
|
| 91 |
-
<!-- Sprint 7 — Matrice de corrélation -->
|
| 92 |
<div class="chart-card technical" style="grid-column:1/-1">
|
| 93 |
<h3 data-i18n="h_correlation">Matrice de corrélation entre métriques</h3>
|
| 94 |
<div style="margin-bottom:.5rem">
|
|
|
|
| 1 |
|
| 2 |
+
<!-- ════ Vue 5 : Analyses (reliquat post-Phase 21) ═══════════════════
|
| 3 |
+
Quasi-vide après le dispatch Phase 21 (A/B/C). Contenu restant :
|
| 4 |
+
- view_results_html — Phase 23 : intégration en <details>
|
| 5 |
+
dans la vue XerOCR la plus pertinente.
|
| 6 |
+
- corr-matrix interactif — Commit 21D : suppression (la version
|
| 7 |
+
Python `engines_correlation_matrix_html`
|
| 8 |
+
couvre déjà le besoin dans
|
| 9 |
+
engines_diagnostics.html, sans selector
|
| 10 |
+
JS mais avec sélection serveur).
|
| 11 |
+
L'onglet Analyses lui-même sera retiré en Phase 22 une fois ces
|
| 12 |
+
deux derniers items traités. Le wrapper `<div id="view-analyses">`
|
| 13 |
+
reste pour ne pas casser les deeplinks `#analyses` legacy entre
|
| 14 |
+
Phase 21 et Phase 22. -->
|
|
|
|
|
|
|
|
|
|
| 15 |
<div id="view-analyses" class="view">
|
| 16 |
<div class="charts-grid">
|
| 17 |
|
| 18 |
+
{# Phase B6 — view_results (par EvaluationView) — destinée à
|
| 19 |
+
Phase 23 : intégration en <details> dans la vue XerOCR
|
| 20 |
+
la plus pertinente (option α conservatrice). #}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
{% if view_results_html %}
|
| 22 |
{{ view_results_html | safe }}
|
| 23 |
{% endif %}
|
| 24 |
|
| 25 |
+
{# Matrice de corrélation interactive (JS) — sera supprimée en
|
| 26 |
+
Commit 21D. La version Python statique vit déjà dans
|
| 27 |
+
engines_diagnostics.html via engines_correlation_matrix_html. #}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
<div class="chart-card technical" style="grid-column:1/-1">
|
| 29 |
<h3 data-i18n="h_correlation">Matrice de corrélation entre métriques</h3>
|
| 30 |
<div style="margin-bottom:.5rem">
|
|
@@ -35,4 +35,63 @@
|
|
| 35 |
{{ crosses_venn_html | safe }}
|
| 36 |
</section>
|
| 37 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
</section>
|
|
|
|
| 35 |
{{ crosses_venn_html | safe }}
|
| 36 |
</section>
|
| 37 |
{% endif %}
|
| 38 |
+
|
| 39 |
+
{# Phase 21C dispatch — depuis view_analyses.html :
|
| 40 |
+
- chart-quality-cer : scatter qualité image × CER (cross-dimensional)
|
| 41 |
+
- pareto-chart : front Pareto coût/vitesse/CO₂ × CER, interactif
|
| 42 |
+
- marginal_cost_html : coût marginal par erreur évitée entre paires
|
| 43 |
+
#}
|
| 44 |
+
<section class="card xer-chart-card" aria-labelledby="crosses-quality-cer-title"
|
| 45 |
+
style="margin-top:1.25rem">
|
| 46 |
+
<h3 id="crosses-quality-cer-title" data-i18n="h_quality_cer">Qualité image ↔ CER (scatter plot)</h3>
|
| 47 |
+
<p data-i18n="quality_cer_note">
|
| 48 |
+
Chaque point = un document. Axe X = score qualité image [0–1]. Axe Y = CER. Corrélation négative attendue.
|
| 49 |
+
</p>
|
| 50 |
+
<div class="chart-canvas-wrap">
|
| 51 |
+
<canvas id="chart-quality-cer" role="img"
|
| 52 |
+
aria-label="Corrélation qualité d'image / CER"
|
| 53 |
+
data-a11y-label="Corrélation qualité d'image / CER"></canvas>
|
| 54 |
+
</div>
|
| 55 |
+
</section>
|
| 56 |
+
|
| 57 |
+
<section class="card xer-chart-card pareto-card" aria-labelledby="crosses-pareto-title"
|
| 58 |
+
style="margin-top:1.25rem">
|
| 59 |
+
<h3 id="crosses-pareto-title" data-i18n="h_pareto">Compromis qualité / coût</h3>
|
| 60 |
+
<div class="pareto-toolbar" role="group" aria-label="Axe d'analyse Pareto">
|
| 61 |
+
<button type="button" class="pareto-toggle active" data-axis="cost"
|
| 62 |
+
onclick="setParetoAxis('cost')"
|
| 63 |
+
aria-pressed="true"
|
| 64 |
+
data-i18n="pareto_axis_cost">Coût € / 1000 pages</button>
|
| 65 |
+
<button type="button" class="pareto-toggle" data-axis="speed"
|
| 66 |
+
onclick="setParetoAxis('speed')"
|
| 67 |
+
aria-pressed="false"
|
| 68 |
+
data-i18n="pareto_axis_speed">Vitesse (s / page)</button>
|
| 69 |
+
<button type="button" class="pareto-toggle pareto-experimental" data-axis="co2"
|
| 70 |
+
onclick="setParetoAxis('co2')"
|
| 71 |
+
aria-pressed="false"
|
| 72 |
+
data-i18n="pareto_axis_co2"
|
| 73 |
+
title="Estimation expérimentale">Carbone (g CO₂)</button>
|
| 74 |
+
</div>
|
| 75 |
+
<div class="chart-canvas-wrap">
|
| 76 |
+
<canvas id="pareto-chart" role="img"
|
| 77 |
+
aria-label="Front Pareto coût/qualité"
|
| 78 |
+
data-a11y-label="Front Pareto coût/qualité"></canvas>
|
| 79 |
+
</div>
|
| 80 |
+
<div id="pareto-method-note" class="pareto-note" data-i18n="pareto_note">
|
| 81 |
+
Les moteurs sur la frontière de Pareto (en évidence) sont ceux pour
|
| 82 |
+
lesquels aucun autre moteur n'offre simultanément un meilleur CER ET
|
| 83 |
+
un meilleur coût. Prix indicatifs (table interne, datée). Le mode
|
| 84 |
+
carbone est expérimental.
|
| 85 |
+
</div>
|
| 86 |
+
<details class="pareto-assumptions">
|
| 87 |
+
<summary data-i18n="pareto_assumptions_summary">Hypothèses détaillées par moteur</summary>
|
| 88 |
+
<ul id="pareto-assumptions-list"></ul>
|
| 89 |
+
</details>
|
| 90 |
+
</section>
|
| 91 |
+
|
| 92 |
+
{% if marginal_cost_html %}
|
| 93 |
+
<section class="card xer-chart-card" style="margin-top:1.25rem">
|
| 94 |
+
{{ marginal_cost_html | safe }}
|
| 95 |
+
</section>
|
| 96 |
+
{% endif %}
|
| 97 |
</section>
|
|
@@ -61,4 +61,17 @@
|
|
| 61 |
{{ documents_worst_lines_html | safe }}
|
| 62 |
</div>
|
| 63 |
{% endif %}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
</section>
|
|
|
|
| 61 |
{{ documents_worst_lines_html | safe }}
|
| 62 |
</div>
|
| 63 |
{% endif %}
|
| 64 |
+
|
| 65 |
+
{# Phase 21C dispatch — CER par document : chart doc-centric depuis
|
| 66 |
+
view_analyses.html. Construit par buildCharts() (lazy au premier
|
| 67 |
+
passage sur l'onglet Documents). #}
|
| 68 |
+
<section class="card xer-chart-card" aria-labelledby="documents-cer-doc-title"
|
| 69 |
+
style="margin-top:1.25rem">
|
| 70 |
+
<h3 id="documents-cer-doc-title" data-i18n="h_cer_doc">CER par document (tous moteurs)</h3>
|
| 71 |
+
<div class="chart-canvas-wrap">
|
| 72 |
+
<canvas id="chart-cer-doc" role="img"
|
| 73 |
+
aria-label="CER par document"
|
| 74 |
+
data-a11y-label="CER par document"></canvas>
|
| 75 |
+
</div>
|
| 76 |
+
</section>
|
| 77 |
</section>
|
|
@@ -271,7 +271,11 @@ class TestReportIntegration:
|
|
| 271 |
out = tmp_path / "report.html"
|
| 272 |
ReportGenerator(benchmark_result).generate(out)
|
| 273 |
html = out.read_text(encoding="utf-8")
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
assert 'id="pareto-chart"' in html
|
| 276 |
assert 'setParetoAxis(\'cost\')' in html
|
| 277 |
assert 'setParetoAxis(\'speed\')' in html
|
|
|
|
| 271 |
out = tmp_path / "report.html"
|
| 272 |
ReportGenerator(benchmark_result).generate(out)
|
| 273 |
html = out.read_text(encoding="utf-8")
|
| 274 |
+
# Phase 21C : Pareto migré vers crosses.html sous convention XerOCR.
|
| 275 |
+
# Le wrapper est passé de <div class="chart-card pareto-card"> à
|
| 276 |
+
# <section class="card xer-chart-card pareto-card">. La classe
|
| 277 |
+
# ``pareto-card`` (border-left vert via CSS) est préservée.
|
| 278 |
+
assert "pareto-card" in html
|
| 279 |
assert 'id="pareto-chart"' in html
|
| 280 |
assert 'setParetoAxis(\'cost\')' in html
|
| 281 |
assert 'setParetoAxis(\'speed\')' in html
|
|
@@ -168,7 +168,11 @@ class TestEndToEnd:
|
|
| 168 |
for marker in [
|
| 169 |
'class="synth-card"', # Sprint 19 narrative
|
| 170 |
'class="cdd-card"', # Sprint 18 CDD
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
'id="glossary-panel"', # Sprint 21 glossaire
|
| 173 |
'id="customize-panel"', # Sprint 21 personnalisation
|
| 174 |
'btn-customize',
|
|
|
|
| 168 |
for marker in [
|
| 169 |
'class="synth-card"', # Sprint 19 narrative
|
| 170 |
'class="cdd-card"', # Sprint 18 CDD
|
| 171 |
+
# Phase 21C : Pareto migré dans Crosses sous convention XerOCR.
|
| 172 |
+
# Wrapper passé de <div class="chart-card pareto-card"> à
|
| 173 |
+
# <section class="card xer-chart-card pareto-card">.
|
| 174 |
+
'pareto-card', # Sprint 20 Pareto (post-21C)
|
| 175 |
+
'id="pareto-chart"', # Canvas Pareto présent
|
| 176 |
'id="glossary-panel"', # Sprint 21 glossaire
|
| 177 |
'id="customize-panel"', # Sprint 21 personnalisation
|
| 178 |
'btn-customize',
|
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests Phase 21C — dispatch Documents + Crosses (chart-cer-doc, chart-quality-cer, pareto, marginal_cost).
|
| 2 |
+
|
| 3 |
+
Vérifie que les charts cross-dimensionnels et doc-centriques sont
|
| 4 |
+
correctement déplacés depuis view_analyses.html vers leur destination
|
| 5 |
+
XerOCR sémantique :
|
| 6 |
+
|
| 7 |
+
- chart-cer-doc → documents.html (doc-centric line chart)
|
| 8 |
+
- chart-quality-cer → crosses.html (cross-dim image quality × CER)
|
| 9 |
+
- pareto-chart → crosses.html (cross-dim cost/quality interactif)
|
| 10 |
+
- marginal_cost_html → crosses.html (cost marginal par erreur évitée)
|
| 11 |
+
|
| 12 |
+
Anti-theater : chaque test échoue par sabotage ciblé (ID rename,
|
| 13 |
+
template miss, etc.) — preuve documentée dans les docstrings de classe.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
|
| 20 |
+
import pytest
|
| 21 |
+
|
| 22 |
+
REPO_ROOT = Path(__file__).resolve().parents[2]
|
| 23 |
+
TEMPLATES = REPO_ROOT / "picarones" / "reports" / "html" / "templates"
|
| 24 |
+
DOCS_VIEW = TEMPLATES / "views" / "documents.html"
|
| 25 |
+
CROSSES_VIEW = TEMPLATES / "views" / "crosses.html"
|
| 26 |
+
ANALYSES_VIEW = TEMPLATES / "view_analyses.html"
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 30 |
+
# 1. Documents — chart-cer-doc
|
| 31 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class TestChartCerDocInDocuments:
|
| 35 |
+
"""``chart-cer-doc`` (CER par document, tous moteurs) doit vivre
|
| 36 |
+
dans documents.html — chart doc-centric va avec la galerie.
|
| 37 |
+
|
| 38 |
+
Anti-theater : retirer/renommer le canvas → ces tests échouent.
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
@pytest.fixture(scope="class")
|
| 42 |
+
def docs_src(self) -> str:
|
| 43 |
+
return DOCS_VIEW.read_text(encoding="utf-8")
|
| 44 |
+
|
| 45 |
+
@pytest.fixture(scope="class")
|
| 46 |
+
def analyses_src(self) -> str:
|
| 47 |
+
return ANALYSES_VIEW.read_text(encoding="utf-8")
|
| 48 |
+
|
| 49 |
+
def test_canvas_in_documents(self, docs_src: str) -> None:
|
| 50 |
+
assert 'id="chart-cer-doc"' in docs_src
|
| 51 |
+
|
| 52 |
+
def test_section_heading_present(self, docs_src: str) -> None:
|
| 53 |
+
assert 'id="documents-cer-doc-title"' in docs_src
|
| 54 |
+
assert 'data-i18n="h_cer_doc"' in docs_src
|
| 55 |
+
|
| 56 |
+
def test_aria_label_preserved(self, docs_src: str) -> None:
|
| 57 |
+
assert 'aria-label="CER par document"' in docs_src
|
| 58 |
+
assert 'data-a11y-label="CER par document"' in docs_src
|
| 59 |
+
|
| 60 |
+
def test_not_in_analyses(self, analyses_src: str) -> None:
|
| 61 |
+
assert 'id="chart-cer-doc"' not in analyses_src, (
|
| 62 |
+
"chart-cer-doc doit être retiré de view_analyses.html (Phase 21C)"
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 67 |
+
# 2. Crosses — chart-quality-cer
|
| 68 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class TestChartQualityCerInCrosses:
|
| 72 |
+
"""``chart-quality-cer`` (qualité image × CER) est un scatter
|
| 73 |
+
cross-dimensionnel — vit dans crosses.html.
|
| 74 |
+
|
| 75 |
+
Anti-theater : double-vérification (présent dans crosses + absent
|
| 76 |
+
dans analyses). Renommer l'ID dans un seul des 2 fichiers fait
|
| 77 |
+
immédiatement échouer un test.
|
| 78 |
+
"""
|
| 79 |
+
|
| 80 |
+
@pytest.fixture(scope="class")
|
| 81 |
+
def crosses_src(self) -> str:
|
| 82 |
+
return CROSSES_VIEW.read_text(encoding="utf-8")
|
| 83 |
+
|
| 84 |
+
@pytest.fixture(scope="class")
|
| 85 |
+
def analyses_src(self) -> str:
|
| 86 |
+
return ANALYSES_VIEW.read_text(encoding="utf-8")
|
| 87 |
+
|
| 88 |
+
def test_canvas_in_crosses(self, crosses_src: str) -> None:
|
| 89 |
+
assert 'id="chart-quality-cer"' in crosses_src
|
| 90 |
+
|
| 91 |
+
def test_section_heading_present(self, crosses_src: str) -> None:
|
| 92 |
+
assert 'id="crosses-quality-cer-title"' in crosses_src
|
| 93 |
+
assert 'data-i18n="h_quality_cer"' in crosses_src
|
| 94 |
+
|
| 95 |
+
def test_note_present(self, crosses_src: str) -> None:
|
| 96 |
+
assert 'data-i18n="quality_cer_note"' in crosses_src
|
| 97 |
+
|
| 98 |
+
def test_not_in_analyses(self, analyses_src: str) -> None:
|
| 99 |
+
assert 'id="chart-quality-cer"' not in analyses_src
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 103 |
+
# 3. Crosses — pareto-chart (canvas + toolbar + assumptions)
|
| 104 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class TestParetoChartInCrosses:
|
| 108 |
+
"""Le Pareto avec toolbar 3 axes (cost/speed/co2) + détails
|
| 109 |
+
hypothèses est entièrement dans crosses.html.
|
| 110 |
+
|
| 111 |
+
Anti-theater : retirer un toggle, retirer la liste d'hypothèses,
|
| 112 |
+
ou laisser une copie dans analyses → tests échouent.
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
@pytest.fixture(scope="class")
|
| 116 |
+
def crosses_src(self) -> str:
|
| 117 |
+
return CROSSES_VIEW.read_text(encoding="utf-8")
|
| 118 |
+
|
| 119 |
+
@pytest.fixture(scope="class")
|
| 120 |
+
def analyses_src(self) -> str:
|
| 121 |
+
return ANALYSES_VIEW.read_text(encoding="utf-8")
|
| 122 |
+
|
| 123 |
+
def test_canvas_in_crosses(self, crosses_src: str) -> None:
|
| 124 |
+
assert 'id="pareto-chart"' in crosses_src
|
| 125 |
+
|
| 126 |
+
def test_toolbar_present(self, crosses_src: str) -> None:
|
| 127 |
+
assert 'class="pareto-toolbar"' in crosses_src
|
| 128 |
+
assert 'role="group"' in crosses_src
|
| 129 |
+
|
| 130 |
+
@pytest.mark.parametrize("axis", ["cost", "speed", "co2"])
|
| 131 |
+
def test_each_axis_toggle_present(self, crosses_src: str, axis: str) -> None:
|
| 132 |
+
assert f'data-axis="{axis}"' in crosses_src
|
| 133 |
+
assert f"setParetoAxis('{axis}')" in crosses_src
|
| 134 |
+
|
| 135 |
+
def test_assumptions_list_container(self, crosses_src: str) -> None:
|
| 136 |
+
assert 'id="pareto-assumptions-list"' in crosses_src
|
| 137 |
+
assert 'class="pareto-assumptions"' in crosses_src
|
| 138 |
+
|
| 139 |
+
def test_method_note_present(self, crosses_src: str) -> None:
|
| 140 |
+
assert 'id="pareto-method-note"' in crosses_src
|
| 141 |
+
assert 'data-i18n="pareto_note"' in crosses_src
|
| 142 |
+
|
| 143 |
+
def test_not_in_analyses(self, analyses_src: str) -> None:
|
| 144 |
+
assert 'id="pareto-chart"' not in analyses_src
|
| 145 |
+
assert 'id="pareto-assumptions-list"' not in analyses_src
|
| 146 |
+
assert 'class="pareto-toolbar"' not in analyses_src
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 150 |
+
# 4. Crosses — marginal_cost_html
|
| 151 |
+
# ─────────────────────────────────────────────────────────────────────────────
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
class TestMarginalCostInCrosses:
|
| 155 |
+
"""``marginal_cost_html`` mesure le coût marginal d'une erreur évitée
|
| 156 |
+
entre paires de moteurs — cross-dimensionnel cost × quality.
|
| 157 |
+
|
| 158 |
+
Anti-theater : déplacer ailleurs ou oublier le wiring → tests échouent.
|
| 159 |
+
"""
|
| 160 |
+
|
| 161 |
+
def test_wired_in_crosses(self) -> None:
|
| 162 |
+
crosses_src = CROSSES_VIEW.read_text(encoding="utf-8")
|
| 163 |
+
assert "{% if marginal_cost_html %}" in crosses_src, (
|
| 164 |
+
"marginal_cost_html doit être câblé conditionnellement dans crosses.html"
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
def test_not_in_analyses(self) -> None:
|
| 168 |
+
analyses_src = ANALYSES_VIEW.read_text(encoding="utf-8")
|
| 169 |
+
assert "{% if marginal_cost_html %}" not in analyses_src
|
| 170 |
+
|
| 171 |
+
def test_not_in_engines_diagnostics(self) -> None:
|
| 172 |
+
"""Anti-confusion : marginal_cost n'est PAS dans diagnostics —
|
| 173 |
+
il appartient à Crosses (cross-dimensional), pas à 'où ça plante'."""
|
| 174 |
+
diag_src = (TEMPLATES / "views" / "engines_diagnostics.html").read_text(encoding="utf-8")
|
| 175 |
+
assert "{% if marginal_cost_html %}" not in diag_src
|