feat: complete production hardening implementation, specialty canonicalization, google-genai migration, voice reliability, sqlite concurrency, and test suite consolidation
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Safety package.
|
||||
"""
|
||||
|
||||
from app.ai.safety.emergency import check_emergency
|
||||
from app.ai.safety.crisis import check_crisis
|
||||
|
||||
__all__ = ["check_emergency", "check_crisis"]
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Mental health crisis and self-harm detection engine.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from app.ai.prompt_rules.safety_rules import CRISIS_KEYWORDS_EN, CRISIS_KEYWORDS_INDIC
|
||||
|
||||
|
||||
TRANSLITERATED_CRISIS_PHRASES = [
|
||||
"saaganam pola irukku", "uyiroda irukka virumbala", "saaga poraen", "valave pidikala",
|
||||
"mar jane ka man", "apne aap ko nuksan", "khudkushee",
|
||||
"chani povali", "sathokobeku",
|
||||
]
|
||||
|
||||
|
||||
def check_crisis(user_message: str, lang: str = "en") -> Tuple[bool, Optional[str], Optional[str]]:
|
||||
"""
|
||||
Evaluates patient message for mental health crisis or self-harm intent.
|
||||
Returns: (is_crisis, reason, crisis_reply)
|
||||
"""
|
||||
if not user_message or not user_message.strip():
|
||||
return False, None, None
|
||||
|
||||
# Check Indic script crisis keywords FIRST against raw message
|
||||
for kw in CRISIS_KEYWORDS_INDIC:
|
||||
if kw in user_message:
|
||||
reply = _build_crisis_reply(lang)
|
||||
return True, f"Matched Indic crisis phrase: '{kw}'", reply
|
||||
|
||||
indic_extra = ["காயப்படுத்திக்கணும்", "கொல்லணும்", "வாழவே பிடிக்கல", "உயிரோட இருக்க", "தற்கொலை", "உயிரை"]
|
||||
for kw in indic_extra:
|
||||
if kw in user_message:
|
||||
reply = _build_crisis_reply(lang)
|
||||
return True, f"Matched Indic crisis phrase: '{kw}'", reply
|
||||
|
||||
msg = user_message.strip().lower()
|
||||
msg_clean = re.sub(r"[^\w\s]", " ", msg)
|
||||
msg_clean = re.sub(r"\s+", " ", msg_clean)
|
||||
|
||||
# Check English crisis keywords
|
||||
for kw in CRISIS_KEYWORDS_EN:
|
||||
kw_clean = re.sub(r"[^\w\s]", " ", kw.lower())
|
||||
if re.search(rf"\b{re.escape(kw_clean)}\b", msg_clean) or kw.lower() in msg:
|
||||
reply = _build_crisis_reply(lang)
|
||||
return True, f"Matched crisis phrase: '{kw}'", reply
|
||||
|
||||
# Check Transliterated crisis phrases
|
||||
for kw in TRANSLITERATED_CRISIS_PHRASES:
|
||||
if kw in msg_clean or kw in msg:
|
||||
reply = _build_crisis_reply(lang)
|
||||
return True, f"Matched transliterated crisis phrase: '{kw}'", reply
|
||||
|
||||
return False, None, None
|
||||
|
||||
|
||||
def _build_crisis_reply(lang: str) -> str:
|
||||
lang_clean = lang.lower() if lang else "en"
|
||||
if lang_clean.startswith("ta"):
|
||||
return "உங்களுக்கு மன உளைச்சல் இருந்தால், தயவுசெய்து டெலி-மானாஸ் (Tele-MANAS) உதவி எண் 14416-ஐ தொடர்பு கொள்ளவும் அல்லது மருத்துவமனை ஊழியர்களிடம் தெரிவிக்கவும்."
|
||||
elif lang_clean.startswith("hi"):
|
||||
return "यदि आप परेशानी में हैं, तो कृपया तुरंत टेली-मानस (Tele-MANAS) 14416 पर संपर्क करें या अस्पताल के कर्मचारियों को सूचित करें।"
|
||||
elif lang_clean.startswith("te"):
|
||||
return "మీరు తీవ్రమైన ఒత్తిడిలో ఉంటే, దయచేసి టెలి-మానస్ (Tele-MANAS) 14416 కు ఫోన్ చేయండి లేదా ఆసుపత్రి సిబ్బందికి చెప్పండి."
|
||||
elif lang_clean.startswith("kn"):
|
||||
return "ನೀವು ಮನೋಒತ್ತಡದಲ್ಲಿದ್ದರೆ, ದಯವಿಟ್ಟು ಟೆಲಿ-ಮಾನಸ್ (Tele-MANAS) 14416 ಗೆ ಕರೆ ಮಾಡಿ ಅಥವಾ ಆಸ್ಪತ್ರೆಯ ಸಿಬ್ಬಂದಿಗೆ ತಿಳಿಸಿ."
|
||||
else:
|
||||
return "If you're in distress, please reach out to Tele-MANAS at 14416 or alert the hospital staff right now."
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Emergency detection engine with strict safety precedence and false-positive protection.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from app.ai.prompt_rules.safety_rules import EMERGENCY_KEYWORDS_EN, EMERGENCY_KEYWORDS_INDIC
|
||||
|
||||
|
||||
TRANSLITERATED_EMERGENCY_PHRASES = [
|
||||
"nenju romba valikuthu", "nenju valla valikuthu", "nenju kadumaya valikuthu",
|
||||
"moochu vida mudiyala", "moochu adaikuthu", "moochu thinaral",
|
||||
"mayangi vilunthuten", "mayakkam varuthu", "paralysis vanthuruchu",
|
||||
"cheest mein bahut dard", "seene mein bahut tej dard", "saans lene mein bahut dikkat",
|
||||
"saans nahi aa rahi", "behosh ho gaya", "behosh ho gayi",
|
||||
"chathi lo thivramaina noppi", "oophiri aadam ledhu", "spruha thappi",
|
||||
"edeyalli theevra novu", "usiraadalu thumba kashta", "pragne thappi",
|
||||
]
|
||||
|
||||
|
||||
def check_emergency(user_message: str, lang: str = "en") -> Tuple[bool, Optional[str], Optional[str]]:
|
||||
"""
|
||||
Evaluates patient message for medical emergency indicators.
|
||||
Returns: (is_emergency, reason, emergency_reply)
|
||||
"""
|
||||
if not user_message or not user_message.strip():
|
||||
return False, None, None
|
||||
|
||||
# Check Indic script emergency keywords against raw user_message FIRST
|
||||
for kw in EMERGENCY_KEYWORDS_INDIC:
|
||||
if kw in user_message:
|
||||
reply = _build_emergency_reply(lang)
|
||||
return True, f"Matched Indic emergency phrase: '{kw}'", reply
|
||||
|
||||
# Additional Tamil/Hindi spoken emergency phrases
|
||||
indic_extra = ["மூச்சு விட முடியல", "மூச்சு திணறல்", "நெஞ்சு வலி", "நெஞ்சு பாரம்", "கடும் நெஞ்சு வலி", "நெஞ்சு அதிகமா வலி"]
|
||||
for kw in indic_extra:
|
||||
if kw in user_message and any(x in user_message for x in ["மூச்சு", "கடும்", "வலி", "அதிகமா"]):
|
||||
reply = _build_emergency_reply(lang)
|
||||
return True, f"Matched Indic emergency phrase: '{kw}'", reply
|
||||
|
||||
msg = user_message.strip().lower()
|
||||
msg_clean = re.sub(r"[^\w\s]", " ", msg)
|
||||
msg_clean = re.sub(r"\s+", " ", msg_clean)
|
||||
|
||||
# False Positive Protection: Negated emergency statements ("no chest pain")
|
||||
negation_patterns = [
|
||||
r"\bno\s+(?:chest\s+pain|breathing\s+difficulty|heart\s+attack|paralysis|bleeding)\b",
|
||||
r"\bdon't\s+have\s+(?:chest\s+pain|breathing\s+difficulty|heart\s+attack)\b",
|
||||
r"\bnot\s+having\s+(?:chest\s+pain|breathing\s+difficulty)\b",
|
||||
r"\bnever\s+had\s+(?:chest\s+pain|breathing\s+difficulty)\b",
|
||||
r"\billness\s+none\b",
|
||||
]
|
||||
for neg_pat in negation_patterns:
|
||||
if re.search(neg_pat, msg_clean):
|
||||
if not any(pos in msg_clean for pos in ["severe chest pain", "cannot breathe", "collapsed"]):
|
||||
return False, None, None
|
||||
|
||||
# Check English emergency keywords
|
||||
for kw in EMERGENCY_KEYWORDS_EN:
|
||||
if re.search(rf"\b{re.escape(kw)}\b", msg_clean):
|
||||
reply = _build_emergency_reply(lang)
|
||||
return True, f"Matched emergency phrase: '{kw}'", reply
|
||||
|
||||
# Check combinations (e.g. "chest pain" AND "difficulty breathing")
|
||||
if "chest pain" in msg_clean and ("breath" in msg_clean or "shortness" in msg_clean or "difficulty" in msg_clean):
|
||||
reply = _build_emergency_reply(lang)
|
||||
return True, "Matched combined chest pain and breathing difficulty", reply
|
||||
|
||||
# Check Transliterated emergency phrases
|
||||
for kw in TRANSLITERATED_EMERGENCY_PHRASES:
|
||||
if kw in msg_clean:
|
||||
reply = _build_emergency_reply(lang)
|
||||
return True, f"Matched transliterated emergency phrase: '{kw}'", reply
|
||||
|
||||
return False, None, None
|
||||
|
||||
|
||||
def _build_emergency_reply(lang: str) -> str:
|
||||
lang_clean = lang.lower() if lang else "en"
|
||||
if lang_clean.startswith("ta"):
|
||||
return "இது அவசர மருத்துவ உதவி தேவைப்படும் நிலை. தயவுசெய்து உடனடியாக அருகில் உள்ள மருத்துவமனை அவசர சிகிச்சைப் பிரிவுக்குச் செல்லவும்."
|
||||
elif lang_clean.startswith("hi"):
|
||||
return "यह एक आपातकालीन स्थिति हो सकती है। कृपया तुरंत निकटतम अस्पताल के आपातकालीन कक्ष (ER) में जाएँ।"
|
||||
elif lang_clean.startswith("te"):
|
||||
return "ఇది అత్యవసర వైద్య పరిస్థితి కావచ్చు. దయచేసి వెంటనే దగ్గరలోని ఆసుపత్రి ఎమర్జెన్సీ విభాగానికి వెళ్ళండి."
|
||||
elif lang_clean.startswith("kn"):
|
||||
return "ಇದು ತುರ್ತು ವೈದ್ಯಕೀಯ ಪರಿಸ್ಥಿತಿಯಾಗಿರಬಹುದು. ದಯವಿಟ್ಟು ತಕ್ಷಣ ಹತ್ತಿರದ ಆಸ್ಪತ್ರೆಯ ತುರ್ತು ವಿಭಾಗಕ್ಕೆ ಹೋಗಿ."
|
||||
else:
|
||||
return "This requires urgent medical attention. Please inform the nearest hospital staff or proceed directly to the Emergency Room right away."
|
||||
Reference in New Issue
Block a user