Files
aionboarding/ai-patient-onboarding/app/ai/extraction/symptom_extractor.py
T

118 lines
7.1 KiB
Python

"""
Layered symptom extraction engine supporting attribute mapping per symptom.
"""
import re
from typing import Dict, Any, List, Optional, Set, Tuple
from app.services.contextual_normalizer import (
normalize_duration_from_message,
extract_severity_from_message,
extract_location_from_message,
is_duration_expression,
is_severity_expression,
is_location_expression,
is_casual_filler,
is_negative_expression,
)
SYMPTOM_DICTIONARY = [
# General & Respiratory
("fever", "fever"), ("feverish", "fever"), ("காய்ச்சல்", "fever"), ("बुखार", "fever"), ("జ్వరం", "fever"), ("ಜ್ವರ", "fever"),
("cough", "cough"), ("coughing", "cough"), ("இருமல்", "cough"), ("खांसी", "cough"), ("దగ్గు", "cough"), ("ಕೆಮ್ಮು", "cough"),
("cold", "cold"), ("sneezing", "cold"), ("சளி", "cold"), ("सर्दी", "cold"), ("జలుబు", "cold"), ("ನೆಗಡಿ", "cold"),
("breathlessness", "breathlessness"), ("shortness of breath", "breathlessness"), ("difficulty breathing", "breathlessness"),
("மூச்சு திணறல்", "breathlessness"), ("மூச்சு வாங்குது", "breathlessness"), ("மூச்சு வாங்குறேன்", "breathlessness"), ("சாப்பிடும்போது மூச்சு", "breathlessness"), ("சாப்பிடும் போது மூச்சு", "breathlessness"), ("सांस फूलना", "breathlessness"),
("palpitations", "palpitations"), ("படபடன்னு", "palpitations"), ("நெஞ்சு படபடன்னு", "palpitations"), ("படபடப்பு", "palpitations"),
# Pain & Head
("headache", "headache"), ("head ache", "headache"), ("head pain", "headache"), ("migraine", "headache"),
("தலைவலி", "headache"), ("தலை பாரம்", "headache"), ("सिरदर्द", "headache"), ("తలనొప్పి", "headache"), ("ತಲೆನೋವು", "headache"),
("chest pain", "chest pain"), ("chest pressure", "chest pain"), ("நெஞ்சு வலி", "chest pain"), ("छाती में दर्द", "chest pain"),
("stomach pain", "stomach pain"), ("abdominal pain", "stomach pain"), ("stomach ache", "stomach pain"),
("வயிற்று வலி", "stomach pain"), ("पेट दर्द", "stomach pain"), ("కడుపు నొప్పి", "stomach pain"), ("<ctrl42> ಹೊಟ್ಟೆ ನೋವು", "stomach pain"),
("back pain", "back pain"), ("முதுகு வலி", "back pain"), ("पीठ दर्द", "back pain"),
("joint pain", "joint pain"), ("knee pain", "knee pain"), ("மூட்டு வலி", "joint pain"), ("घुटने का दर्द", "knee pain"),
# Neurological / ENT
("dizziness", "dizziness"), ("giddiness", "dizziness"), ("dizzy", "dizziness"),
("தலை சுத்துது", "dizziness"), ("தலைசுற்றல்", "dizziness"), ("மயக்கம்", "dizziness"), ("चक्कर", "dizziness"),
("sore throat", "sore throat"), ("throat pain", "sore throat"), ("தொண்டை வலி", "sore throat"), ("गले में खराश", "sore throat"),
# GI
("vomiting", "vomiting"), ("vomit", "vomiting"), ("vomited", "vomiting"), ("nausea", "vomiting"),
("வாந்தி", "vomiting"), ("உल्टी", "vomiting"), ("వాంతులు", "vomiting"), ("ವಾಂತಿ", "vomiting"),
("loose motion", "diarrhea"), ("diarrhea", "diarrhea"), ("loose stools", "diarrhea"),
("வயிற்றுப்போக்கு", "diarrhea"), ("दस्त", "diarrhea"),
# Dermatological
("itching", "itching"), ("rash", "rash"), ("skin rash", "rash"),
("அரிப்பு", "itching"), ("தழும்பு", "rash"), ("खुजली", "itching"),
]
def extract_symptoms(
message: str,
normalized_msg: str,
current_state: Optional[Dict[str, Any]] = None
) -> Tuple[List[Dict[str, Any]], Optional[str]]:
"""
Extracts symptoms and maps attributes (duration, severity, location, frequency) specifically.
Returns: (symptoms_list, primary_reason_for_visit)
"""
msg_lower = normalized_msg.lower()
found_symptoms: List[Dict[str, Any]] = []
found_names: Set[str] = set()
# Extract global context attributes
duration = normalize_duration_from_message(message)
severity = extract_severity_from_message(message)
location = extract_location_from_message(message)
# Frequency extraction (e.g. "twice", "2 times")
freq_match = re.search(r"(\b\d+\s*times\b|\btwice\b|\bonce\b|\bmultiple times\b)", msg_lower)
frequency = freq_match.group(1) if freq_match else None
# Scan for symptoms
for kw, canonical in SYMPTOM_DICTIONARY:
pattern = rf"\b{re.escape(kw)}\b"
if re.search(pattern, msg_lower) or kw in message:
if canonical not in found_names:
found_names.add(canonical)
# Store canonical name as 'name' if kw is an English inflection like 'vomit', else keep kw if non-English
stored_name = canonical if kw in ["vomit", "vomited", "coughing", "feverish"] else kw
sym_obj: Dict[str, Any] = {"name": stored_name}
if duration:
sym_obj["duration"] = duration
if severity:
sym_obj["severity"] = severity
if location:
sym_obj["location"] = location
if frequency and ("vomit" in canonical or "வாந்தி" in kw or "உल्टी" in kw):
sym_obj["frequency"] = frequency
found_symptoms.append(sym_obj)
primary_rfv = list(found_names)[0] if found_names else None
# Check for medication or non-symptom statements to prevent fake symptom fallback
is_med_statement = any(k in msg_lower or k in message for k in ["metformin", "dolo", "paracetamol", "pantocid", "telmisartan", "amlodipine", "tablets", "medicine", "pill", "pills", "goli", "mathirai", "மாத்திரை", "மருந்து", "சாப்பிடுறேன்", "சாப்பிட்டேன்", "வாங்குறேன்", "दवाई", "दवा"])
# Fallback: single phrase short symptom message
if not found_symptoms and message and len(message.split()) < 10 and not is_med_statement:
if not (
is_duration_expression(message)
or is_severity_expression(message)
or is_location_expression(message)
or is_casual_filler(message)
or is_negative_expression(message)
):
clean_symptom = message.strip()
sym_obj = {"name": clean_symptom}
if duration:
sym_obj["duration"] = duration
if severity:
sym_obj["severity"] = severity
if location:
sym_obj["location"] = location
found_symptoms.append(sym_obj)
primary_rfv = clean_symptom
return found_symptoms, primary_rfv