HemaTwin is a Digital Twin pipeline that combines:
- Real-time BioGears telemetry (physiology from the engine)
- Stochastic hematology analytics (hemolysis, hemoglobin, risk)
- A mission dashboard that explains EVA readiness in live terms
This document explains the implementation in code terms, especially Monte Carlo and hemoglobin dynamics.
- File: shared_data/engine_listener.py
- Purpose:
- start BioGears scenario runs
- find correct run output CSV
- continuously mirror output to shared_data/SpaceAnemiaResults.csv
- expose engine status and twin sync metadata
- File: web/main.py
- Purpose:
- serve dashboard
- trigger simulation with dynamic scenario generation
- parse mirrored telemetry safely while file is still growing
- expose stochastic analytics endpoints
- File: shared_data/stochastic_generator.py
- Purpose:
- generate hourly hemolysis and EPO response
- simulate hemoglobin progression
- run Monte Carlo risk bands
- run sensitivity and recovery branches
- generate risk conclusions
- File: web/index.html
- Purpose:
- gather controls (anemia, exercise, scenario profile)
- poll telemetry and twin state
- keep hemoglobin display synchronized with mission progress
- render readiness, grounding ETA, and reason strings
- render analytics charts/panels
- User clicks INITIATE BIO-SIM.
- Frontend calls /trigger_simulation with dynamic=true and current controls.
- web/main.py creates a scenario file from slider values.
- engine_listener.py launches bg-cli with that scenario.
- While bg-cli is running, engine_listener.py mirrors fresh rows to shared_data/SpaceAnemiaResults.csv.
- Frontend polls /telemetry and /twin_state on intervals.
- Frontend maps current simulation time to current stochastic timeline hour and updates Hb live.
- EVA status, grounding estimate, and explanation text refresh continuously.
- RUN ANALYTICS calls sensitivity, monte_carlo, recovery, conclusions and updates all analytics panels.
-
_clamp(v, lo, hi)
- utility to keep numeric inputs in valid bounds.
-
_build_dynamic_scenario(base_scenario, anemia_severity, exercise_intensity)
- builds a temporary scenario XML in /shared_data.
- transforms control values into:
- anemia reduction
- exercise phase intensities
- acute stress severity phases
- anemia reduction is capped for BioGears compatibility.
-
GET /
- serves dashboard HTML.
-
GET /trigger_simulation
- builds dynamic scenario if requested.
- calls engine /start_engine with resolved scenario filename.
- returns requested_scenario and resolved_scenario for UI traceability.
-
GET /engine_status
- proxies engine state (running, done, error, csv path).
-
GET /twin_state
- proxies twin metadata, including last sync and source/destination CSV path.
- GET /telemetry
- reads mirrored CSV with defensive parsing.
- strips unit row, converts numeric fields, normalizes SpO2 to percent.
- returns row_count and parsed records.
- handles waiting states when file is missing, empty, or mid-write.
-
GET /hemolysis_profile
- single stochastic timeline run with current controls.
-
GET /monte_carlo
- percentile bands and grounding risk.
-
GET /sensitivity
- nominal vs high-stress comparison.
-
GET /recovery
- intervention branch simulation.
-
GET /conclusions
- narrative conclusions from Monte Carlo risk outputs.
-
_is_biogears_telemetry_csv(path)
- checks for Time, HeartRate, OxygenSaturation columns.
- prevents accidental use of analytics CSVs as live telemetry.
-
find_output_csv(search_roots, started_at)
- searches known BioGears output patterns.
- keeps only files newer than run start.
- uses telemetry header validation before selecting.
-
mirror_csv_once(csv_path)
- copies source CSV to shared_data/SpaceAnemiaResults.csv.
- updates sync timestamp and source path in sim_state.
-
stream_mirror_while_running(process, search_roots, started_at)
- loops while process is alive, mirrors on mtime change.
- does final flush pass after process exit.
- run_biogears_cli(scenario_file)
- validates scenario exists.
- starts bg-cli scenario run.
- mirrors output live.
- writes final status/error into sim_state.
-
POST /start_engine
- starts run in background task.
-
GET /engine_status
- returns sim_state snapshot.
-
GET /twin_state
- returns Digital Twin metadata payload.
This file is the core of Monte Carlo and hemoglobin modeling.
- _resolve_profile(profile_name, anemia_severity, exercise_intensity)
- starts from nominal or high_stress preset.
- modifies key parameters using severity/exercise:
- hemolysis_mean
- epo_suppression
- spike frequency and magnitudes
-
_generate_hemolysis_rate(hours, params, rng)
- hourly hemolysis pressure sampled from lognormal distribution.
-
_inject_events(hr_series, params, hours, rng)
- injects:
- acute hemolytic spikes (Poisson-distributed start times)
- EVA exertion windows at fixed mission times
- returns event metadata used by UI timeline.
- injects:
-
_generate_epo_response(hours, hr_series, params, rng)
- baseline normal noise around suppressed EPO mean.
- stress-coupled increment from hr_series.
At each hour i > 0:
hb_next = hb_prev - rbc_loss + epo_recovery + baseline_recovery (+ restorative_pull when low)
Where:
- rbc_loss = hr_series[i] * rbc_volume_factor
- epo_recovery = epo_series[i] * epo_recovery_factor
- rbc_volume_factor = (65 + 95severity + 45exercise) * depletion_scale
- epo_recovery_factor = 0.50 - 0.10*severity
- baseline_recovery = 0.14 + 0.20*(1-severity)
Implementation details:
- floor and ceiling are applied to keep Hb in physiological bounds.
- if previous Hb is low, a small restorative pull is added to avoid unrealistic collapse in mild cases.
-
_compute_readiness_status(hb_series, hr_series)
- labels each hour GREEN/YELLOW/RED using modeled thresholds.
-
_predict_grounding_day(hb_series)
- if already below grounded threshold, grounding is immediate.
- otherwise slope of last window is used to estimate days/hours to grounding.
- simulate_profile(...)
- executes all steps above for one timeline.
- returns:
- timeline list (hourly hb, hr, epo, readiness)
- events list
- grounding prediction
- summary stats
Function: run_monte_carlo(...)
Algorithm:
- Choose number of runs n (minimum safety floor is enforced).
- For run index i:
- call simulate_profile with deterministic seed 1000 + i
- extract hemoglobin timeline into hb_runs[i, :]
- mark grounded_by_day2 if any Hb < threshold in first 48 hours
- mark grounded_by_day3 if any Hb < threshold in first 72 hours
- Across hb_runs, compute hourly percentiles:
- P5, P50, P95
- Convert grounding booleans to probabilities:
- grounding_probability_day2 = mean(grounded_by_day2)
- grounding_probability_day3 = mean(grounded_by_day3)
- Return:
- bands: hour-wise hb_p5, hb_p50, hb_p95
- risk: day2/day3 grounding probabilities
Why this is useful:
- captures uncertainty envelope instead of only one deterministic line.
- P50 shows expected trajectory, P5 shows low-tail risk trajectory.
- run_sensitivity_analysis(...)
- runs Monte Carlo on:
- Nominal profile with current controls
- High-Stress profile with stressed offsets
- returns two median trajectories and risks for side-by-side comparison.
- runs Monte Carlo on:
- simulate_recovery(...)
- simulates three interventions:
- Rest
- IronSupplement
- EPOTherapy
- each branch updates Hb and HR each hour using branch-specific gains.
- outputs time_to_green_hours and final state metrics.
- simulates three interventions:
- generate_conclusions(monte_carlo_output)
- derives mission text from risk and percentile bands.
- statements are tail-aware, not only median-aware.
- updateControlValues and getControlParams manage UI control state.
- startSimulation does:
- preloads stochastic profile for current controls
- triggers dynamic BioGears run
- starts polling loops:
- telemetry: 1s
- engine status: 2s
- twin state: 1s
In updateCharts(rows):
- Read latest engine time from telemetry row.
- Convert engine progress to stochastic timeline index:
- hourIdx = floor((simTime / 1260) * maxHour)
- Set currentHb from stochasticTimeline[hourIdx].hemoglobin_g_per_L.
- Push into hbHistory for trend-based grounding estimation.
This is the key fix that prevents static/preloaded Hb behavior.
-
computeReadiness(hr, spo2, hb)
- computes RED/YELLOW/GREEN for mission decision state.
-
updateGroundingAndReason(status, hr, spo2, hb)
- sets days to grounding using status + Hb trend.
- writes explicit reasons like:
- low SpO2
- elevated HR
- low Hb
- if no warning reasons exist, writes green-safe reason.
- refreshAdvancedAnalytics runs four endpoints in parallel:
- /sensitivity
- /monte_carlo
- /recovery
- /conclusions
- renderSensitivity, renderMonteCarlo, renderRecovery, renderConclusions update dedicated panels/charts.
- Depletion slider was intentionally removed from frontend controls.
- Backend still supports depletion_scale with default behavior for compatibility.
- Telemetry CSV selection is hardened to avoid non-BioGears CSV contamination.
- Frontend no longer auto-loads profile on page load to avoid misleading pre-run visuals.
- Twin-state metadata is surfaced to UI for transparency of sync health.
- shared_data/engine_listener.py
- web/main.py
- shared_data/stochastic_generator.py
- web/index.html
- README.md
- .gitignore
- Start services with docker compose.
- Open dashboard at localhost:5000.
- Start a simulation.
- Confirm telemetry values increment while run is active.
- Confirm Hb changes over mission progress (not fixed).
- Confirm RED/YELLOW/GREEN reason text updates.
- Click RUN ANALYTICS and confirm all four panels refresh.
- Confirm Monte Carlo risk text and percentile chart are populated.