feat: quality_tier CPAM (A/B/C) + requires_review + warnings catégorisés
- ControleCPAM enrichi : quality_tier, requires_review, quality_warnings - _assess_quality_tier() : classification basée sur score adversarial + warnings - Tier C (requires_review) : score <4, code hors périmètre, >2 preuves non traçables - Tier B : score 4-6, warnings mineurs - Tier A : score >=7, 0 critique - _format_response() : bandeau "REVUE MANUELLE REQUISE" pour tier C, sections CRITIQUES/MINEURS séparées - Badge qualité dans le viewer CPAM (vert A / orange B / rouge C) - 17 tests : tier A/B/C, bandeau, séparation warnings, backward compat Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -300,10 +300,91 @@ def _build_correction_prompt(
|
||||
return original_prompt + correction_block
|
||||
|
||||
|
||||
def _format_response(parsed: dict, ref_warnings: list[str] | None = None) -> str:
|
||||
def _assess_quality_tier(
|
||||
parsed: dict,
|
||||
ref_warnings: list[str],
|
||||
grounding_warnings: list[str],
|
||||
code_warnings: list[str],
|
||||
adversarial_result: dict | None,
|
||||
) -> tuple[str, bool, list[str]]:
|
||||
"""Évalue le tier qualité (A/B/C) et le flag requires_review.
|
||||
|
||||
Classification :
|
||||
- Tier C (requires_review=True) :
|
||||
score adversarial < 4 OU code_warnings > 0 OU grounding_warnings > 2
|
||||
- Tier B :
|
||||
score adversarial 4-6 OU ref_warnings > 0 OU grounding_warnings 1-2
|
||||
- Tier A :
|
||||
score adversarial >= 7, 0 warning critique, <= 1 warning mineur
|
||||
|
||||
Returns:
|
||||
(tier, requires_review, categorized_warnings)
|
||||
"""
|
||||
categorized: list[str] = []
|
||||
score = adversarial_result.get("score_confiance", -1) if adversarial_result else -1
|
||||
has_critical = False
|
||||
minor_count = 0
|
||||
|
||||
# --- Warnings critiques ---
|
||||
for w in code_warnings:
|
||||
categorized.append(f"[CRITIQUE] {w}")
|
||||
has_critical = True
|
||||
|
||||
if score != -1 and score <= 3:
|
||||
categorized.append(f"[CRITIQUE] Score adversarial très bas : {score}/10")
|
||||
has_critical = True
|
||||
|
||||
if len(grounding_warnings) > 2:
|
||||
for w in grounding_warnings:
|
||||
categorized.append(f"[CRITIQUE] {w}")
|
||||
has_critical = True
|
||||
elif grounding_warnings:
|
||||
for w in grounding_warnings:
|
||||
categorized.append(f"[MINEUR] {w}")
|
||||
minor_count += 1
|
||||
|
||||
# --- Warnings mineurs ---
|
||||
for w in ref_warnings:
|
||||
categorized.append(f"[MINEUR] {w}")
|
||||
minor_count += 1
|
||||
|
||||
if adversarial_result and not adversarial_result.get("coherent", True):
|
||||
for e in adversarial_result.get("erreurs", []):
|
||||
if isinstance(e, str) and e.strip():
|
||||
categorized.append(f"[MINEUR] Incohérence : {e}")
|
||||
minor_count += 1
|
||||
|
||||
if score != -1 and 4 <= score <= 6:
|
||||
categorized.append(f"[MINEUR] Score adversarial moyen : {score}/10")
|
||||
minor_count += 1
|
||||
|
||||
# --- Classification ---
|
||||
if has_critical or (score != -1 and score < 4):
|
||||
tier = "C"
|
||||
requires_review = True
|
||||
elif minor_count > 0 or (score != -1 and 4 <= score <= 6):
|
||||
tier = "B"
|
||||
requires_review = False
|
||||
else:
|
||||
tier = "A"
|
||||
requires_review = False
|
||||
|
||||
return tier, requires_review, categorized
|
||||
|
||||
|
||||
def _format_response(
|
||||
parsed: dict,
|
||||
ref_warnings: list[str] | None = None,
|
||||
quality_tier: str | None = None,
|
||||
categorized_warnings: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Formate la réponse LLM en texte lisible."""
|
||||
sections = []
|
||||
|
||||
# Bandeau qualité si tier C
|
||||
if quality_tier == "C":
|
||||
sections.append("⚠ REVUE MANUELLE REQUISE (Qualité : C)")
|
||||
|
||||
analyse = parsed.get("analyse_contestation")
|
||||
if analyse:
|
||||
sections.append(f"ANALYSE DE LA CONTESTATION\n{analyse}")
|
||||
@@ -368,8 +449,20 @@ def _format_response(parsed: dict, ref_warnings: list[str] | None = None) -> str
|
||||
if conclusion:
|
||||
sections.append(f"CONCLUSION\n{conclusion}")
|
||||
|
||||
# Avertissements sur les références non vérifiables
|
||||
if ref_warnings:
|
||||
# Avertissements catégorisés (nouveau format)
|
||||
if categorized_warnings:
|
||||
critiques = [w for w in categorized_warnings if w.startswith("[CRITIQUE]")]
|
||||
mineurs = [w for w in categorized_warnings if w.startswith("[MINEUR]")]
|
||||
if critiques:
|
||||
sections.append(
|
||||
"AVERTISSEMENTS CRITIQUES\n" + "\n".join(f"- {w}" for w in critiques)
|
||||
)
|
||||
if mineurs:
|
||||
sections.append(
|
||||
"AVERTISSEMENTS MINEURS\n" + "\n".join(f"- {w}" for w in mineurs)
|
||||
)
|
||||
elif ref_warnings:
|
||||
# Fallback ancien format
|
||||
warning_text = "\n".join(f"- {w}" for w in ref_warnings)
|
||||
sections.append(f"AVERTISSEMENT — REFERENCES NON VÉRIFIÉES\n{warning_text}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user