"""Rendu HTML « Lisibilité (delta Flesch) » — Sprint 87 (A.II.2). Suite directe ``picarones/core/readability.py`` (Sprint 52) + câblage runner Sprint 87. Pattern identique aux autres rendus : server-side, pas de JS, anti-injection systématique. Vue --- Tableau résumé moteur × {delta_mean, delta_median, % over-normalisés, % under-normalisés, n_docs}. Cellule delta_mean colorée par gradient : - vert (delta ≈ 0) : OCR fidèle à la GT en complexité. - orange (delta > 5) : over-normalisation (typique LLM). - bleu (delta < -5) : dégradation OCR brutale. Adaptative : ``""`` si aucun moteur n'a de ``aggregated_readability``. """ from __future__ import annotations from html import escape as _e from typing import Optional from picarones.report.render_helpers import color_diverging def _bg_for_flesch_delta(delta: float) -> str: """Vert au centre (delta ≈ 0), orange en sur-normalisation (delta > 0), bleu en sous-normalisation (delta < 0). Saturation à ±15 pts Flesch. """ if abs(delta) <= 1.0: return "#a7f0a7" # neutre vert clair, indistinguable du bruit return color_diverging( delta, max_abs=15.0, neutral_rgb=(167, 240, 167), positive_rgb=(220, 140, 60), negative_rgb=(90, 160, 210), ) def build_readability_summary_html( engines: list[dict], labels: Optional[dict[str, str]] = None, ) -> str: """Construit la table HTML lisibilité. Returns ``""`` si aucun moteur n'a de signal. """ rows = [ e for e in engines if isinstance(e.get("aggregated_readability"), dict) ] if not rows: return "" labels = labels or {} title = labels.get("readability_title", "Lisibilité (delta Flesch)") note = labels.get( "readability_note", "Différence de lisibilité Flesch entre la sortie OCR et la " "GT. Δ > +5 : over-normalisation (typique des LLM qui " "modernisent un texte ancien). Δ < -5 : dégradation " "brutale. Δ ≈ 0 : fidélité au registre linguistique.", ) col_engine = labels.get("readability_engine", "Moteur") col_mean = labels.get("readability_delta_mean", "Δ moyen") col_median = labels.get("readability_delta_median", "Δ médian") col_over = labels.get( "readability_over_norm_rate", "% over-normalisé", ) col_under = labels.get( "readability_under_norm_count", "Docs under-normalisés", ) col_docs = labels.get("readability_docs", "Docs") parts = [ '
| ' f'{_e(col)} | ' ) parts.append("|||||
|---|---|---|---|---|---|
| {_e(str(name))} | ' f'' f'{delta_mean:+.2f} | ' f'{delta_median:+.2f} | ' f'{over_rate * 100:.0f}% | ' f'{n_under} | ' f'{n_docs} | ' f'