medical-agent / medical_template3_mapper.py
Nourhenem's picture
Upload folder using huggingface_hub
1eb76aa verified
import re
import logging
from typing import Dict, List, Optional, Tuple, Any
from dataclasses import dataclass
from enum import Enum
logger = logging.getLogger(__name__)
class FieldType(Enum):
"""Types de champs dans le template"""
CHECKBOX = "checkbox" # &x cases à cocher
TEXT = "text" # &x texte libre
MEASUREMENT = "measurement" # &x valeurs numériques
@dataclass
class TemplateField:
"""Définition d'un champ du template"""
placeholder: str # &x dans le template
field_type: FieldType
source_field: str # Champ correspondant dans ExtractedData
default_value: str = ""
validation_pattern: Optional[str] = None
transformation_func: Optional[callable] = None
context_identifier: Optional[str] = None # Pour différencier gauche/droite
@dataclass
class MappingResult:
"""Résultat du mapping"""
filled_template: str
mapped_fields: Dict[str, str]
unmapped_placeholders: List[str]
mapping_confidence: float
errors: List[str]
class MedicalTemplateMapper:
"""Moteur de mapping des données extraites vers le template médical"""
def __init__(self):
self.template = self._load_template()
self.field_mappings = self._define_field_mappings()
self.checkbox_logic = self._define_checkbox_logic()
def _load_template(self) -> str:
"""Template médical de base avec placeholders &x"""
return """BILAN
L'utérus est &x antéversé, &x rétroversé, &x intermédiaire, &x rétrofléchi, &x antéfléchi, &x fixe de taille normale (&x x &x x &x cm).
Hystérométrie : distance orifice externe du col - fond de la cavité utérine : &x mm.
L'endomètre : mesuré à &x mm.
Myometre : pas de myome.
Zone jonctionnelle : Atteinte de la zone de jonction : &x non &x oui
Adénomyose associée : &x non &x oui : &x diffuse &x focale &x interne &x externe
Col utérin: pas de kyste de Naboth. Absence de pathologies échographiquement décelable à son niveau.
Cavité utérine en 3D: morphologie triangulaire.
&xKISSING OVARIES
L'ovaire droit mesure &x x &x mm, &x est de dimensions supérieures à la normale il mesure &x x &x mm, &xfolliculaire CFA &x follicules: (&x mm). &x Absence d'endométriome. &x Présence d'une formation kystique hypoéchogène, uniloculaire, non vascularisé, à contenu ground glass mesurée à &x mm d'allure endométriome.
Accessibilité : &x rétro-utérin &x fixe &x aisée.
L'ovaire gauche mesure &x x &x mm, &x est de dimensions supérieures à la normale il mesure &x x &x mm, &x folliculaire CFA &x follicules: (&x mm). &x Absence d'endométriome. &x Présence d'une formation kystique hypoéchogène, uniloculaire, non vascularisé, à contenu ground glass mesurée à &x mm d'allure endométriome.
Accessibilité : &x rétro-utérin &x fixe &x aisée.
&x Présence de micro-calcifications sous thécales &x bilatérales &x droites &x gauches pouvant témoigner d'implants endométriosiques superficiels.
L'échostructure des deux ovaires apparait normale, avec une vascularisation artério-veineuse normale au Doppler, sans formation ou image kystique pathologique échographiquement décelable à leur niveau.
Cavité péritonéale
&x- Pas d'épanchement liquidien dans le cul du sac du Douglas. Pas de douleur à l'écho-palpation.
&x- Faible épanchement corpusculé dans le cul du sac du Douglas qui silhouette des adhérences (soft marqueur d'endométriose?). Pas de douleur à l'écho-palpation.
- &xVessie vide pendant l'examen. &x Vessie en semi-réplétion pendant l'examen.
- &x Absence de dilatation pyélo-calicielle.
- Artère utérine : IP : &x - IR : 0,&x - Spectre : type 2 avec notch protodiastolique.
- Pas d'image d'hydrosalpinx visible à ce jour.
RECHERCHE ENDOMETRIOSE PELVIENNE
A-Compartiment antérieur (vessie en semi-réplétion)
- Signe du glissement (sliding) : &xprésent &xdiminué &xabsent
- Présence d'un nodule : &xnon &xoui
- Uretères dans la partie pelvienne vus non dilatés.
B-Compartiment postérieur
- Signe du glissement (sliding) :
- Espace recto-vaginal : &xprésent &xdiminué &xabsent
- Plan sus-péritonéal : &xprésent &xdiminué &xabsent
- Aspect du torus : &x normal &x épaissi
- Aspect des ligaments utéro-sacrés :
- Ligament utéro- sacré droit : &x normal &x épaissi
- Ligament utéro-sacré gauche : &x normal &x épaissi
- Présence d'un nodule hypoéchogène : &x non
- Infiltration digestive: &x non &x oui : &x bas rectum &x moyen rectum &x haut rectum &x jonction recto-sigmoïde
Conclusions
Utérus de taille et de morphologie normales.
Endomètre mesuré à &x mm.
CFA : &x+&x follicules.
Ovaires sans formation ou image kystique pathologique échographiquement décelable à leur niveau.
&x Absence d'image d'endométriose visible ce jour, à confronter éventuellement à une IRM.
&x Endométriose &x superficielle &x et profonde.
Absence d'anomalie échographiquement décelable au niveau des trompes.
--> L'ensemble de ces aspects reste à confronter au contexte clinico-thérapeutique.
"""
def _define_field_mappings(self) -> Dict[str, TemplateField]:
"""Définit les mappings entre données extraites et placeholders template"""
return {
# Position utérus - checkboxes
"uterus_position_antéversé": TemplateField(
placeholder="&x antéversé",
field_type=FieldType.CHECKBOX,
source_field="uterus_position",
transformation_func=lambda x: "X" if x and "antéversé" in x.lower() else ""
),
"uterus_position_rétroversé": TemplateField(
placeholder="&x rétroversé",
field_type=FieldType.CHECKBOX,
source_field="uterus_position",
transformation_func=lambda x: "X" if x and "rétroversé" in x.lower() else ""
),
"uterus_position_intermédiaire": TemplateField(
placeholder="&x intermédiaire",
field_type=FieldType.CHECKBOX,
source_field="uterus_position",
transformation_func=lambda x: "X" if x and "intermédiaire" in x.lower() else ""
),
"uterus_position_rétrofléchi": TemplateField(
placeholder="&x rétrofléchi",
field_type=FieldType.CHECKBOX,
source_field="uterus_position",
transformation_func=lambda x: "X" if x and "rétrofléchi" in x.lower() else ""
),
"uterus_position_antéfléchi": TemplateField(
placeholder="&x antéfléchi",
field_type=FieldType.CHECKBOX,
source_field="uterus_position",
transformation_func=lambda x: "X" if x and "antéfléchi" in x.lower() else ""
),
"uterus_position_fixe": TemplateField(
placeholder="&x fixe",
field_type=FieldType.CHECKBOX,
source_field="uterus_position",
transformation_func=lambda x: "X" if x and "fixe" in x.lower() else ""
),
# Taille utérus - dimensions (corrected)
"uterus_size_length": TemplateField(
placeholder="normale (&x x",
field_type=FieldType.MEASUREMENT,
source_field="uterus_size",
transformation_func=self._extract_first_dimension
),
"uterus_size_width": TemplateField(
placeholder="x &x x",
field_type=FieldType.MEASUREMENT,
source_field="uterus_size",
transformation_func=self._extract_second_dimension
),
"uterus_size_height": TemplateField(
placeholder="x &x cm)",
field_type=FieldType.MEASUREMENT,
source_field="uterus_size",
transformation_func=self._extract_third_dimension
),
# Hystérométrie
"hysterometry_value": TemplateField(
placeholder="fond de la cavité utérine : &x mm",
field_type=FieldType.MEASUREMENT,
source_field="hysterometry",
transformation_func=self._clean_numeric_value
),
# Endomètre
"endometrium_thickness": TemplateField(
placeholder="L'endomètre : mesuré à &x mm",
field_type=FieldType.MEASUREMENT,
source_field="endometrium_thickness",
transformation_func=self._clean_numeric_value
),
# Zone jonctionnelle
"junctional_zone_non": TemplateField(
placeholder="Atteinte de la zone de jonction : &x non",
field_type=FieldType.CHECKBOX,
source_field="junctional_zone_status",
transformation_func=lambda x: "X" if not x or x.lower() in ["normale", "normal"] else ""
),
"junctional_zone_oui": TemplateField(
placeholder="&x oui",
field_type=FieldType.CHECKBOX,
source_field="junctional_zone_status",
transformation_func=lambda x: "X" if x and x.lower() in ["épaissie", "épaisse", "atteinte"] else ""
),
# Adénomyose - checkboxes
"adenomyosis_non": TemplateField(
placeholder="Adénomyose associée : &x non",
field_type=FieldType.CHECKBOX,
source_field="adenomyosis_type",
transformation_func=lambda x: "X" if not x or x.lower() in ["absente", "non"] else ""
),
"adenomyosis_oui": TemplateField(
placeholder="&x oui :",
field_type=FieldType.CHECKBOX,
source_field="adenomyosis_type",
transformation_func=lambda x: "X" if x and x.lower() in ["diffuse", "focale"] else ""
),
"adenomyosis_diffuse": TemplateField(
placeholder="&x diffuse",
field_type=FieldType.CHECKBOX,
source_field="adenomyosis_type",
transformation_func=lambda x: "X" if x and "diffuse" in x.lower() else ""
),
"adenomyosis_focale": TemplateField(
placeholder="&x focale",
field_type=FieldType.CHECKBOX,
source_field="adenomyosis_type",
transformation_func=lambda x: "X" if x and "focale" in x.lower() else ""
),
# Ovaire droit - dimensions (corrected with context)
"right_ovary_length": TemplateField(
placeholder="L'ovaire droit mesure &x",
field_type=FieldType.MEASUREMENT,
source_field="right_ovary_dimensions",
context_identifier="ovaire droit",
transformation_func=self._extract_first_dimension
),
"right_ovary_width_first": TemplateField(
placeholder="x &x mm,",
field_type=FieldType.MEASUREMENT,
source_field="right_ovary_dimensions",
context_identifier="ovaire droit mesure",
transformation_func=self._extract_second_dimension
),
# Ovaire droit - CFA
"right_ovary_cfa": TemplateField(
placeholder="folliculaire CFA &x follicules:",
field_type=FieldType.MEASUREMENT,
source_field="right_ovary_cfa",
context_identifier="ovaire droit",
transformation_func=self._clean_cfa_value
),
# Ovaire droit - accessibilité
"right_ovary_access_retro": TemplateField(
placeholder="Accessibilité : &x rétro-utérin",
field_type=FieldType.CHECKBOX,
source_field="right_ovary_accessibility",
context_identifier="ovaire droit",
transformation_func=lambda x: "X" if x and "rétro" in x.lower() else ""
),
"right_ovary_access_fixe": TemplateField(
placeholder="rétro-utérin &x fixe",
field_type=FieldType.CHECKBOX,
source_field="right_ovary_accessibility",
context_identifier="ovaire droit",
transformation_func=lambda x: "X" if x and "fixe" in x.lower() else ""
),
"right_ovary_access_aisee": TemplateField(
placeholder="fixe &x aisée",
field_type=FieldType.CHECKBOX,
source_field="right_ovary_accessibility",
context_identifier="ovaire droit",
transformation_func=lambda x: "X" if x and ("aisée" in x.lower() or "normale" in x.lower()) else ""
),
# Ovaire gauche - dimensions (corrected with context)
"left_ovary_length": TemplateField(
placeholder="L'ovaire gauche mesure &x x",
field_type=FieldType.MEASUREMENT,
source_field="left_ovary_dimensions",
context_identifier="ovaire gauche",
transformation_func=self._extract_first_dimension
),
"left_ovary_width_first": TemplateField(
placeholder="&x mm,",
field_type=FieldType.MEASUREMENT,
source_field="left_ovary_dimensions",
context_identifier="ovaire gauche mesure",
transformation_func=self._extract_second_dimension
),
# Ovaire gauche - CFA
"left_ovary_cfa": TemplateField(
placeholder="folliculaire CFA &x follicules:",
field_type=FieldType.MEASUREMENT,
source_field="left_ovary_cfa",
context_identifier="ovaire gauche",
transformation_func=self._clean_cfa_value
),
# Ovaire gauche - accessibilité
"left_ovary_access_retro": TemplateField(
placeholder="Accessibilité : &x rétro-utérin",
field_type=FieldType.CHECKBOX,
source_field="left_ovary_accessibility",
context_identifier="ovaire gauche",
transformation_func=lambda x: "X" if x and "rétro" in x.lower() else ""
),
"left_ovary_access_fixe": TemplateField(
placeholder="rétro-utérin &x fixe",
field_type=FieldType.CHECKBOX,
source_field="left_ovary_accessibility",
context_identifier="ovaire gauche",
transformation_func=lambda x: "X" if x and "fixe" in x.lower() else ""
),
"left_ovary_access_aisee": TemplateField(
placeholder="fixe &x aisée",
field_type=FieldType.CHECKBOX,
source_field="left_ovary_accessibility",
context_identifier="ovaire gauche",
transformation_func=lambda x: "X" if x and ("aisée" in x.lower() or "normale" in x.lower()) else ""
),
# Doppler
"doppler_ip": TemplateField(
placeholder="IP : &x",
field_type=FieldType.MEASUREMENT,
source_field="doppler_ip",
transformation_func=self._clean_numeric_value
),
"doppler_ir": TemplateField(
placeholder="IR : 0,&x",
field_type=FieldType.MEASUREMENT,
source_field="doppler_ir",
transformation_func=self._format_doppler_ir
),
# Conclusions - CFA total
"conclusion_cfa_right": TemplateField(
placeholder="CFA : &x+",
field_type=FieldType.MEASUREMENT,
source_field="right_ovary_cfa",
transformation_func=self._clean_cfa_value
),
"conclusion_cfa_left": TemplateField(
placeholder="+&x follicules",
field_type=FieldType.MEASUREMENT,
source_field="left_ovary_cfa",
transformation_func=self._clean_cfa_value
),
# Conclusion - endomètre
"conclusion_endometrium": TemplateField(
placeholder="Endomètre mesuré à &x mm",
field_type=FieldType.MEASUREMENT,
source_field="endometrium_thickness",
transformation_func=self._clean_numeric_value
),
}
def _define_checkbox_logic(self) -> Dict[str, List[str]]:
"""Définit la logique des checkboxes mutuellement exclusives"""
return {
"uterus_position": ["antéversé", "rétroversé", "intermédiaire", "rétrofléchi", "antéfléchi"],
"adenomyosis": ["non", "oui"],
"adenomyosis_type": ["diffuse", "focale", "interne", "externe"],
"ovary_accessibility": ["rétro-utérin", "fixe", "aisée"]
}
def map_extracted_data_to_template(self, extracted_data) -> MappingResult:
"""
Fonction principale de mapping des données extraites vers le template
"""
logger.info("🔄 Début du mapping vers le template médical")
filled_template = self.template
mapped_fields = {}
unmapped_placeholders = []
errors = []
# Étape 1: Identifier tous les placeholders &x dans le template
all_placeholders = self._find_all_placeholders(filled_template)
logger.info(f"📍 {len(all_placeholders)} placeholders trouvés dans le template")
# Étape 2: Appliquer les mappings définis avec gestion du contexte
for mapping_key, template_field in self.field_mappings.items():
try:
# Récupérer la valeur source
source_value = getattr(extracted_data, template_field.source_field, None)
if source_value:
# Appliquer la transformation
if template_field.transformation_func:
mapped_value = template_field.transformation_func(source_value)
else:
mapped_value = str(source_value)
# Remplacer dans le template avec gestion du contexte
if mapped_value and mapped_value.strip():
filled_template = self._replace_placeholder_with_context(
filled_template, template_field.placeholder, mapped_value, template_field.context_identifier
)
mapped_fields[mapping_key] = mapped_value
logger.debug(f"✅ {mapping_key}: {mapped_value}")
else:
logger.debug(f"⚠️ {mapping_key}: Valeur vide après transformation")
except Exception as e:
error_msg = f"Erreur mapping {mapping_key}: {e}"
errors.append(error_msg)
logger.error(error_msg)
# Étape 3: Gestion des placeholders non mappés
remaining_placeholders = self._find_all_placeholders(filled_template)
unmapped_placeholders = [p for p in remaining_placeholders if "&x" in p]
# Étape 4: Application des règles de logique métier
filled_template = self._apply_business_logic(filled_template, extracted_data)
# Étape 5: Calcul du score de mapping
mapping_confidence = self._calculate_mapping_confidence(
len(mapped_fields), len(all_placeholders), len(errors)
)
logger.info(f"✅ Mapping terminé - {len(mapped_fields)} champs mappés, {len(unmapped_placeholders)} non mappés")
return MappingResult(
filled_template=filled_template,
mapped_fields=mapped_fields,
unmapped_placeholders=unmapped_placeholders,
mapping_confidence=mapping_confidence,
errors=errors
)
def _find_all_placeholders(self, template: str) -> List[str]:
"""Trouve tous les placeholders &x dans le template"""
# Pattern pour capturer le contexte autour de &x
pattern = r'[^.]*&x[^.]*'
matches = re.findall(pattern, template)
return matches
def _replace_placeholder_with_context(self, template: str, context_pattern: str, value: str, context_identifier: str = None) -> str:
"""Remplace &x dans un contexte spécifique avec gestion du contexte gauche/droit"""
if context_identifier:
# Trouver la section correspondante (ovaire droit/gauche)
lines = template.split('\n')
in_context = False
context_found = False
for i, line in enumerate(lines):
if context_identifier.lower() in line.lower():
in_context = True
context_found = True
elif context_found and (("ovaire" in line.lower() and context_identifier not in line.lower()) or
line.strip() == "" or
"Accessibilité" in line and i > 0 and context_identifier not in lines[i-1].lower()):
in_context = False
if in_context and context_pattern in line:
# Échapper les caractères spéciaux pour regex
escaped_pattern = re.escape(context_pattern).replace(r'\&x', r'&x')
lines[i] = re.sub(escaped_pattern, context_pattern.replace('&x', value), line, count=1)
break
return '\n'.join(lines)
else:
return self._replace_placeholder_in_context(template, context_pattern, value)
def _replace_placeholder_in_context(self, template: str, context_pattern: str, value: str) -> str:
"""Remplace &x dans un contexte spécifique pour éviter les remplacements incorrects"""
# Échapper les caractères spéciaux pour regex
escaped_pattern = re.escape(context_pattern).replace(r'\&x', r'&x')
# Remplacer &x uniquement dans ce contexte
def replace_func(match):
return match.group(0).replace('&x', value, 1) # Remplacer seulement le premier &x
return re.sub(escaped_pattern, replace_func, template)
def _apply_business_logic(self, template: str, extracted_data) -> str:
"""Applique la logique métier spécifique au domaine médical"""
# Logique 1: Si pas d'adénomyose détectée, cocher "non"
if not extracted_data.adenomyosis_type or extracted_data.adenomyosis_type.lower() == "absente":
template = template.replace("Adénomyose associée : &x non", "Adénomyose associée : X non")
# Logique 2: Gestion de l'accessibilité par défaut pour ovaire droit
if not getattr(extracted_data, 'right_ovary_accessibility', None) or getattr(extracted_data, 'right_ovary_accessibility', '').lower() == "normale":
# Chercher la section ovaire droit et marquer aisée
lines = template.split('\n')
for i, line in enumerate(lines):
if "ovaire droit" in line.lower() and i < len(lines) - 1:
# Chercher la ligne accessibilité suivante
for j in range(i+1, min(i+5, len(lines))):
if "Accessibilité" in lines[j] and "ovaire droit" in lines[i].lower():
lines[j] = lines[j].replace("&x aisée", "X aisée")
break
break
template = '\n'.join(lines)
# Logique 3: Gestion de l'accessibilité pour ovaire gauche
if getattr(extracted_data, 'left_ovary_accessibility', None) and "rétro" in getattr(extracted_data, 'left_ovary_accessibility', '').lower():
lines = template.split('\n')
for i, line in enumerate(lines):
if "ovaire gauche" in line.lower() and i < len(lines) - 1:
# Chercher la ligne accessibilité suivante
for j in range(i+1, min(i+5, len(lines))):
if "Accessibilité" in lines[j] and "gauche" in lines[i].lower():
lines[j] = lines[j].replace("Accessibilité : &x rétro-utérin", "Accessibilité : X rétro-utérin")
break
break
template = '\n'.join(lines)
# Logique 4: Valeurs par défaut pour les examens standard
template = template.replace("- &xVessie vide pendant l'examen", "- XVessie vide pendant l'examen")
template = template.replace("&x Absence de dilatation pyélo-calicielle", "X Absence de dilatation pyélo-calicielle")
# Logique 5: Conclusions par défaut
template = template.replace("&x Absence d'image d'endométriose visible ce jour", "X Absence d'image d'endométriose visible ce jour")
return template
def _calculate_mapping_confidence(self, mapped_count: int, total_placeholders: int, error_count: int) -> float:
"""Calcule le score de confiance du mapping"""
if total_placeholders == 0:
return 1.0
base_confidence = mapped_count / total_placeholders
error_penalty = min(error_count * 0.1, 0.3) # Maximum 30% de pénalité
return max(0.0, base_confidence - error_penalty)
# Fonctions de transformation des données
def _clean_numeric_value(self, value: str) -> str:
"""Nettoie les valeurs numériques"""
if not value:
return ""
# Supprimer les unités redondantes comme "mm mm"
cleaned = re.sub(r'\s*(mm|cm)\s*(mm|cm)', r' \1', str(value))
cleaned = re.sub(r'\s*(mm|cm).*', r'', cleaned) # Supprimer unités en fin
cleaned = cleaned.replace(',', '.').strip()
return cleaned
def _clean_cfa_value(self, value: str) -> str:
"""Nettoie les valeurs CFA en supprimant les doublons"""
if not value:
return ""
cleaned = str(value).replace(' follicules', '').replace(' follicules follicules', '').strip()
# Extraire seulement le nombre
match = re.search(r'(\d+)', cleaned)
return match.group(1) if match else cleaned
def _extract_first_dimension(self, dimensions: str) -> str:
"""Extrait la première dimension (longueur)"""
if not dimensions:
return ""
match = re.search(r'(\d+(?:[.,]\d+)?)', dimensions)
return match.group(1).replace(',', '.') if match else ""
def _extract_second_dimension(self, dimensions: str) -> str:
"""Extrait la deuxième dimension (largeur)"""
if not dimensions:
return ""
matches = re.findall(r'(\d+(?:[.,]\d+)?)', dimensions)
return matches[1].replace(',', '.') if len(matches) > 1 else ""
def _extract_third_dimension(self, dimensions: str) -> str:
"""Extrait la troisième dimension (hauteur)"""
if not dimensions:
return ""
matches = re.findall(r'(\d+(?:[.,]\d+)?)', dimensions)
return matches[2].replace(',', '.') if len(matches) > 2 else ""
def _format_doppler_ir(self, ir_value: str) -> str:
"""Formate la valeur IR pour le template (0,XX)"""
if not ir_value:
return ""
cleaned = self._clean_numeric_value(ir_value)
# Si la valeur commence par 0. enlever le 0.
if cleaned.startswith('0.'):
return cleaned[2:]
elif '.' in cleaned:
return cleaned.split('.')[1]
return cleaned
def print_mapping_report(self, result: MappingResult) -> str:
"""Génère un rapport de mapping formaté"""
report = "🔄 RAPPORT DE MAPPING TEMPLATE\n"
report += "=" * 50 + "\n\n"
# Statistiques générales
report += f"📊 STATISTIQUES:\n"
report += f" Champs mappés: {len(result.mapped_fields)}\n"
report += f" Placeholders non mappés: {len(result.unmapped_placeholders)}\n"
report += f" Score de confiance: {result.mapping_confidence:.1%}\n"
report += f" Erreurs: {len(result.errors)}\n\n"
# Détail des mappings
if result.mapped_fields:
report += "✅ CHAMPS MAPPÉS:\n"
for field, value in result.mapped_fields.items():
report += f" {field}: {value}\n"
report += "\n"
# Placeholders non mappés
if result.unmapped_placeholders:
report += "❌ PLACEHOLDERS NON MAPPÉS:\n"
for placeholder in result.unmapped_placeholders[:10]: # Limiter l'affichage
report += f" {placeholder[:50]}...\n"
if len(result.unmapped_placeholders) > 10:
report += f" ... et {len(result.unmapped_placeholders) - 10} autres\n"
report += "\n"
# Erreurs
if result.errors:
report += "⚠️ ERREURS:\n"
for error in result.errors:
report += f" {error}\n"
return report
# Fonction utilitaire pour utilisation
def create_filled_medical_report(extracted_data) -> str:
"""
Fonction principale pour créer un rapport médical complet
à partir des données extraites
"""
mapper = MedicalTemplateMapper()
result = mapper.map_extracted_data_to_template(extracted_data)
# Log du rapport
print(mapper.print_mapping_report(result))
return result.filled_template
# Exemple d'utilisation avec correction des problèmes identifiés
class ExtractedData:
"""Classe exemple pour les données extraites"""
def __init__(self):
# Données exemple basées sur votre extraction
self.uterus_position = "antéversé"
self.uterus_size = "7,8 cm"
self.hysterometry = "60 mm"
self.endometrium_thickness = "3,7 mm"
self.junctional_zone_status = "épaissie"
self.adenomyosis_type = "diffuse"
# Données ovaires corrigées
self.right_ovary_dimensions = "26 x 20 mm"
self.right_ovary_cfa = "22 follicules"
self.right_ovary_accessibility = "normale"
self.left_ovary_dimensions = "25 x 19 mm" # Correction: 19 au lieu de 20
self.left_ovary_cfa = "22 follicules"
self.left_ovary_accessibility = "rétro-utérine"
# Données Doppler
self.doppler_ip = "3,24"
self.doppler_ir = "0,91"
def test_corrected_mapping():
"""Test de la correction du mapping"""
# Créer des données test
data = ExtractedData()
# Utiliser le mapper corrigé
mapper = MedicalTemplateMapper()
result = mapper.map_extracted_data_to_template(data)
print("🔧 TEST DU MAPPING CORRIGÉ")
print("=" * 40)
print(mapper.print_mapping_report(result))
# Vérifications spécifiques pour les ovaires
print("\n🔍 VÉRIFICATIONS SPÉCIFIQUES:")
print("-" * 30)
# Vérifier ovaire droit
if "L'ovaire droit mesure 26 x 20 mm" in result.filled_template:
print("✅ Ovaire droit: dimensions correctes")
else:
print("❌ Ovaire droit: problème dimensions")
# Vérifier ovaire gauche
if "L'ovaire gauche mesure 25 x 19 mm" in result.filled_template:
print("✅ Ovaire gauche: dimensions correctes")
else:
print("❌ Ovaire gauche: problème dimensions")
# Vérifier CFA dans conclusions
if "CFA : 22+22 follicules" in result.filled_template:
print("✅ CFA conclusion: format correct")
else:
print("❌ CFA conclusion: problème format")
# Vérifier accessibilité
if "Accessibilité : X rétro-utérin" in result.filled_template and "ovaire gauche" in result.filled_template:
print("✅ Accessibilité gauche: rétro-utérine correcte")
else:
print("❌ Accessibilité gauche: problème")
return result.filled_template
# Exécuter le test si le script est lancé directement
if __name__ == "__main__":
filled_report = test_corrected_mapping()
print("\n" + "="*50)
print("RAPPORT FINAL CORRIGÉ:")
print("="*50)
print(filled_report)