Spaces:
Sleeping
Sleeping
| # -*- 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 | |