medgraph-engine/api/services/query.py

222 lines
7.4 KiB
Python

"""Query preprocessing: clasificación de intención, expansión de sinónimos y decomposición."""
import re
# Sinónimos médicos comunes (abreviatura → expansiones)
SINONIMOS = {
# Cardiología
"hta": ["hipertension arterial"],
"iam": ["infarto agudo de miocardio", "infarto"],
"icc": ["insuficiencia cardiaca congestiva", "insuficiencia cardiaca"],
"ecg": ["electrocardiograma"],
"fa": ["fibrilacion auricular"],
"tev": ["tromboembolismo venoso"],
"tep": ["tromboembolismo pulmonar", "embolia pulmonar"],
"tvp": ["trombosis venosa profunda"],
# Neumología
"epoc": ["enfermedad pulmonar obstructiva cronica"],
"sdra": ["sindrome de dificultad respiratoria aguda"],
"nac": ["neumonia adquirida en la comunidad"],
"rx": ["radiografia"],
# Endocrinología
"dbt": ["diabetes", "diabetes mellitus"],
"dm": ["diabetes mellitus"],
"dm2": ["diabetes mellitus tipo 2"],
"dm1": ["diabetes mellitus tipo 1"],
"tsh": ["tirotropina", "hormona estimulante de tiroides"],
# Infectología
"hiv": ["virus de inmunodeficiencia humana", "vih", "sida"],
"hbv": ["hepatitis b", "virus hepatitis b"],
"hcv": ["hepatitis c", "virus hepatitis c"],
"tbc": ["tuberculosis"],
"egb": ["estreptococo grupo b"],
"its": ["infeccion de transmision sexual"],
"itu": ["infeccion del tracto urinario", "infeccion urinaria"],
# Nefrología
"irc": ["insuficiencia renal cronica"],
"ira": ["insuficiencia renal aguda"],
"tfg": ["tasa de filtracion glomerular"],
# Gastroenterología
"eii": ["enfermedad inflamatoria intestinal"],
"rge": ["reflujo gastroesofagico"],
# Neurología
"acv": ["accidente cerebrovascular", "stroke"],
"ait": ["accidente isquemico transitorio"],
"lcr": ["liquido cefalorraquideo"],
# Pediatría / Neonatología
"rn": ["recien nacido"],
"rnpt": ["recien nacido pretermino"],
"rnt": ["recien nacido de termino"],
"bpn": ["bajo peso al nacer"],
"apgar": ["apgar"],
"ehrn": ["enfermedad hemorragica del recien nacido"],
# Oftalmología
"av": ["agudeza visual"],
"cv": ["campo visual"],
"pio": ["presion intraocular"],
"dpar": ["defecto pupilar aferente relativo"],
# ORL
"oma": ["otitis media aguda"],
"ome": ["otitis media con efusion", "otitis media secretora"],
"cae": ["conducto auditivo externo"],
# Dermatología
"da": ["dermatitis atopica", "eccema atopico"],
# Farmacología
"aine": ["antiinflamatorio no esteroideo", "antiinflamatorios no esteroideos"],
"atb": ["antibiotico", "antibioticos"],
"vo": ["via oral"],
"im": ["intramuscular"],
"iv": ["intravenoso", "endovenoso"],
"sc": ["subcutaneo"],
"ev": ["endovenoso", "intravenoso"],
# Sinónimos anatómicos
"papila optica": ["disco optico", "cabeza del nervio optico"],
"disco optico": ["papila optica", "cabeza del nervio optico"],
"mt": ["membrana timpanica", "timpano"],
"membrana timpanica": ["timpano"],
}
# Patrones para clasificar intención
INTENCION_PATTERNS = {
"tratamiento": [
r"tratamiento\b", r"terapia\b", r"como se trata",
r"farmaco", r"medicament", r"dosis", r"posologia",
r"primera linea", r"segunda linea", r"se trata con",
],
"diagnostico": [
r"diagnostico\b", r"como se diagnostica", r"metodo dx",
r"criterios", r"laboratorio", r"imagen", r"ecografia",
r"estudios complementarios", r"diferencial",
],
"anatomia": [
r"anatomia\b", r"histologia\b", r"estructura",
r"ubicacion", r"donde se encuentra", r"partes de",
r"capas", r"tunica", r"musculo", r"nervio",
],
"fisiologia": [
r"fisiologia\b", r"mecanismo", r"funcion de",
r"como funciona", r"transduccion", r"via",
],
"clinica": [
r"clinica\b", r"sintomas", r"signos", r"manifestacion",
r"cuadro clinico", r"presenta con", r"cursa con",
],
"etiologia": [
r"etiologia\b", r"causa\b", r"causas\b", r"agente",
r"patogenia", r"fisiopatologia", r"por que se produce",
],
"epidemiologia": [
r"epidemiologia\b", r"prevalencia", r"incidencia",
r"frecuencia", r"factores de riesgo",
],
"procedimiento": [
r"procedimiento\b", r"tecnica\b", r"como se hace",
r"como se realiza", r"pasos", r"lista de cotejo",
],
}
def expandir_sinonimos(query_text: str) -> str:
"""Expande abreviaturas y sinónimos médicos en la query."""
words = query_text.lower().split()
expanded = list(words)
for i, word in enumerate(words):
clean = word.strip(".,;:?!()")
if clean in SINONIMOS:
# Agregar sinónimos al final
for sin in SINONIMOS[clean]:
expanded.append(sin)
# También buscar frases de 2 palabras
text_lower = query_text.lower()
for phrase, sins in SINONIMOS.items():
if " " in phrase and phrase in text_lower:
for sin in sins:
expanded.append(sin)
return " ".join(expanded)
def clasificar_intencion(query_text: str) -> str:
"""Clasifica la intención de la query médica."""
text_lower = query_text.lower()
scores = {}
for intencion, patterns in INTENCION_PATTERNS.items():
score = 0
for pattern in patterns:
if re.search(pattern, text_lower):
score += 1
if score > 0:
scores[intencion] = score
if not scores:
return "general"
return max(scores, key=scores.get)
def descomponer_query(query_text: str, intencion: str) -> list[str]:
"""Descompone una query compleja en sub-queries más específicas."""
# Para queries simples (1-3 palabras), no descomponer
words = query_text.strip().split()
if len(words) <= 3:
return [query_text]
# Para queries de diagnóstico diferencial
if "diferencial" in query_text.lower() or " vs " in query_text.lower():
# Extraer los dos términos
parts = re.split(r"\bvs\b|\bdiferencial\b|\bentre\b", query_text.lower())
parts = [p.strip() for p in parts if p.strip()]
if len(parts) >= 2:
return [f"{parts[0]} definicion clinica", f"{parts[1]} definicion clinica",
f"{parts[0]} {parts[1]} diferencias"]
return [query_text]
# Para queries largas (>6 palabras), mantener original + versión condensada
if len(words) > 6:
# Mantener query original + versión con solo sustantivos médicos (quitar preposiciones)
stop_words = {"de", "del", "la", "el", "los", "las", "un", "una", "y", "o", "en", "con", "por", "para", "que", "como", "se"}
filtered = [w for w in words if w.lower() not in stop_words]
if len(filtered) >= 2:
return [query_text, " ".join(filtered)]
return [query_text]
def preprocess(query_text: str) -> dict:
"""Pipeline completo de preprocesamiento de query.
Returns:
{
"original": "tratamiento de HTA",
"intencion": "tratamiento",
"expandida": "tratamiento de HTA hipertension arterial",
"sub_queries": ["tratamiento de HTA hipertension arterial"],
}
"""
intencion = clasificar_intencion(query_text)
expandida = expandir_sinonimos(query_text)
sub_queries = descomponer_query(expandida, intencion)
return {
"original": query_text,
"intencion": intencion,
"expandida": expandida,
"sub_queries": sub_queries,
}