eduPicto-api / coref_bridge.py
akramRedjdal's picture
Backend EduPicto
11e4edf
Raw
History Blame Contribute Delete
3.52 kB
# -*- coding: utf-8 -*-
"""
Pont propp_fr → server.py
Fournit une résolution de coréférence générique pour remplacer le dict
`memoire_coref` hardcodé du serveur.
propp_fr (CamemBERT) clusterise les mentions (tous les "il" d'un même
personnage ensemble, les noms communs coréférents, etc.). Le serveur, qui
possède l'ontologie, mappe ensuite chaque cluster vers un individu.
Limite connue : le modèle NER propp_fr rate souvent les noms propres
courts dans le registre instructionnel (recette, consignes). Le serveur
complète donc avec une mémoire dynamique "dernier individu Personne vu"
quand un cluster de pronoms n'a aucune tête nominale.
"""
import coref_propp as cp
_MODELS = None
def init(with_lingmess=True, device="cuda:0"):
"""Charge les modèles une seule fois (réutilisé entre requêtes)."""
global _MODELS
if _MODELS is None:
_MODELS = cp.load_all_models(with_lingmess=with_lingmess, device=device)
return _MODELS
def _char_span(row, tokens_df, b2c):
"""Span caractères (start, end) d'une entité via byte offsets de propp_fr."""
st = int(row["start_token"])
et = int(row["end_token"])
b_start = int(tokens_df.loc[
tokens_df["token_ID_within_document"] == st, "byte_onset"
].values[0])
b_end = int(tokens_df.loc[
tokens_df["token_ID_within_document"] == et, "byte_offset"
].values[0])
return b2c.get(b_start, b_start), b2c.get(b_end, b_end)
def resoudre(texte):
"""
Lance propp_fr sur le texte et renvoie deux structures alignées en
offsets caractères (mêmes positions que spaCy côté serveur) :
pronom_to_cluster : { char_start_pronom : cluster_id }
cluster_rep : { cluster_id : { "head_word", "text", "cat" } }
tête nominale représentative du cluster (None si
le cluster ne contient que des pronoms)
Renvoie ({}, {}) si propp_fr échoue ou trouve trop peu de mentions
(le serveur bascule alors sur sa logique de secours).
"""
models = init()
try:
tokens_df, entities_df = cp.run_pipeline(texte, models=models)
except Exception as e:
print(f"[coref_bridge] propp_fr indisponible, fallback serveur : {e}")
return {}, {}
if entities_df is None or len(entities_df) == 0:
return {}, {}
b2c = cp.build_byte_to_char_map(texte)
pronom_to_cluster = {}
# mentions nominales par cluster, gardées par ordre d'apparition
cluster_noms = {}
for _, row in entities_df.iterrows():
coref = row.get("COREF")
if coref is None:
continue
cid = int(coref)
c_start, c_end = _char_span(row, tokens_df, b2c)
prop = str(row.get("prop", ""))
if prop == "PRON":
pronom_to_cluster[c_start] = cid
else:
cluster_noms.setdefault(cid, []).append({
"char_start": c_start,
"head_word": str(row.get("head_word", "")),
"text": str(row.get("text", "")),
"cat": str(row.get("cat", "")),
})
# Représentant = première mention nominale du cluster (ordre du texte)
cluster_rep = {}
for cid, noms in cluster_noms.items():
noms.sort(key=lambda n: n["char_start"])
first = noms[0]
cluster_rep[cid] = {
"head_word": first["head_word"],
"text": first["text"],
"cat": first["cat"],
}
return pronom_to_cluster, cluster_rep