fix: injecter les tags réels du dossier dans le prompt CPAM pour éliminer les tags génériques [TYPE-N]

Le LLM générait des tags génériques [BIO-N], [TRT-N] au lieu des vrais tags du dossier,
causant des warnings "preuve non traçable". Corrigé en 3 points :
- cpam_context: liste exhaustive des tags disponibles injectée dans le prompt
- templates: remplacement des patterns génériques par {tags_disponibles_str}
- cpam_validation: guardian step 4b résout les tags génériques résiduels

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dom
2026-03-04 23:14:40 +01:00
parent 798cee463f
commit 542797a124
3 changed files with 78 additions and 3 deletions

View File

@@ -191,7 +191,12 @@ def _build_tagged_context(dossier: DossierMedical) -> tuple[str, dict[str, str]]
if not lines:
return "", tag_map
text = "ÉLÉMENTS CLINIQUES RÉFÉRENCÉS (cite le tag [XX-N] dans tes preuves) :\n" + "\n".join(lines)
available = ", ".join(f"[{t}]" for t in sorted(tag_map.keys()))
text = (
"ÉLÉMENTS CLINIQUES RÉFÉRENCÉS :\n"
+ "\n".join(lines)
+ f"\n\nTAGS DISPONIBLES pour ce dossier (liste EXHAUSTIVE, n'en invente aucun) : {available}"
)
return text, tag_map
@@ -839,6 +844,10 @@ def _build_cpam_prompt(
+ "\n".join(ext_lines)
)
tags_disponibles_str = (
", ".join(f"[{t}]" for t in sorted(tag_map.keys()))
if tag_map else "(aucun)"
)
prompt = CPAM_ARGUMENTATION.format(
dossier_str=dossier_str,
asymetrie_str=asymetrie_str,
@@ -853,5 +862,6 @@ def _build_cpam_prompt(
extraction_str=extraction_str,
bio_confrontation_str=bio_confrontation,
numero_ogc=controle.numero_ogc,
tags_disponibles_str=tags_disponibles_str,
)
return prompt, tag_map

View File

@@ -32,6 +32,48 @@ def _fuzzy_match_ref(ref: str, tag_map: dict[str, str]) -> str | None:
return None
GENERIC_TAG_RE = re.compile(r"\[([A-Z]+)-N\]")
def _resolve_generic_tag(
prefix: str, fait: str, tag_map: dict[str, str]
) -> str | None:
"""Résout un tag générique [PREFIX-N] vers le vrai tag le plus proche.
Cherche dans *tag_map* les tags commençant par *prefix* dont le contenu
partage des mots-clés significatifs avec *fait*.
"""
fait_lower = fait.lower()
fait_words = set(fait_lower.split())
# Mots trop communs à ignorer
stop = {"de", "du", "le", "la", "les", "un", "une", "des", "et", "ou", "en", "à", "=", "mg", "l", "g", "ml"}
fait_words -= stop
best_tag: str | None = None
best_score = 0
for tag, content in tag_map.items():
if not tag.startswith(prefix + "-"):
continue
content_lower = content.lower()
content_words = set(content_lower.split()) - stop
# Score = nombre de mots en commun + bonus substring
score = len(fait_words & content_words)
if fait_lower in content_lower or content_lower in fait_lower:
score += 3
if score > best_score:
best_score = score
best_tag = tag
# Fallback : si un seul tag de ce prefix existe, le prendre
if best_tag is None:
candidates = [t for t in tag_map if t.startswith(prefix + "-")]
if len(candidates) == 1:
best_tag = candidates[0]
return best_tag
def _validate_grounding(response_data: dict, tag_map: dict[str, str]) -> list[str]:
"""Vérifie que les références dans preuves correspondent à des tags existants.
@@ -856,6 +898,28 @@ def _guardian_deterministic(
report["preuves_invalid_tags"].append(tag)
penalties += 0.5
# ===== 4b. Corriger les tags génériques [TYPE-N] → vrai tag =====
if tag_map:
for moyen in result.get("moyens_defense", []):
if not isinstance(moyen, dict):
continue
for preuve in moyen.get("preuves", []):
if not isinstance(preuve, dict):
continue
ref = str(preuve.get("ref", ""))
m = GENERIC_TAG_RE.search(ref)
if m:
prefix = m.group(1) # ex: "BIO"
fait = str(preuve.get("fait", "")).lower()
best_tag = _resolve_generic_tag(prefix, fait, tag_map)
if best_tag:
preuve["ref"] = f"[{best_tag}]"
report["tags_corrected"] = report.get("tags_corrected", 0) + 1
logger.info(
"Guardian 4b: [%s-N] → [%s] (fait: %s)",
prefix, best_tag, fait[:60],
)
# ===== 5. Nettoyage des champs texte libre =====
# Remplacer les valeurs bio hallucinées dans les strings (conclusion, rappel, etc.)
text_fields = [

View File

@@ -329,7 +329,8 @@ CONSIGNES DE RÉDACTION DES MOYENS
6. JAMAIS d'argument sans preuve traçable — si tu n'as pas la preuve, NE FAIS PAS l'argument
7. Ton ASSERTIF mais factuel — pas de formules creuses ("il convient de noter que...")
8. Si un point CPAM est légitime, le reconnaître CLAIREMENT (R4)
9. Tags valides UNIQUEMENT : [DP], [DAS-N], [BIO-N], [IMG-N], [TRT-N], [ACTE-N], [ANT-N], [COMPL-N]
9. Tags valides UNIQUEMENT ceux listés ci-dessus : {tags_disponibles_str}
Si un élément n'a pas de tag, décris le fait en clair SANS inventer de tag
Réponds UNIQUEMENT avec un objet JSON :
{{
@@ -346,7 +347,7 @@ Réponds UNIQUEMENT avec un objet JSON :
{{
"numero": 1,
"titre": "Titre court du moyen (ex: Le DP N17.8 est justifié par la biologie)",
"argument": "Développement avec preuves tagées [XX-N], valeurs bio avec seuils, sources réglementaires",
"argument": "Développement avec preuves tagées (utiliser les tags listés ci-dessus), valeurs bio avec seuils, sources réglementaires",
"preuves": [
{{"ref": "[BIO-1]", "fait": "Créatinine = 280 µmol/L [norme 50-120]", "signification": "IRA confirmée"}}
],