BrutalCaesar's picture
Upload webhook/parser.py with huggingface_hub
ec64345 verified
Raw
History Blame Contribute Delete
4.45 kB
"""
BP text parser — extracts systolic/diastolic/pulse from free-form WhatsApp text.
Handles Hindi time words (subah, dopahar, shaam, raat) and English equivalents.
Real attendant formats observed in DPG - CARE:
"Blad pressure (144), 67 pulse ( 65)" parentheses, comma
"Blad pressure 136=55 pulse 58" equals separator
"Blad pressure 142 69. Pulse,,54" space separator, comma after pulse keyword
"Blad pressure 164,,,,75 pulse 60" multiple commas
"Blad pressure 155,,70 pulse59" no space before pulse digit
"Blad pressure 149,65, pulse,65" comma after pulse keyword
"""
import re
from datetime import datetime
def parse_bp_from_text(text: str) -> dict | None:
"""
Extracts BP reading from free-form WhatsApp text.
Returns dict with systolic/diastolic/pulse/time_of_day/raw_text, or None.
"""
text = text.strip()
if not text:
return None
# Pre-process: strip parentheses wrapping numbers — "(144)" or "( 65)" → "144" or "65"
text_clean = re.sub(r'\(\s*(\d{1,3})\s*\)', r'\1', text)
# Primary pattern: separators /, -, any number of commas, or =
bp_match = re.search(r'(\d{2,3})\s*[/\-,=]+\s*(\d{2,3})', text_clean)
# Space-separator fallback — "Blad pressure 142 69" or "BP 138 88"
# Only fires when a BP keyword precedes the two numbers
if not bp_match:
kw_match = re.search(
r'(?:blad|blood)\s*pressure|b\.?p\.?',
text_clean, re.IGNORECASE
)
if kw_match:
after_kw = text_clean[kw_match.end():]
space_match = re.search(r'(\d{2,3})\s+(\d{2,3})', after_kw)
if space_match:
bp_match = space_match
# Last fallback: spelled-out "systolic NNN diastolic NNN"
if not bp_match:
sys_m = re.search(r'systolic\s*[:\-]?\s*(\d{2,3})', text_clean, re.IGNORECASE)
dia_m = re.search(r'diastolic\s*[:\-]?\s*(\d{2,3})', text_clean, re.IGNORECASE)
if sys_m and dia_m:
systolic = int(sys_m.group(1))
diastolic = int(dia_m.group(1))
else:
return None
else:
systolic = int(bp_match.group(1))
diastolic = int(bp_match.group(2))
# Physiological sanity check
if not (60 <= systolic <= 260 and 40 <= diastolic <= 160):
return None
if systolic <= diastolic:
return None
# Pulse — allow commas/whitespace between keyword and digit
# Handles: "pulse 72", "pulse59", "pulse,,54", "pulse ( 65)", "Pulse 51"
pulse = None
pulse_match = re.search(
r'(?:pulse|pr(?!\w)|hr\b|heart\s*rate|bpm|p\.r\.)\s*[:\-,\s]*(\d{2,3})',
text_clean, re.IGNORECASE
)
if pulse_match:
p = int(pulse_match.group(1))
if 30 <= p <= 220:
pulse = p
time_of_day = detect_time_of_day(text)
return {
"systolic": systolic,
"diastolic": diastolic,
"pulse": pulse,
"time_of_day": time_of_day,
"raw_text": text[:300],
}
def detect_time_of_day(text: str) -> str:
"""
Detect morning/afternoon/evening/night from text keywords.
Falls back to current clock time (IST-aware) if no keyword found.
"""
t = text.lower()
morning_kw = ["morning", "subah", "सुबह", "a.m", " am", "breakfast", "wake", "उठ"]
afternoon_kw = ["afternoon", "dopahar", "दोपहर", "lunch", "noon", "दोपह"]
# night keywords also map to evening — only 3 readings per day
evening_kw = ["evening", "shaam", "शाम", "p.m", " pm", "dinner", "शाम",
"night", "raat", "रात", "sleep", "bedtime", "sone"]
for kw in morning_kw:
if kw in t:
return "morning"
for kw in afternoon_kw:
if kw in t:
return "afternoon"
for kw in evening_kw:
if kw in t:
return "evening"
h = datetime.now().hour
if 5 <= h < 12:
return "morning"
elif 12 <= h < 17:
return "afternoon"
else:
return "evening"
def time_of_day_from_hour(hour: int) -> str:
"""Determine time_of_day from an IST hour integer (0-23).
Only 3 readings per day: morning / afternoon / evening.
Anything from 17:00 onwards (including late night) is 'evening'.
"""
if 5 <= hour < 12:
return "morning"
elif 12 <= hour < 17:
return "afternoon"
else:
return "evening"