File size: 6,169 Bytes
d80edbe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
"""
Vitals Guard AI β€” Shared Utilities
Common constants, vital ranges, and helper functions used across all models.
"""

import numpy as np
import pandas as pd

# ──────────────────────────────────────────────
#  Normal Vital Ranges
# ──────────────────────────────────────────────
VITAL_RANGES = {
    "heart_rate":       {"min": 60,   "max": 100,  "unit": "bpm"},
    "spo2":             {"min": 95,   "max": 100,  "unit": "%"},
    "temperature":      {"min": 36.1, "max": 37.2, "unit": "Β°C"},
    "respiratory_rate": {"min": 12,   "max": 20,   "unit": "breaths/min"},
    "systolic_bp":      {"min": 90,   "max": 140,  "unit": "mmHg"},
    "diastolic_bp":     {"min": 60,   "max": 90,   "unit": "mmHg"},
    "rr_interval":      {"min": 0.6,  "max": 1.0,  "unit": "seconds"},
}

# ──────────────────────────────────────────────
#  Condition Labels
# ──────────────────────────────────────────────
HEALTH_CONDITIONS = [
    "Normal",
    "Hypertension",
    "Hypotension",
    "Tachycardia",
    "Bradycardia",
    "Hypoxia",
    "Fever",
    "Hypothermia",
    "Cardiac_Arrhythmia",
    "Respiratory_Distress",
]

SEVERITY_LEVELS = ["stable", "warning", "critical"]

DISEASE_FINGERPRINTS = {
    "COVID_like":           {"spo2": "low",  "temp": "high", "rr": "high", "hr": "high"},
    "Cardiac":              {"hr": "irregular", "rr_interval": "irregular", "bp": "high"},
    "Sepsis_like":          {"temp": "high", "hr": "high", "bp": "low", "rr": "high"},
    "Respiratory_Distress": {"spo2": "low",  "rr": "high",  "hr": "high"},
    "Heat_Stroke":          {"temp": "very_high", "hr": "high", "bp": "low", "spo2": "low"},
}

BEHAVIORAL_FEATURES = ["sleep_hours", "stress_level", "activity_level"]

# ──────────────────────────────────────────────
#  Helper Functions
# ──────────────────────────────────────────────

def add_noise(values, noise_level=0.02):
    """Add Gaussian noise to an array of values."""
    noise = np.random.normal(0, noise_level * np.std(values), size=len(values))
    return values + noise


def normalize_vitals(df, columns=None):
    """Min-max normalize specified columns in a DataFrame."""
    if columns is None:
        columns = df.select_dtypes(include=[np.number]).columns.tolist()
    df_normalized = df.copy()
    for col in columns:
        col_min = df[col].min()
        col_max = df[col].max()
        if col_max - col_min > 0:
            df_normalized[col] = (df[col] - col_min) / (col_max - col_min)
        else:
            df_normalized[col] = 0.0
    return df_normalized


def generate_normal_vitals(n=1):
    """Generate n samples of normal vital signs."""
    return {
        "heart_rate":       np.random.uniform(60, 100, n),
        "spo2":             np.random.uniform(95, 100, n),
        "temperature":      np.random.uniform(36.1, 37.2, n),
        "respiratory_rate": np.random.uniform(12, 20, n),
        "systolic_bp":      np.random.uniform(90, 140, n),
        "diastolic_bp":     np.random.uniform(60, 90, n),
    }


def generate_ecg_wave(duration_sec=2.0, sampling_rate=250, heart_rate=72, noise_level=0.05):
    """
    Generate a synthetic ECG-like signal using a simplified PQRST model.
    Returns (time_array, ecg_signal).
    """
    t = np.linspace(0, duration_sec, int(duration_sec * sampling_rate), endpoint=False)
    ecg = np.zeros_like(t)
    beat_interval = 60.0 / heart_rate
    num_beats = int(duration_sec / beat_interval) + 1

    for i in range(num_beats):
        beat_start = i * beat_interval
        # P wave
        p_center = beat_start + 0.1
        ecg += 0.15 * np.exp(-((t - p_center) ** 2) / (2 * 0.01 ** 2))
        # QRS complex
        q_center = beat_start + 0.2
        ecg -= 0.1 * np.exp(-((t - q_center) ** 2) / (2 * 0.005 ** 2))
        r_center = beat_start + 0.22
        ecg += 1.0 * np.exp(-((t - r_center) ** 2) / (2 * 0.005 ** 2))
        s_center = beat_start + 0.24
        ecg -= 0.15 * np.exp(-((t - s_center) ** 2) / (2 * 0.005 ** 2))
        # T wave
        t_center = beat_start + 0.4
        ecg += 0.3 * np.exp(-((t - t_center) ** 2) / (2 * 0.02 ** 2))

    ecg += np.random.normal(0, noise_level, len(ecg))
    return t, ecg


def calculate_ews(hr, spo2, temp, rr, systolic_bp):
    """
    Calculate a simplified Early Warning Score (inspired by NEWS2).
    Returns integer score (0 = best, higher = worse).
    """
    score = 0

    # Heart Rate scoring
    if hr <= 40 or hr >= 131:
        score += 3
    elif hr <= 50 or (hr >= 111 and hr <= 130):
        score += 2
    elif (hr >= 91 and hr <= 110):
        score += 1

    # SpO2 scoring
    if spo2 <= 91:
        score += 3
    elif spo2 <= 93:
        score += 2
    elif spo2 <= 95:
        score += 1

    # Temperature scoring
    if temp <= 35.0:
        score += 3
    elif temp <= 36.0 or temp >= 39.1:
        score += 2
    elif (temp >= 38.1 and temp <= 39.0):
        score += 1

    # Respiratory Rate scoring
    if rr <= 8 or rr >= 25:
        score += 3
    elif rr >= 21 and rr <= 24:
        score += 2
    elif rr >= 9 and rr <= 11:
        score += 1

    # Systolic BP scoring
    if systolic_bp <= 90 or systolic_bp >= 220:
        score += 3
    elif systolic_bp <= 100 or (systolic_bp >= 180 and systolic_bp <= 219):
        score += 2
    elif systolic_bp <= 110:
        score += 1

    return score


def ews_to_severity(ews_score):
    """Convert EWS score to severity level."""
    if ews_score <= 4:
        return "stable"
    elif ews_score <= 8:
        return "warning"
    else:
        return "critical"


def get_project_root():
    """Get the project root directory."""
    return "."