File size: 4,726 Bytes
e13590b 12d6af7 e13590b a26b99c ad1f162 a26b99c 2a7a79d a26b99c 2a7a79d a26b99c 2a7a79d a26b99c e13590b ad1f162 a26b99c ad1f162 a26b99c ad1f162 a26b99c e13590b a26b99c ad1f162 a26b99c e13590b ad1f162 e13590b a26b99c e13590b ad1f162 e13590b 12d6af7 e13590b 12d6af7 e13590b 12d6af7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | """Détecte les trous de couverture dans JDM pour un terme donné.
Trois familles de gaps :
- MISSING : aucun triplet (term, relation, ?) pour une relation pourtant
utile pour ce type de terme.
- NEGATIVE_FILLED : que des triplets négatifs (JDM a regardé et dit non).
- LOW_COVERAGE : très peu de triplets positifs (< seuil).
Pas d'appel LLM, déterministe, basé sur les requêtes JDM.
"""
from __future__ import annotations
from typing import Iterable, Optional
from jdm_agent.client import JDMClient
from jdm_agent.enrich.models import Gap, GapType
# Relations qu'on considère utile d'avoir pour la plupart des noms communs.
DEFAULT_TARGET_RELATIONS: tuple[str, ...] = (
"r_has_part", "r_carac", "r_has_color", "r_telic_role",
"r_lieu", "r_make", "r_object>mater",
)
# Relations qu'on considère utile pour les verbes.
VERB_TARGET_RELATIONS: tuple[str, ...] = (
"r_agent", "r_patient", "r_instr", "r_lieu", "r_manner",
"r_has_conseq", "r_has_causatif", "r_but",
)
def _get_relations_signed(client: JDMClient, term: str, relation: str,
limit: int = 50) -> list[tuple[str, float]]:
"""Récupère les triplets avec leur poids (signé), Phase 9b sans seuil.
Renvoie [(target_name, w), ...] trié par |w| décroissant. Inclut tout
ce que JDM renvoie par défaut (négatifs et faibles inclus).
Note : on NE passe PAS `limit` à JDM (l'API tronque AVANT le tri par
poids — on rate alors les triplets les plus pertinents). On récupère
tout, on trie ici, puis on tronque.
"""
rid = client.relation_type_id(relation)
if rid is None:
return []
try:
res = client.relations_from(term, types_ids=[rid])
except Exception:
return []
idx = res.node_index()
out = []
for r in res.relations:
n = idx.get(r.node2)
if n is not None:
out.append((n.name, r.w))
out.sort(key=lambda x: -abs(x[1]))
if limit and limit > 0:
out = out[:limit]
return out
def _detect_missing(client: JDMClient, term: str,
relations: Iterable[str], min_to_consider: int = 1) -> list[Gap]:
"""Pour chaque relation cible, classifie en MISSING / NEGATIVE_FILLED / LOW_COVERAGE.
Phase 9b : aucun seuil de poids hardcodé.
- MISSING strict : aucun triplet (ni positif NI négatif)
- NEGATIVE_FILLED : que des triplets négatifs (JDM a regardé et dit non)
- LOW_COVERAGE : < min_to_consider triplets positifs
"""
gaps: list[Gap] = []
for rel in relations:
signed = _get_relations_signed(client, term, rel)
positives = [w for _, w in signed if w > 0]
negatives = [(n, w) for n, w in signed if w < 0]
n_pos = len(positives)
if n_pos == 0 and len(negatives) > 0:
top_neg = sorted(negatives, key=lambda x: x[1])[:3]
detail_extras = "; ".join(f"{n} (w={w:.0f})" for n, w in top_neg)
gaps.append(Gap(
term=term, relation=rel, gap_type=GapType.NEGATIVE_FILLED,
severity=0.3,
detail=(f"JDM contient {len(negatives)} triplet(s) NÉGATIFS pour "
f"`{term} | {rel} | ?` : {detail_extras}. Pas un gap au "
f"sens strict — déjà rempli avec des assertions négatives."),
))
elif n_pos == 0:
gaps.append(Gap(
term=term, relation=rel, gap_type=GapType.MISSING,
severity=1.0,
detail=f"Aucun triplet positif `{term} | {rel} | ?` dans JDM.",
))
elif n_pos < min_to_consider:
gaps.append(Gap(
term=term, relation=rel, gap_type=GapType.LOW_COVERAGE,
severity=0.6,
detail=f"Seulement {n_pos} triplet(s) positif(s) `{term} | {rel} | ?`.",
))
return gaps
def detect_gaps(
client: JDMClient,
term: str,
target_relations: Optional[Iterable[str]] = None,
min_to_consider: int = 3,
) -> list[Gap]:
"""Point d'entrée principal : trouve les gaps de couverture d'un terme.
Args:
client: JDMClient.
term: le terme à analyser.
target_relations: relations à examiner (défaut: noun-typiques + verb-typiques).
min_to_consider: seuil pour LOW_COVERAGE (< N triplets positifs).
"""
if target_relations is None:
# On essaie les deux jeux ; les non-pertinents se traduiront par MISSING
# qu'on pourra filtrer côté pipeline si besoin.
target_relations = tuple(set(DEFAULT_TARGET_RELATIONS) | set(VERB_TARGET_RELATIONS))
return _detect_missing(client, term, target_relations, min_to_consider)
|