Spaces:
Running
Running
| """ | |
| 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" | |