require "time" module JosieHealth::LUT # Vital signs ranges based on medical standards VITAL_RANGES = { "pulse" => { "normal_min" => 60, "normal_max" => 100, "concerning_low" => 50, "concerning_high" => 120, "critical_low" => 40, "critical_high" => 150, }, "systolic_bp" => { "normal_min" => 90, "normal_max" => 120, "concerning_low" => 80, "concerning_high" => 140, "critical_low" => 70, "critical_high" => 180, }, "diastolic_bp" => { "normal_min" => 60, "normal_max" => 80, "concerning_low" => 50, "concerning_high" => 90, "critical_low" => 40, "critical_high" => 120, }, "blood_oxygen" => { "normal_min" => 95, "normal_max" => 100, "concerning_low" => 90, "concerning_high" => 100, "critical_low" => 85, "critical_high" => 100, }, } record VitalReading, timestamp : Time, user_id : String, pulse : Int32?, systolic_bp : Int32?, diastolic_bp : Int32?, blood_oxygen : Int32?, notes : String?, time_from_dose : Time::Span? = nil record VitalValidationResult, valid : Bool, errors : Array(String) record VitalLogResult, success : Bool, message : String, concerning_alerts : Array(String) = [] of String record VitalQueryResult, found : Bool, vitals : Array(VitalReading), error : String? = nil module VitalsTracker extend self def validate_vitals(pulse : Int32? = nil, systolic_bp : Int32? = nil, diastolic_bp : Int32? = nil, blood_oxygen : Int32? = nil) : VitalValidationResult errors = [] of String if pulse && (pulse < 20 || pulse > 300) errors << "pulse must be between 20-300 bpm" end if systolic_bp && (systolic_bp < 50 || systolic_bp > 300) errors << "systolic BP must be between 50-300 mmHg" end if diastolic_bp && (diastolic_bp < 30 || diastolic_bp > 200) errors << "diastolic BP must be between 30-200 mmHg" end if blood_oxygen && (blood_oxygen < 70 || blood_oxygen > 100) errors << "blood oxygen must be between 70-100%" end # Check BP relationship if both provided if systolic_bp && diastolic_bp && systolic_bp <= diastolic_bp errors << "systolic BP must be higher than diastolic BP" end VitalValidationResult.new( valid: errors.empty?, errors: errors ) end def check_concerning_vitals(pulse : Int32? = nil, systolic_bp : Int32? = nil, diastolic_bp : Int32? = nil, blood_oxygen : Int32? = nil) : Array(String) alerts = [] of String if pulse ranges = VITAL_RANGES["pulse"] if pulse <= ranges["critical_low"] alerts << "CRITICAL: Pulse extremely low (#{pulse} bpm)" elsif pulse >= ranges["critical_high"] alerts << "CRITICAL: Pulse extremely high (#{pulse} bpm)" elsif pulse <= ranges["concerning_low"] alerts << "CONCERNING: Pulse low (#{pulse} bpm)" elsif pulse >= ranges["concerning_high"] alerts << "CONCERNING: Pulse elevated (#{pulse} bpm)" end end if systolic_bp ranges = VITAL_RANGES["systolic_bp"] if systolic_bp <= ranges["critical_low"] alerts << "CRITICAL: Systolic BP extremely low (#{systolic_bp} mmHg)" elsif systolic_bp >= ranges["critical_high"] alerts << "CRITICAL: Systolic BP extremely high (#{systolic_bp} mmHg)" elsif systolic_bp <= ranges["concerning_low"] alerts << "CONCERNING: Systolic BP low (#{systolic_bp} mmHg)" elsif systolic_bp >= ranges["concerning_high"] alerts << "CONCERNING: Systolic BP elevated (#{systolic_bp} mmHg)" end end if diastolic_bp ranges = VITAL_RANGES["diastolic_bp"] if diastolic_bp <= ranges["critical_low"] alerts << "CRITICAL: Diastolic BP extremely low (#{diastolic_bp} mmHg)" elsif diastolic_bp >= ranges["critical_high"] alerts << "CRITICAL: Diastolic BP extremely high (#{diastolic_bp} mmHg)" elsif diastolic_bp <= ranges["concerning_low"] alerts << "CONCERNING: Diastolic BP low (#{diastolic_bp} mmHg)" elsif diastolic_bp >= ranges["concerning_high"] alerts << "CONCERNING: Diastolic BP elevated (#{diastolic_bp} mmHg)" end end if blood_oxygen ranges = VITAL_RANGES["blood_oxygen"] if blood_oxygen <= ranges["critical_low"] alerts << "CRITICAL: Blood oxygen critically low (#{blood_oxygen}%)" elsif blood_oxygen <= ranges["concerning_low"] alerts << "CONCERNING: Blood oxygen low (#{blood_oxygen}%)" end end alerts end def build_vitals_summary(pulse : Int32? = nil, systolic_bp : Int32? = nil, diastolic_bp : Int32? = nil, blood_oxygen : Int32? = nil) : String parts = [] of String parts << "#{pulse} bpm" if pulse if systolic_bp && diastolic_bp parts << "#{systolic_bp}/#{diastolic_bp} mmHg" elsif systolic_bp parts << "#{systolic_bp}/- mmHg" elsif diastolic_bp parts << "-/#{diastolic_bp} mmHg" end parts << "#{blood_oxygen}% O2" if blood_oxygen parts.join(", ") end def create_vital_reading(user_id : String, pulse : Int32? = nil, systolic_bp : Int32? = nil, diastolic_bp : Int32? = nil, blood_oxygen : Int32? = nil, notes : String? = nil, timestamp : Time = Time.utc) : VitalReading? # Validate at least one vital is provided vitals_provided = [pulse, systolic_bp, diastolic_bp, blood_oxygen].compact return nil if vitals_provided.empty? # Validate ranges validation = validate_vitals(pulse, systolic_bp, diastolic_bp, blood_oxygen) return nil unless validation.valid VitalReading.new( timestamp: timestamp, user_id: user_id.downcase, pulse: pulse, systolic_bp: systolic_bp, diastolic_bp: diastolic_bp, blood_oxygen: blood_oxygen, notes: notes ) end def vitals_within_window(vitals : Array(VitalReading), dose_timestamp : Time, window_hours : Int32 = 2) : Array(VitalReading) window_seconds = window_hours * 3600 vitals.select do |vital| time_diff = (vital.timestamp - dose_timestamp).abs.total_seconds time_diff <= window_seconds end.map do |vital| # Add time_from_dose for correlation analysis VitalReading.new( timestamp: vital.timestamp, user_id: vital.user_id, pulse: vital.pulse, systolic_bp: vital.systolic_bp, diastolic_bp: vital.diastolic_bp, blood_oxygen: vital.blood_oxygen, notes: vital.notes, time_from_dose: vital.timestamp - dose_timestamp ) end.sort_by(&.timestamp) end end end