IDKHowToCodeFr commited on
Commit
55f07bf
·
verified ·
1 Parent(s): 3b532f8

Upload folder using huggingface_hub

Browse files
README.md CHANGED
@@ -12,8 +12,8 @@ pinned: false
12
  <p><strong>Cardiovascular Telemetry & Edge MLOps Engine</strong></p>
13
 
14
  <p>
15
- <a href="https://huggingface.co/spaces/IDKHowToCodeFR/HEARTFLOW_OS" target="_blank">
16
- <img src="https://img.shields.io/badge/🤗_HuggingFace-Live_Demo-FFD21E.svg?style=for-the-badge" alt="Hugging Face Space" />
17
  </a>
18
  <a href="https://idkhowtocodefr.github.io/HEARTFLOW_OS" target="_blank">
19
  <img src="https://img.shields.io/badge/🌐_Website-Live_App-black.svg?style=for-the-badge&logo=githubpages" alt="Live Website" />
 
12
  <p><strong>Cardiovascular Telemetry & Edge MLOps Engine</strong></p>
13
 
14
  <p>
15
+ <a href="https://idkhowtocodefr-tinyml-backend.hf.space/docs" target="_blank">
16
+ <img src="https://img.shields.io/badge/🤗_HuggingFace-API_Docs-FFD21E.svg?style=for-the-badge" alt="Hugging Face API" />
17
  </a>
18
  <a href="https://idkhowtocodefr.github.io/HEARTFLOW_OS" target="_blank">
19
  <img src="https://img.shields.io/badge/🌐_Website-Live_App-black.svg?style=for-the-badge&logo=githubpages" alt="Live Website" />
backend/database.py CHANGED
@@ -91,8 +91,9 @@ async def init_db() -> None:
91
 
92
  async def log_prediction(data: Any, prediction_label: str, confidence: float) -> None:
93
  async with aiosqlite.connect(DB_PATH) as db:
94
- from datetime import timezone, timedelta
95
- ist = timezone(timedelta(hours=5, minutes=30))
 
96
  await db.execute('''
97
  INSERT INTO predictions (timestamp, heart_rate, spo2, sys_bp, dia_bp, temp, fall_detection, prediction_label, confidence)
98
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
 
91
 
92
  async def log_prediction(data: Any, prediction_label: str, confidence: float) -> None:
93
  async with aiosqlite.connect(DB_PATH) as db:
94
+ from datetime import datetime
95
+ import zoneinfo
96
+ ist = zoneinfo.ZoneInfo("Asia/Kolkata")
97
  await db.execute('''
98
  INSERT INTO predictions (timestamp, heart_rate, spo2, sys_bp, dia_bp, temp, fall_detection, prediction_label, confidence)
99
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
backend/main.py CHANGED
@@ -2,17 +2,14 @@ import sys
2
  import os
3
  sys.path.append(os.path.dirname(os.path.abspath(__file__)))
4
 
5
- from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException, WebSocket, WebSocketDisconnect, Depends
6
  from fastapi.middleware.cors import CORSMiddleware
7
- from pydantic import BaseModel
8
  import pandas as pd
9
  import asyncio
10
- import random
11
  import shap
12
  import database as db
13
- import numpy as np
14
  from contextlib import asynccontextmanager
15
- from config import settings
16
  import warnings
17
 
18
  # Suppress sklearn version mismatch warnings in stdout
@@ -21,11 +18,20 @@ try:
21
  warnings.filterwarnings("ignore", category=InconsistentVersionWarning)
22
  except ImportError:
23
  pass
 
24
  from preprocessing import preprocess_data
25
  from export import generate_c_code
26
  from inference import evaluate
 
 
 
 
 
 
 
 
27
 
28
- ensemble_system = None
29
 
30
  async def background_sync():
31
  while True:
@@ -35,20 +41,16 @@ async def background_sync():
35
 
36
  @asynccontextmanager
37
  async def lifespan(app: FastAPI):
38
- global ensemble_system
39
  await db.init_db()
40
-
41
  sync_task = asyncio.create_task(background_sync())
42
 
43
- try:
44
- from ensemble import EnsembleModel
45
- ensemble_system = EnsembleModel()
46
- print("Models loaded successfully.")
47
- except Exception as e:
48
- print(f"Model loading failed: {e}")
49
  yield
50
  sync_task.cancel()
51
- ensemble_system = None
52
 
53
  app = FastAPI(title="TinyML Healthcare API", lifespan=lifespan)
54
 
@@ -60,36 +62,13 @@ app.add_middleware(
60
  allow_headers=["*"],
61
  )
62
 
63
- def get_ensemble():
64
- global ensemble_system
65
- return ensemble_system
66
-
67
- class PatientData(BaseModel):
68
- Heart_Rate: float
69
- SpO2_Level: float
70
- Systolic_BP: float
71
- Diastolic_BP: float
72
- Body_Temp: float
73
-
74
- from fastapi.responses import RedirectResponse
75
-
76
  @app.get("/")
77
  def read_root():
78
  return RedirectResponse(url="/docs")
79
 
80
  @app.get("/health")
81
  def health_check():
82
- return {"status": "Healthy" if get_ensemble() else "Warning - Models Offline"}
83
-
84
- def log_alert_sync(data: PatientData, conf: float):
85
- try:
86
- from datetime import datetime, timezone, timedelta
87
- ist = timezone(timedelta(hours=5, minutes=30))
88
- timestamp = datetime.now(ist).strftime("%Y-%m-%d %H:%M:%S")
89
- with open("alerts.log", "a") as f:
90
- f.write(f"[{timestamp}] ALERT: Patient at risk! HR: {data.Heart_Rate}, SpO2: {data.SpO2_Level}, Confidence: {conf:.2f}\n")
91
- except Exception:
92
- pass
93
 
94
  async def log_prediction_background(data, final_pred, conf):
95
  try:
@@ -100,13 +79,11 @@ async def log_prediction_background(data, final_pred, conf):
100
  @app.post("/predict")
101
  async def predict(data: PatientData, background_tasks: BackgroundTasks):
102
  try:
103
- eng = get_ensemble()
104
  if not eng:
105
  return {"error": "Models untrained. Ensure python backend/models.py executes."}
106
 
107
- from inference import evaluate
108
  result = await asyncio.to_thread(evaluate, eng, data)
109
-
110
  if "error" in result:
111
  return result
112
 
@@ -114,11 +91,11 @@ async def predict(data: PatientData, background_tasks: BackgroundTasks):
114
  final_pred = result["prediction_label"]
115
  conf = result["probability"]
116
 
117
- # Critical Alert System — fault-tolerant
118
  if is_at_risk == 1 and float(conf) > 0.80:
119
- background_tasks.add_task(log_alert_sync, data, float(conf))
120
 
121
- # Log to SQLite History — fault-tolerant
122
  background_tasks.add_task(log_prediction_background, data, final_pred, float(conf))
123
 
124
  return result
@@ -129,34 +106,15 @@ async def predict(data: PatientData, background_tasks: BackgroundTasks):
129
  @app.websocket("/ws/feed")
130
  async def websocket_feed(websocket: WebSocket):
131
  await websocket.accept()
132
-
133
- eng = get_ensemble()
134
  if not eng:
135
  await websocket.close(code=1011)
136
  return
137
 
138
- from simulator import PatientDataSimulator
139
- simulator = PatientDataSimulator()
140
-
141
  try:
142
- async for state in simulator.run():
143
- data = PatientData(**state)
144
-
145
- prediction_result = await asyncio.to_thread(evaluate, eng, data)
146
-
147
- front_pred = None
148
- if "error" not in prediction_result:
149
- front_pred = {
150
- "is_at_risk": 1 if prediction_result["prediction"] else 0,
151
- "label": prediction_result["prediction_label"],
152
- "confidence": prediction_result["probability"],
153
- "disease_probs": prediction_result.get("disease_probs", {})
154
- }
155
-
156
- await websocket.send_json({
157
- "sensor_data": data.model_dump(),
158
- "prediction": front_pred
159
- })
160
  except WebSocketDisconnect:
161
  pass
162
  except Exception as e:
@@ -168,11 +126,9 @@ async def history():
168
 
169
  @app.get("/dataset")
170
  def get_dataset():
171
- data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'patient_dataset.csv')
172
- if os.path.exists(data_path):
173
  try:
174
- # Force UTF-8 and strip column whitespace
175
- df = pd.read_csv(data_path, encoding='utf-8')
176
  df.columns = [c.strip() for c in df.columns]
177
  return df.to_dict(orient="records")
178
  except Exception as e:
@@ -187,18 +143,11 @@ def force_sync():
187
 
188
  @app.post("/explain")
189
  async def explain(data: PatientData):
190
- eng = get_ensemble()
191
  if not eng or 'rf' not in eng.models:
192
  return {"error": "RF Model unavailable for explanation."}
193
 
194
- df = pd.DataFrame([{
195
- 'Heart Rate (bpm)': data.Heart_Rate,
196
- 'SpO2 Level (%)': data.SpO2_Level,
197
- 'Systolic Blood Pressure (mmHg)': data.Systolic_BP,
198
- 'Diastolic Blood Pressure (mmHg)': data.Diastolic_BP,
199
- 'Body Temperature (°C)': data.Body_Temp
200
- }])
201
-
202
  X_proc, _ = await asyncio.to_thread(preprocess_data, df, False)
203
 
204
  def compute_shap():
@@ -223,56 +172,22 @@ async def explain(data: PatientData):
223
 
224
  @app.get("/export_tinyml")
225
  def export_tinyml(model_name: str = "rf", quantize: bool = False):
226
- eng = get_ensemble()
227
  return generate_c_code(eng, model_name, quantize)
228
 
229
- def run_training_background():
230
- try:
231
- from models import train_models
232
- train_models()
233
- # Force reload in lifespan is tricky, but we can do it via global
234
- global ensemble_system
235
- from ensemble import EnsembleModel
236
- ensemble_system = EnsembleModel()
237
- except Exception as e:
238
- print(f"Background training failed: {e}")
239
 
240
  @app.post("/retrain")
241
  async def retrain(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
242
- data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'patient_dataset.csv')
243
- os.makedirs(os.path.dirname(data_path), exist_ok=True)
244
 
245
- try:
246
- content = await file.read()
247
- import io
248
- new_df = pd.read_csv(io.BytesIO(content))
249
- new_df.columns = [c.strip() for c in new_df.columns]
250
-
251
- if os.path.exists(data_path):
252
- existing_df = pd.read_csv(data_path, encoding='utf-8')
253
- existing_df.columns = [c.strip() for c in existing_df.columns]
254
-
255
- # Validate schema
256
- required_cols = set(existing_df.columns)
257
- provided_cols = set(new_df.columns)
258
-
259
- if not required_cols.issubset(provided_cols):
260
- missing = required_cols - provided_cols
261
- return {"error": f"Schema mismatch. Missing columns: {list(missing)}"}
262
 
263
- # Ensure columns are in the same order and select only necessary ones
264
- new_df = new_df[existing_df.columns]
265
-
266
- # Append data
267
- combined_df = pd.concat([existing_df, new_df], ignore_index=True)
268
- else:
269
- combined_df = new_df
270
-
271
- # Save validated and combined dataset
272
- combined_df.to_csv(data_path, index=False, encoding='utf-8')
273
-
274
- background_tasks.add_task(run_training_background)
275
-
276
- return {"status": "success", "message": f"Dataset updated (now {len(combined_df)} records) and ensemble retrain started in background!"}
277
- except Exception as e:
278
- return {"error": f"Retraining failed: {str(e)}"}
 
2
  import os
3
  sys.path.append(os.path.dirname(os.path.abspath(__file__)))
4
 
5
+ from fastapi import FastAPI, UploadFile, File, BackgroundTasks, HTTPException, WebSocket, WebSocketDisconnect
6
  from fastapi.middleware.cors import CORSMiddleware
 
7
  import pandas as pd
8
  import asyncio
 
9
  import shap
10
  import database as db
11
+ import logging
12
  from contextlib import asynccontextmanager
 
13
  import warnings
14
 
15
  # Suppress sklearn version mismatch warnings in stdout
 
18
  warnings.filterwarnings("ignore", category=InconsistentVersionWarning)
19
  except ImportError:
20
  pass
21
+
22
  from preprocessing import preprocess_data
23
  from export import generate_c_code
24
  from inference import evaluate
25
+ from schemas import PatientData
26
+ from mlops import MLOpsEngine
27
+ from streamer import TelemetryStreamer
28
+ from fastapi.responses import RedirectResponse
29
+
30
+ # Setup standard logging for critical fault alerts
31
+ logging.basicConfig(filename="alerts.log", level=logging.WARNING,
32
+ format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
33
 
34
+ ml_engine = None
35
 
36
  async def background_sync():
37
  while True:
 
41
 
42
  @asynccontextmanager
43
  async def lifespan(app: FastAPI):
44
+ global ml_engine
45
  await db.init_db()
 
46
  sync_task = asyncio.create_task(background_sync())
47
 
48
+ data_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'patient_dataset.csv')
49
+ ml_engine = MLOpsEngine(data_path)
50
+
 
 
 
51
  yield
52
  sync_task.cancel()
53
+ ml_engine = None
54
 
55
  app = FastAPI(title="TinyML Healthcare API", lifespan=lifespan)
56
 
 
62
  allow_headers=["*"],
63
  )
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  @app.get("/")
66
  def read_root():
67
  return RedirectResponse(url="/docs")
68
 
69
  @app.get("/health")
70
  def health_check():
71
+ return {"status": "Healthy" if ml_engine and ml_engine.get_ensemble() else "Warning - Models Offline"}
 
 
 
 
 
 
 
 
 
 
72
 
73
  async def log_prediction_background(data, final_pred, conf):
74
  try:
 
79
  @app.post("/predict")
80
  async def predict(data: PatientData, background_tasks: BackgroundTasks):
81
  try:
82
+ eng = ml_engine.get_ensemble()
83
  if not eng:
84
  return {"error": "Models untrained. Ensure python backend/models.py executes."}
85
 
 
86
  result = await asyncio.to_thread(evaluate, eng, data)
 
87
  if "error" in result:
88
  return result
89
 
 
91
  final_pred = result["prediction_label"]
92
  conf = result["probability"]
93
 
94
+ # Critical Alert System — standard library logging
95
  if is_at_risk == 1 and float(conf) > 0.80:
96
+ logging.warning(f"Patient at risk! HR: {data.Heart_Rate}, SpO2: {data.SpO2_Level}, Confidence: {conf:.2f}")
97
 
98
+ # Log to SQLite History
99
  background_tasks.add_task(log_prediction_background, data, final_pred, float(conf))
100
 
101
  return result
 
106
  @app.websocket("/ws/feed")
107
  async def websocket_feed(websocket: WebSocket):
108
  await websocket.accept()
109
+ eng = ml_engine.get_ensemble()
 
110
  if not eng:
111
  await websocket.close(code=1011)
112
  return
113
 
114
+ streamer = TelemetryStreamer(eng)
 
 
115
  try:
116
+ async for payload in streamer.stream():
117
+ await websocket.send_json(payload)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  except WebSocketDisconnect:
119
  pass
120
  except Exception as e:
 
126
 
127
  @app.get("/dataset")
128
  def get_dataset():
129
+ if ml_engine and os.path.exists(ml_engine.dataset_path):
 
130
  try:
131
+ df = pd.read_csv(ml_engine.dataset_path, encoding='utf-8')
 
132
  df.columns = [c.strip() for c in df.columns]
133
  return df.to_dict(orient="records")
134
  except Exception as e:
 
143
 
144
  @app.post("/explain")
145
  async def explain(data: PatientData):
146
+ eng = ml_engine.get_ensemble()
147
  if not eng or 'rf' not in eng.models:
148
  return {"error": "RF Model unavailable for explanation."}
149
 
150
+ df = pd.DataFrame([data.model_dump()])
 
 
 
 
 
 
 
151
  X_proc, _ = await asyncio.to_thread(preprocess_data, df, False)
152
 
153
  def compute_shap():
 
172
 
173
  @app.get("/export_tinyml")
174
  def export_tinyml(model_name: str = "rf", quantize: bool = False):
175
+ eng = ml_engine.get_ensemble()
176
  return generate_c_code(eng, model_name, quantize)
177
 
178
+ def _run_mlops_ingest(csv_bytes: bytes):
179
+ return ml_engine.ingest_batch_sync(csv_bytes)
 
 
 
 
 
 
 
 
180
 
181
  @app.post("/retrain")
182
  async def retrain(background_tasks: BackgroundTasks, file: UploadFile = File(...)):
183
+ content = await file.read()
 
184
 
185
+ # We add this to background tasks so the HTTP response returns immediately
186
+ # while the engine merges CSVs and retrains the models.
187
+ def bg_task():
188
+ res = ml_engine.ingest_batch_sync(content)
189
+ if not res.success:
190
+ print(f"MLOps Background Task Failed: {res.message}")
 
 
 
 
 
 
 
 
 
 
 
191
 
192
+ background_tasks.add_task(bg_task)
193
+ return {"status": "success", "message": "Dataset uploaded and ensemble retrain started in background!"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/mlops.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import pandas as pd
4
+ from typing import Optional
5
+
6
+ class RetrainResult:
7
+ def __init__(self, success: bool, message: str):
8
+ self.success = success
9
+ self.message = message
10
+
11
+ class MLOpsEngine:
12
+ """
13
+ Deep Module encapsulating the MLOps Lifecycle:
14
+ - Maintains the active ensemble model.
15
+ - Handles schema validation and data ingestion natively with pandas.
16
+ - Orchestrates model retraining and hot-swapping.
17
+ """
18
+ def __init__(self, dataset_path: str):
19
+ self.dataset_path = dataset_path
20
+ self.active_ensemble = None
21
+ self._load_active_models()
22
+
23
+ def _load_active_models(self):
24
+ try:
25
+ from ensemble import EnsembleModel
26
+ self.active_ensemble = EnsembleModel()
27
+ except Exception as e:
28
+ print(f"MLOps: Model loading failed (likely untrained): {e}")
29
+
30
+ def get_ensemble(self):
31
+ return self.active_ensemble
32
+
33
+ def ingest_batch_sync(self, csv_bytes: bytes) -> RetrainResult:
34
+ try:
35
+ new_df = pd.read_csv(io.BytesIO(csv_bytes))
36
+ new_df.columns = [c.strip() for c in new_df.columns]
37
+
38
+ if os.path.exists(self.dataset_path):
39
+ existing_df = pd.read_csv(self.dataset_path, encoding='utf-8')
40
+ existing_df.columns = [c.strip() for c in existing_df.columns]
41
+
42
+ # Check for schema mismatch before concatenation
43
+ missing = set(existing_df.columns) - set(new_df.columns)
44
+ if missing:
45
+ return RetrainResult(False, f"Schema mismatch. Missing columns: {list(missing)}")
46
+
47
+ # Ponytail shrink: pandas join="inner" drops mismatched extra columns cleanly
48
+ combined_df = pd.concat([existing_df, new_df], join="inner", ignore_index=True)
49
+ else:
50
+ combined_df = new_df
51
+
52
+ os.makedirs(os.path.dirname(self.dataset_path), exist_ok=True)
53
+ combined_df.to_csv(self.dataset_path, index=False, encoding='utf-8')
54
+
55
+ # Re-train models synchronously
56
+ from models import train_models
57
+ train_models()
58
+
59
+ # Hot-swap the live models
60
+ self._load_active_models()
61
+
62
+ return RetrainResult(True, f"Dataset updated (now {len(combined_df)} records) and ensemble hot-swapped!")
63
+ except Exception as e:
64
+ return RetrainResult(False, f"Retraining failed: {str(e)}")
backend/schemas.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel
2
+
3
+ class PatientData(BaseModel):
4
+ Heart_Rate: float
5
+ SpO2_Level: float
6
+ Systolic_BP: float
7
+ Diastolic_BP: float
8
+ Body_Temp: float
backend/streamer.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ from typing import AsyncGenerator
3
+ from simulator import PatientDataSimulator
4
+ from inference import evaluate
5
+ from schemas import PatientData
6
+
7
+ class TelemetryStreamer:
8
+ """
9
+ Deep Module for Telemetry Streaming.
10
+ Encapsulates the simulator lifecycle, ML inference threading, and payload formatting.
11
+ Provides a simple async generator interface.
12
+ """
13
+ def __init__(self, ensemble_model):
14
+ self.ensemble_model = ensemble_model
15
+ self.simulator = PatientDataSimulator()
16
+
17
+ async def stream(self) -> AsyncGenerator[dict, None]:
18
+ async for state in self.simulator.run():
19
+ data = PatientData(**state)
20
+
21
+ # Offload ML inference to thread pool to avoid blocking the event loop
22
+ prediction_result = await asyncio.to_thread(evaluate, self.ensemble_model, data)
23
+
24
+ front_pred = None
25
+ if "error" not in prediction_result:
26
+ front_pred = {
27
+ "is_at_risk": 1 if prediction_result.get("prediction") else 0,
28
+ "label": prediction_result.get("prediction_label"),
29
+ "confidence": prediction_result.get("probability"),
30
+ "disease_probs": prediction_result.get("disease_probs", {})
31
+ }
32
+
33
+ yield {
34
+ "sensor_data": data.model_dump(),
35
+ "prediction": front_pred
36
+ }
frontend/src/app/architecture/page.tsx CHANGED
@@ -4,152 +4,172 @@ import { motion } from "framer-motion";
4
 
5
  const easeFluid: [number, number, number, number] = [0.16, 1, 0.3, 1];
6
 
7
- const staggerContainer = {
8
- hidden: { opacity: 0 },
9
- show: {
10
- opacity: 1,
11
- transition: { staggerChildren: 0.1, delayChildren: 0.1 }
12
- }
13
- };
14
-
15
  const fadeUp = {
16
- hidden: { opacity: 0, y: 20 },
17
- show: { opacity: 1, y: 0, transition: { duration: 0.8, ease: easeFluid } }
18
  };
19
 
20
- const BlueprintNode = ({ title, desc, pulse = false }: { title: string, desc: string, pulse?: boolean }) => (
21
- <motion.div
22
- whileHover={{ x: 5 }}
23
- className="border border-slate/30 bg-ink/50 p-4 relative group hover:border-hazard transition-colors cursor-default"
24
- >
25
- {pulse && (
26
- <div className="absolute right-4 top-1/2 -translate-y-1/2 flex items-center justify-center">
27
- <motion.div
28
- animate={{ scale: [1, 2, 1], opacity: [0.8, 0, 0.8] }}
29
- transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
30
- className="absolute w-3 h-3 bg-hazard rounded-full"
31
- />
32
- <div className="w-1.5 h-1.5 bg-hazard rounded-full relative z-10" />
33
- </div>
34
- )}
35
- <div className="text-[10px] text-slate tracking-[0.1em] mb-1 uppercase">{desc}</div>
36
- <div className="text-[14px] text-white font-bold tracking-tight uppercase">{title}</div>
37
- </motion.div>
38
- );
39
-
40
  export default function Architecture() {
41
  return (
42
  <main className="min-h-[100dvh] bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16 overflow-x-hidden">
43
- <motion.div
44
- variants={staggerContainer}
45
- initial="hidden"
46
- animate="show"
47
- className="max-w-[1200px] mx-auto w-full px-4 md:px-8 py-8 md:py-16 flex-1 flex flex-col"
48
- >
49
- <motion.header variants={fadeUp} className="mb-8 md:mb-12 border-b-4 border-ink pb-8 flex justify-between items-end">
 
50
  <div>
51
- <div className="text-[12px] text-hazard font-bold tracking-[0.1em] mb-4">
52
- /// DOCS_AND_SPECS
 
53
  </div>
54
- <h1 className="text-[clamp(2.5rem,6vw,6rem)] leading-tight">
55
- SYSTEM<br />ARCHITECTURE
56
  </h1>
57
  </div>
 
 
 
 
 
58
  </motion.header>
59
 
60
- {/* Live Hardware Blueprint */}
61
- <motion.div variants={fadeUp} className="w-full bg-ink p-8 md:p-12 mb-16 relative font-mono overflow-hidden border-2 border-ink shadow-[8px_8px_0px_0px_rgba(242,56,39,1)]">
62
- {/* Animated Scanning Line */}
63
- <motion.div
64
- animate={{ top: ["-10%", "110%"] }}
65
- transition={{ duration: 8, ease: "linear", repeat: Infinity }}
66
- className="absolute left-0 right-0 h-32 bg-gradient-to-b from-transparent via-hazard/10 to-transparent pointer-events-none z-0"
67
- />
68
-
69
- <div className="relative z-10 flex flex-col lg:flex-row gap-12 lg:gap-16 items-stretch">
 
70
 
71
- {/* Stage 1: Ingestion */}
72
- <div className="flex-1 flex flex-col gap-6 border-l-2 border-slate/30 pl-6 relative">
73
- <div className="absolute -left-[5px] top-0 w-2 h-2 bg-hazard" />
74
- <div className="text-[10px] text-hazard tracking-[0.2em] font-bold">PHASE_01 // INGESTION</div>
75
- <BlueprintNode title="PATIENT SENSORS" desc="60Hz Telemetry Stream" pulse />
76
- <BlueprintNode title="CLIENT UI" desc="Next.js 16 / React 19" />
77
- <BlueprintNode title="CSV BATCH" desc="Offline Dataset Upload" />
 
 
 
 
 
 
 
78
  </div>
 
 
79
 
80
- {/* Stage 2: Processing */}
81
- <div className="flex-1 flex flex-col gap-6 border-l-2 border-slate/30 pl-6 relative">
82
- <div className="absolute -left-[5px] top-0 w-2 h-2 bg-hazard" />
83
- <div className="text-[10px] text-hazard tracking-[0.2em] font-bold">PHASE_02 // CORE_ROUTER</div>
84
- <BlueprintNode title="FASTAPI SERVER" desc="Async WebSockets & REST" pulse />
85
- <BlueprintNode title="SQLITE DB" desc="Aiosqlite Async Storage" />
 
 
 
 
 
 
86
  </div>
 
 
87
 
88
- {/* Stage 3: Inference & Compilation */}
89
- <div className="flex-1 flex flex-col gap-6 border-l-2 border-slate/30 pl-6 relative">
90
- <div className="absolute -left-[5px] top-0 w-2 h-2 bg-hazard" />
91
- <div className="text-[10px] text-hazard tracking-[0.2em] font-bold">PHASE_03 // EDGE_INTELLIGENCE</div>
92
- <BlueprintNode title="SCIKIT-LEARN" desc="Soft-Voting Ensemble" pulse />
93
- <BlueprintNode title="AST COMPILER" desc="Python to C Transpiler" />
94
- <BlueprintNode title="ESP32 HARDWARE" desc="Zero-Dep C Firmware" pulse />
 
 
 
 
 
 
 
 
 
95
  </div>
96
-
97
  </div>
98
  </motion.div>
99
 
100
- {/* Detailed Specifications */}
101
- <motion.div variants={staggerContainer} initial="hidden" animate="show" className="w-full flex flex-col gap-8">
102
 
103
- <motion.div variants={fadeUp} className="border-t-4 border-ink pt-8">
104
- <h2 className="text-[20px] md:text-[24px] font-sans font-black tracking-[-0.02em] uppercase text-ink mb-6">
105
- [ SPEC_01 ] Data Sanitization & Feature Engineering
106
- </h2>
107
- <div className="grid grid-cols-1 md:grid-cols-2 gap-8">
108
- <div className="bg-white border-2 border-ink p-6">
109
- <h3 className="text-[12px] text-hazard font-bold tracking-[0.1em] mb-4 uppercase">/// Pipeline_Execution</h3>
110
- <p className="text-[13px] md:text-[14px] text-slate leading-relaxed uppercase mb-4">
111
- Raw telemetry is hostile. The ingestion script aggressively drops redundant alert flags to prevent target-leakage. Broken unicode encodings from edge sensors are patched on the fly.
112
- </p>
113
- <div className="font-mono text-[10px] md:text-[11px] bg-ink text-canvas-cream p-4 border-l-4 border-hazard">
114
- <span className="text-slate">01</span> DROP: 'Heart Rate Alert', 'Patient Number'<br/>
115
- <span className="text-slate">02</span> PATCH: '\ufffd' -&gt; '°'<br/>
116
- <span className="text-slate">03</span> ENCODE: LabelEncoder(Predicted Disease)
 
 
 
 
 
117
  </div>
118
- </div>
119
- <div className="bg-white border-2 border-ink p-6">
120
- <h3 className="text-[12px] text-hazard font-bold tracking-[0.1em] mb-4 uppercase">/// Feature_Synthesis</h3>
121
- <p className="text-[13px] md:text-[14px] text-slate leading-relaxed uppercase mb-4">
122
- Missing sensor drops are repaired using a fitted <code className="bg-canvas-cream px-1 text-ink">SimpleImputer(mean)</code>. We inject a non-linear hint directly into the AST: <code className="bg-canvas-cream px-1 text-ink">Risk_Severity</code>. All floats are strictly normalized via <code className="bg-canvas-cream px-1 text-ink">StandardScaler</code>.
123
- </p>
124
- <div className="font-mono text-[10px] md:text-[11px] bg-ink text-canvas-cream p-4 border-l-4 border-hazard">
125
- Risk_Severity = (HR &gt; 105) + (SpO2 &lt; 94)<br/>
126
- Imputer.fit_transform(continuous_features)<br/>
127
- Scaler.fit_transform(continuous_features)
 
128
  </div>
 
129
  </div>
130
  </div>
131
- </motion.div>
132
 
133
- <motion.div variants={fadeUp} className="border-t-4 border-ink pt-8">
134
- <h2 className="text-[20px] md:text-[24px] font-sans font-black tracking-[-0.02em] uppercase text-ink mb-6">
135
- [ SPEC_02 ] Soft-Voting Ensemble & MLOps
136
- </h2>
137
- <div className="bg-white border-2 border-ink p-6">
138
- <p className="text-[13px] md:text-[14px] text-slate leading-relaxed uppercase mb-6 max-w-3xl">
 
 
 
139
  Single-model inference is clinically insufficient. HeartFlow_OS aggregates probability distributions from 5 disparate algorithms: <strong>KNN, SVM, LogReg, Random Forest, and a Neural Network (MLP)</strong>.
140
  <br/><br/>
141
- When a clinician uploads a new batch dataset, the API spawns a background thread. It retrains all 5 models, calculates the weighted F1-Score against a 20% holdout, and checks <code className="bg-canvas-cream px-1 text-ink">registry.json</code>. If the new score degrades, the batch is destroyed. If it improves, the active `.pkl` weights are hot-swapped globally without dropping WebSocket streams.
142
  </p>
143
- <div className="flex flex-col md:flex-row gap-4 items-center justify-start text-[12px] font-bold">
144
- <div className="bg-ink text-white px-4 py-2">F1 SCORE &gt; ACTIVE_V1</div>
145
- <div className="text-ink">━━▶</div>
146
- <div className="bg-hazard text-white px-4 py-2 border-2 border-hazard shadow-[4px_4px_0px_0px_rgba(10,10,10,1)]">HOT_SWAP_MODELS()</div>
 
 
 
 
147
  </div>
148
  </div>
149
- </motion.div>
150
 
151
  </motion.div>
152
- </motion.div>
153
  </main>
154
  );
155
  }
 
4
 
5
  const easeFluid: [number, number, number, number] = [0.16, 1, 0.3, 1];
6
 
 
 
 
 
 
 
 
 
7
  const fadeUp = {
8
+ hidden: { opacity: 0, y: 10 },
9
+ show: { opacity: 1, y: 0, transition: { duration: 0.6, ease: easeFluid } }
10
  };
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  export default function Architecture() {
13
  return (
14
  <main className="min-h-[100dvh] bg-canvas-cream flex flex-col font-mono selection:bg-hazard selection:text-white pb-16 overflow-x-hidden">
15
+
16
+ <div className="max-w-[1400px] mx-auto w-full px-4 md:px-8 py-16 flex-1 flex flex-col">
17
+
18
+ {/* HEADER BLOCK */}
19
+ <motion.header
20
+ initial="hidden" animate="show" variants={fadeUp}
21
+ className="mb-16 border-b-2 border-ink pb-8 flex flex-col md:flex-row justify-between items-start md:items-end gap-8"
22
+ >
23
  <div>
24
+ <div className="text-[10px] text-hazard font-bold tracking-[0.2em] mb-4 uppercase flex items-center gap-4">
25
+ <span className="w-2 h-2 bg-hazard animate-pulse" />
26
+ SYSTEM_ARCHITECTURE // DECLASSIFIED
27
  </div>
28
+ <h1 className="text-[clamp(3rem,8vw,8rem)] leading-[0.85] font-sans font-black tracking-[-0.04em] uppercase text-ink">
29
+ PIPELINE<br />TOPOLOGY
30
  </h1>
31
  </div>
32
+ <div className="text-[12px] text-slate font-bold tracking-[0.1em] uppercase text-right max-w-xs">
33
+ REF: HFL-OS-ARCH-01<br/>
34
+ REV: 2.6.4<br/>
35
+ STATUS: ACTIVE
36
+ </div>
37
  </motion.header>
38
 
39
+ {/* BLUEPRINT GRID */}
40
+ <motion.div
41
+ initial="hidden" animate="show" variants={fadeUp}
42
+ className="w-full bg-ink grid grid-cols-1 md:grid-cols-3 gap-[2px] p-[2px] mb-16 shadow-[16px_16px_0px_0px_rgba(230,25,25,1)]"
43
+ >
44
+ {/* COLUMN 1 */}
45
+ <div className="bg-canvas-cream flex flex-col justify-between p-8 relative group">
46
+ <div className="absolute top-2 left-2 text-ink/30 text-[10px]">+</div>
47
+ <div className="absolute top-2 right-2 text-ink/30 text-[10px]">+</div>
48
+ <div className="absolute bottom-2 left-2 text-ink/30 text-[10px]">+</div>
49
+ <div className="absolute bottom-2 right-2 text-ink/30 text-[10px]">+</div>
50
 
51
+ <div className="text-[10px] text-hazard tracking-[0.2em] font-bold mb-8">PHASE_01 // INGESTION</div>
52
+ <div className="flex flex-col gap-12">
53
+ <div>
54
+ <h3 className="font-sans text-[24px] font-black uppercase text-ink tracking-tight mb-2 group-hover:text-hazard transition-colors">[ PATIENT SENSORS ]</h3>
55
+ <p className="text-[12px] text-slate uppercase leading-tight">60Hz Telemetry Stream via WebSockets</p>
56
+ </div>
57
+ <div>
58
+ <h3 className="font-sans text-[24px] font-black uppercase text-ink tracking-tight mb-2 group-hover:text-hazard transition-colors">[ CSV BATCH ]</h3>
59
+ <p className="text-[12px] text-slate uppercase leading-tight">Offline Dataset Upload for MLOps</p>
60
+ </div>
61
+ <div>
62
+ <h3 className="font-sans text-[24px] font-black uppercase text-ink tracking-tight mb-2 group-hover:text-hazard transition-colors">[ CLIENT UI ]</h3>
63
+ <p className="text-[12px] text-slate uppercase leading-tight">React 19 / Next.js 16 Edge Render</p>
64
+ </div>
65
  </div>
66
+ <div className="mt-16 text-[10px] text-slate uppercase border-t border-slate/30 pt-4">INPUT_VECTORS &gt;&gt;&gt;</div>
67
+ </div>
68
 
69
+ {/* COLUMN 2 */}
70
+ <div className="bg-canvas-cream flex flex-col justify-between p-8 relative group">
71
+ <div className="text-[10px] text-hazard tracking-[0.2em] font-bold mb-8">PHASE_02 // CORE_ROUTER</div>
72
+ <div className="flex flex-col gap-12">
73
+ <div>
74
+ <h3 className="font-sans text-[24px] font-black uppercase text-ink tracking-tight mb-2 group-hover:text-hazard transition-colors">[ FASTAPI SERVER ]</h3>
75
+ <p className="text-[12px] text-slate uppercase leading-tight">Async Uvicorn Router & REST API</p>
76
+ </div>
77
+ <div className="border-l-4 border-hazard pl-4">
78
+ <h3 className="font-sans text-[24px] font-black uppercase text-ink tracking-tight mb-2 group-hover:text-hazard transition-colors">[ SQLITE DB ]</h3>
79
+ <p className="text-[12px] text-slate uppercase leading-tight">Aiosqlite Non-Blocking Storage</p>
80
+ </div>
81
  </div>
82
+ <div className="mt-16 text-[10px] text-slate uppercase border-t border-slate/30 pt-4">PROCESSING_BUS &gt;&gt;&gt;</div>
83
+ </div>
84
 
85
+ {/* COLUMN 3 */}
86
+ <div className="bg-canvas-cream flex flex-col justify-between p-8 relative group">
87
+ <div className="text-[10px] text-hazard tracking-[0.2em] font-bold mb-8">PHASE_03 // EDGE_INTELLIGENCE</div>
88
+ <div className="flex flex-col gap-12">
89
+ <div>
90
+ <h3 className="font-sans text-[24px] font-black uppercase text-ink tracking-tight mb-2 group-hover:text-hazard transition-colors">[ SCIKIT-LEARN ]</h3>
91
+ <p className="text-[12px] text-slate uppercase leading-tight">Soft-Voting 5-Model Ensemble</p>
92
+ </div>
93
+ <div>
94
+ <h3 className="font-sans text-[24px] font-black uppercase text-ink tracking-tight mb-2 group-hover:text-hazard transition-colors">[ AST COMPILER ]</h3>
95
+ <p className="text-[12px] text-slate uppercase leading-tight">Python-to-C Zero-Dep Transpiler</p>
96
+ </div>
97
+ <div>
98
+ <h3 className="font-sans text-[24px] font-black uppercase text-ink tracking-tight mb-2 group-hover:text-hazard transition-colors">[ ESP32 HARDWARE ]</h3>
99
+ <p className="text-[12px] text-slate uppercase leading-tight">PlatformIO INT8 Quantized Firmware</p>
100
+ </div>
101
  </div>
102
+ <div className="mt-16 text-[10px] text-slate uppercase border-t border-slate/30 pt-4">TARGET_OUTPUT ///</div>
103
  </div>
104
  </motion.div>
105
 
106
+ {/* SPECIFICATIONS */}
107
+ <motion.div initial="hidden" animate="show" variants={fadeUp} className="w-full flex flex-col gap-16">
108
 
109
+ {/* SPEC 1 */}
110
+ <div className="w-full grid grid-cols-1 md:grid-cols-4 gap-8">
111
+ <div className="md:col-span-1">
112
+ <div className="text-[10px] text-hazard font-bold tracking-[0.15em] mb-2 uppercase">SPEC_01</div>
113
+ <h2 className="text-[24px] font-sans font-black tracking-[-0.02em] uppercase text-ink leading-tight">DATA SANITIZATION & FEATURES</h2>
114
+ </div>
115
+ <div className="md:col-span-3 bg-ink p-[2px]">
116
+ <div className="bg-canvas-cream grid grid-cols-1 md:grid-cols-2 h-full gap-[2px] bg-ink">
117
+
118
+ <div className="bg-canvas-cream p-8">
119
+ <h3 className="text-[12px] font-bold text-ink tracking-[0.1em] mb-4 uppercase border-b-2 border-ink pb-2">/// Pipeline_Execution</h3>
120
+ <p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
121
+ Raw telemetry is hostile. The ingestion script aggressively drops redundant alert flags to prevent target-leakage. Broken unicode encodings from edge sensors are patched on the fly.
122
+ </p>
123
+ <pre className="text-[10px] bg-ink text-canvas-cream p-4 uppercase overflow-hidden leading-loose">
124
+ <span className="text-hazard">01</span> DROP: 'Heart Rate Alert'<br/>
125
+ <span className="text-hazard">02</span> PATCH: '\ufffd' -&gt; '°'<br/>
126
+ <span className="text-hazard">03</span> ENCODE: LabelEncoder(Disease)
127
+ </pre>
128
  </div>
129
+
130
+ <div className="bg-canvas-cream p-8">
131
+ <h3 className="text-[12px] font-bold text-ink tracking-[0.1em] mb-4 uppercase border-b-2 border-ink pb-2">/// Feature_Synthesis</h3>
132
+ <p className="text-[13px] text-slate leading-relaxed uppercase mb-6">
133
+ Missing sensor drops are repaired using a fitted <code className="bg-ink text-white px-1">SimpleImputer</code>. We inject a non-linear hint directly into the AST: <code className="bg-ink text-white px-1">Risk_Severity</code>. All floats are strictly normalized.
134
+ </p>
135
+ <pre className="text-[10px] bg-ink text-canvas-cream p-4 uppercase overflow-hidden leading-loose">
136
+ <span className="text-hazard">&gt;</span> Risk_Sev = (HR&gt;105) + (SpO2&lt;94)<br/>
137
+ <span className="text-hazard">&gt;</span> Imputer.fit_transform(features)<br/>
138
+ <span className="text-hazard">&gt;</span> Scaler.fit_transform(features)
139
+ </pre>
140
  </div>
141
+
142
  </div>
143
  </div>
144
+ </div>
145
 
146
+ {/* SPEC 2 */}
147
+ <div className="w-full grid grid-cols-1 md:grid-cols-4 gap-8">
148
+ <div className="md:col-span-1">
149
+ <div className="text-[10px] text-hazard font-bold tracking-[0.15em] mb-2 uppercase">SPEC_02</div>
150
+ <h2 className="text-[24px] font-sans font-black tracking-[-0.02em] uppercase text-ink leading-tight">ENSEMBLE & AUTOMATED MLOPS</h2>
151
+ </div>
152
+ <div className="md:col-span-3 bg-white border-4 border-ink p-8 relative">
153
+ <div className="absolute top-0 right-8 w-8 h-full bg-[repeating-linear-gradient(45deg,transparent,transparent_4px,rgba(230,25,25,0.1)_4px,rgba(230,25,25,0.1)_8px)]" />
154
+ <p className="text-[14px] text-slate leading-relaxed uppercase mb-8 max-w-2xl relative z-10">
155
  Single-model inference is clinically insufficient. HeartFlow_OS aggregates probability distributions from 5 disparate algorithms: <strong>KNN, SVM, LogReg, Random Forest, and a Neural Network (MLP)</strong>.
156
  <br/><br/>
157
+ When a clinician uploads a new batch dataset, the API spawns a background thread. It retrains all 5 models, calculates the weighted F1-Score against a 20% holdout, and checks <code className="bg-ink text-white px-1">registry.json</code>. If the new score degrades, the batch is destroyed. If it improves, the active `.pkl` weights are hot-swapped globally without dropping WebSocket streams.
158
  </p>
159
+
160
+ <div className="inline-flex flex-col md:flex-row border-2 border-ink bg-ink gap-[2px] relative z-10">
161
+ <div className="bg-canvas-cream text-ink px-6 py-4 font-bold text-[12px] flex items-center justify-center">
162
+ F1 SCORE &gt; ACTIVE_V1
163
+ </div>
164
+ <div className="bg-hazard text-white px-6 py-4 font-bold text-[12px] flex items-center justify-center">
165
+ HOT_SWAP_MODELS()
166
+ </div>
167
  </div>
168
  </div>
169
+ </div>
170
 
171
  </motion.div>
172
+ </div>
173
  </main>
174
  );
175
  }
frontend/src/app/layout.tsx CHANGED
@@ -56,10 +56,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
56
  © 2026 HEARTFLOW_OS. TELEMETRY_SYS.
57
  </div>
58
  <div className="flex flex-wrap items-center justify-center gap-4 md:gap-8">
59
- <a href="https://huggingface.co/spaces/IDKHowToCodeFR/HEARTFLOW_OS" target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-[0.05em] uppercase text-ink hover:text-hazard transition-colors">HUGGING_FACE</a>
60
- <a href="https://idkhowtocodefr.github.io/HEARTFLOW_OS" target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-[0.05em] uppercase text-ink hover:text-hazard transition-colors">LIVE_APP</a>
61
  <Link href="/architecture" className="text-[12px] font-bold tracking-[0.05em] uppercase text-ink hover:text-hazard transition-colors">ARCHITECTURE.MD</Link>
62
- <a href={`${process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"}/docs`} target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-[0.05em] uppercase text-ink hover:text-hazard transition-colors">API_DOCS</a>
63
  <a href="https://github.com/IDKHowToCodeFR/HEARTFLOW_OS" target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-[0.05em] uppercase text-ink hover:text-hazard transition-colors">GITHUB_REPO</a>
64
  </div>
65
  </div>
 
56
  © 2026 HEARTFLOW_OS. TELEMETRY_SYS.
57
  </div>
58
  <div className="flex flex-wrap items-center justify-center gap-4 md:gap-8">
 
 
59
  <Link href="/architecture" className="text-[12px] font-bold tracking-[0.05em] uppercase text-ink hover:text-hazard transition-colors">ARCHITECTURE.MD</Link>
60
+ <a href={`${process.env.NEXT_PUBLIC_API_URL || "https://idkhowtocodefr-tinyml-backend.hf.space"}/docs`} target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-[0.05em] uppercase text-ink hover:text-hazard transition-colors">API_DOCS</a>
61
  <a href="https://github.com/IDKHowToCodeFR/HEARTFLOW_OS" target="_blank" rel="noopener noreferrer" className="text-[12px] font-bold tracking-[0.05em] uppercase text-ink hover:text-hazard transition-colors">GITHUB_REPO</a>
62
  </div>
63
  </div>