118 lines
7.1 KiB
Python
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
|