Spaces:
Sleeping
Sleeping
Claude
docs(sprint-H.8): cleanup obsolete legacy/shim language in production docstrings
e407ec0 unverified | """Détecteurs narratifs liés à l'*opportunité d'ensemble inter-moteurs* (chantier 5). | |
| 1 détecteur déplacé depuis ``narrative/detectors.py`` : | |
| - :func:`detect_ensemble_opportunity` (Sprint 36) | |
| """ | |
| from __future__ import annotations | |
| from typing import Optional | |
| from picarones.domain.facts import Fact, FactImportance, FactType | |
| from picarones.reports.narrative.registry import register_detector | |
| def detect_ensemble_opportunity(benchmark_data: dict) -> list[Fact]: | |
| """Deux moteurs très complémentaires : un voting majoritaire entre eux | |
| pourrait améliorer significativement le CER token-level. | |
| Lit la structure ``inter_engine_analysis`` produite par le runner | |
| (Sprint 35-36) et déclenche si la fraction d'erreurs du meilleur | |
| moteur récupérable par un ensemble dépasse 25 %. | |
| L'importance monte à ``HIGH`` quand le gap relatif dépasse 50 % | |
| (ensemble franchement profitable) — sinon reste à ``MEDIUM``. | |
| """ | |
| iea = benchmark_data.get("inter_engine_analysis") or {} | |
| comp = iea.get("complementarity") or {} | |
| if not comp: | |
| return [] | |
| relative_gap = float(comp.get("relative_gap") or 0.0) | |
| if relative_gap < 0.25: | |
| # En deçà de 25 %, l'ensemble n'apporterait quasi rien — on ne | |
| # remonte pas le fait pour ne pas bruiter la synthèse. | |
| return [] | |
| best_engine = comp.get("best_engine") or "" | |
| if not best_engine: | |
| return [] | |
| payload: dict = { | |
| "best_engine": best_engine, | |
| "best_recall_pct": round(float(comp.get("best_single_recall") or 0.0) * 100, 2), | |
| "oracle_recall_pct": round(float(comp.get("oracle_recall") or 0.0) * 100, 2), | |
| "absolute_gap_pct": round(float(comp.get("absolute_gap") or 0.0) * 100, 2), | |
| "relative_gap_pct": round(relative_gap * 100, 1), | |
| "doc_count": int(comp.get("doc_count") or 0), | |
| } | |
| # Paire la plus complémentaire — la divergence taxonomique, quand | |
| # disponible, fournit deux moteurs « candidats naturels ». Sinon on | |
| # tombe sur le best + le second-best en recall individuel. | |
| div = iea.get("taxonomy_divergence") or {} | |
| pair = div.get("max_pair") or [] | |
| pair_a = "" | |
| pair_b = "" | |
| divergence_value: Optional[float] = None | |
| if pair and len(pair) >= 3 and isinstance(pair[2], (int, float)) and pair[2] > 0: | |
| pair_a, pair_b, divergence_value = str(pair[0]), str(pair[1]), float(pair[2]) | |
| else: | |
| # Fallback : best engine + second-best engine par recall individuel | |
| per_engine = comp.get("per_engine_recall") or {} | |
| if len(per_engine) >= 2: | |
| ranked = sorted(per_engine.items(), key=lambda kv: kv[1], reverse=True) | |
| pair_a, pair_b = ranked[0][0], ranked[1][0] | |
| payload["pair_a"] = pair_a | |
| payload["pair_b"] = pair_b | |
| payload["divergence"] = round(divergence_value, 3) if divergence_value is not None else 0.0 | |
| payload["divergence_metric"] = (div.get("metric") or "js") | |
| importance = ( | |
| FactImportance.HIGH if relative_gap >= 0.5 else FactImportance.MEDIUM | |
| ) | |
| engines_involved: tuple[str, ...] = ( | |
| (pair_a, pair_b) if pair_a and pair_b else (best_engine,) | |
| ) | |
| return [Fact( | |
| type=FactType.ENSEMBLE_OPPORTUNITY, | |
| importance=importance, | |
| payload=payload, | |
| engines_involved=engines_involved, | |
| )] | |