"""Rendu HTML « Spécialisation inter-moteurs » — Sprint 89
(A.II.8b).
Suite directe ``picarones/core/specialization.py``. Vue
**factuelle** sans recommandation : on liste les paires de
moteurs les plus spécialisées, le chercheur arbitre.
Pattern identique aux autres rendus : server-side, pas de JS,
anti-injection systématique.
"""
from __future__ import annotations
from html import escape as _e
from typing import Optional
from picarones.measurements.specialization import (
compute_specialization_matrix,
top_specialized_pairs,
)
from picarones.report.render_helpers import color_single_gradient
#: Bleu profond cible — préservé de l'ancien `_color_for_score` local.
_SPECIALIZATION_BLUE = (50, 110, 180)
def _category_label(cat: str, labels: dict[str, str]) -> str:
return labels.get(f"specialization_cat_{cat}", cat)
def build_specialization_html(
taxonomies: Optional[dict[str, dict[str, float]]],
labels: Optional[dict[str, str]] = None,
*,
top_n: int = 5,
) -> str:
"""Construit la vue HTML de spécialisation inter-moteurs.
Parameters
----------
taxonomies:
Map ``{engine: {error_class: count}}``. Si ``None`` ou
moins de 2 moteurs, retourne ``""``.
labels:
Dict i18n. Clés sous le préfixe ``specialization_*``.
top_n:
Nombre de paires à afficher (défaut 5).
"""
if not taxonomies or len(taxonomies) < 2:
return ""
matrix_data = compute_specialization_matrix(taxonomies)
if not matrix_data:
return ""
pairs = top_specialized_pairs(matrix_data, n=top_n)
if not pairs:
return ""
labels = labels or {}
title = labels.get(
"specialization_title", "Spécialisation inter-moteurs",
)
note = labels.get(
"specialization_note",
"Score de divergence Jensen-Shannon entre les profils "
"taxonomiques de chaque paire de moteurs (0 = profils "
"identiques, 1 = totalement disjoints). Une paire très "
"spécialisée signale des erreurs de natures différentes "
"— c'est au chercheur d'en tirer parti, pas à l'outil "
"de prescrire un ensemble.",
)
h_a = labels.get("specialization_engine_a", "Moteur A")
h_b = labels.get("specialization_engine_b", "Moteur B")
h_score = labels.get("specialization_score", "Score")
h_cat = labels.get("specialization_category", "Lecture")
parts = [
'',
f'
{_e(title)}
',
f'
'
f'{_e(note)}
',
'
',
'
',
]
for col in (h_a, h_b, h_score, h_cat):
parts.append(
f'
'
f'{_e(col)}
'
)
parts.append("
")
for pair in pairs:
score = float(pair.get("score") or 0.0)
cat = pair.get("category") or "?"
color = color_single_gradient(score, end_rgb=_SPECIALIZATION_BLUE)
parts.append(
f'