tinyml-backend / frontend /src /lib /edge /simulator.ts
IDKHowToCodeFr's picture
Upload folder using huggingface_hub
604d53b verified
Raw History Blame
3.94 kB
import { PatientVitals, TelemetryPacket } from "./types";
import { evaluateEdge } from "./inference";
type PatientState = "normal" | "asthma" | "hypertension" | "heart_disease" | "diabetes";
export class BrowserTelemetrySimulator {
private hr = 75.0;
private spo2 = 98.0;
private sys_bp = 120.0;
private dia_bp = 80.0;
private temp = 37.0;
private target_hr = 75.0;
private target_spo2 = 98.0;
private target_sys = 120.0;
private target_dia = 80.0;
private timer: NodeJS.Timeout | null = null;
private listeners: ((packet: TelemetryPacket) => void)[] = [];
constructor() {}
private shiftState() {
// 8% chance every tick to shift patient state
if (Math.random() < 0.08) {
const states: PatientState[] = [
"normal", "normal", "normal", "asthma", "hypertension", "heart_disease", "diabetes"
];
const state = states[Math.floor(Math.random() * states.length)];
if (state === "normal") {
this.target_hr = 75.0; this.target_spo2 = 98.0; this.target_sys = 120.0; this.target_dia = 80.0;
} else if (state === "asthma") {
this.target_hr = 115.0; this.target_spo2 = 88.0; this.target_sys = 135.0; this.target_dia = 85.0;
} else if (state === "hypertension") {
this.target_hr = 90.0; this.target_spo2 = 97.0; this.target_sys = 175.0; this.target_dia = 105.0;
} else if (state === "heart_disease") {
this.target_hr = 135.0; this.target_spo2 = 91.0; this.target_sys = 150.0; this.target_dia = 95.0;
} else if (state === "diabetes") {
this.target_hr = 85.0; this.target_spo2 = 96.0; this.target_sys = 140.0; this.target_dia = 90.0;
}
}
}
private stepPhysics() {
// Interpolate towards target with noise
this.hr += (this.target_hr - this.hr) * 0.12 + (Math.random() * 4 - 2);
this.spo2 += (this.target_spo2 - this.spo2) * 0.12 + (Math.random() * 0.8 - 0.4);
this.sys_bp += (this.target_sys - this.sys_bp) * 0.12 + (Math.random() * 2 - 1);
this.dia_bp += (this.target_dia - this.dia_bp) * 0.12 + (Math.random() * 2 - 1);
this.temp = Math.max(36.0, Math.min(39.2, this.temp + (Math.random() * 0.2 - 0.1)));
// Clamping to physiologically valid ranges
this.hr = Math.max(50.0, Math.min(180.0, this.hr));
this.spo2 = Math.max(78.0, Math.min(100.0, this.spo2));
this.sys_bp = Math.max(85.0, Math.min(210.0, this.sys_bp));
this.dia_bp = Math.max(55.0, Math.min(130.0, this.dia_bp));
}
public getSnapshot(): PatientVitals {
return {
Heart_Rate: Number(this.hr.toFixed(1)),
SpO2_Level: Number(this.spo2.toFixed(1)),
Systolic_BP: Number(this.sys_bp.toFixed(1)),
Diastolic_BP: Number(this.dia_bp.toFixed(1)),
Body_Temp: Number(this.temp.toFixed(1))
};
}
public tick(): TelemetryPacket {
this.shiftState();
this.stepPhysics();
const sensor_data = this.getSnapshot();
const prediction = evaluateEdge(sensor_data);
const packet: TelemetryPacket = {
sensor_data,
prediction: {
is_at_risk: prediction.is_at_risk,
label: prediction.label,
confidence: prediction.confidence,
disease_probs: prediction.disease_probs,
inference_latency_us: prediction.latency_us
},
runtime_mode: "EDGE_WASM_BROWSER"
};
return packet;
}
public subscribe(fn: (packet: TelemetryPacket) => void) {
this.listeners.push(fn);
if (!this.timer && typeof window !== "undefined") {
this.timer = setInterval(() => {
const p = this.tick();
this.listeners.forEach(cb => cb(p));
}, 1000);
}
return () => {
this.listeners = this.listeners.filter(cb => cb !== fn);
if (this.listeners.length === 0 && this.timer) {
clearInterval(this.timer);
this.timer = null;
}
};
}
}
// Global singleton instance for the browser
export const globalSimulator = new BrowserTelemetrySimulator();