System architecture
Four services share one PostgreSQL + PostGIS database. The EO pipeline measures lakes, the API decides what becomes an alert, and the field app and this website put that alert in front of people.
Components
- CryoHealth-api ↗
- NestJS service that owns product logic: authentication and roles, alert policy, offline sync, admin, and the unauthenticated Open Data API. It is the only place schema migrations are written.
- PostgreSQL 16 + PostGIS
- The single shared database. Lake points and boundaries are stored as PostGIS geometries (SRID 4326).
- CryoHealth-geo ↗
- Python / FastAPI pipeline on a schedule. It fetches Sentinel-2 scenes, measures lake water extent with NDWI, writes observations, and posts hazard scores to the API, which stores them.
- CryoHealth-app ↗
- Expo / React Native field app. Works offline and syncs through the API's /sync endpoint when a connection is available.
- cryohealth dashboard ↗
- This website, server-rendered with TanStack Start on Cloudflare Workers, for the public, administrators, and CHW leads.
- Cloudflare tunnel
- The backend host accepts no inbound connections. Traffic reaches the API only through an outbound-only tunnel at api.cryohealth.io.
From satellite scene to alert
- CryoHealth-geo fetches new Sentinel-2 scenes for each monitored lake.
- It measures water extent with NDWI and writes a row to observations, tagged with the pipeline run ID.
- It computes a hazard score and tier and posts it, with every input, to the API, which stores it in hazard_scores (inputs in components).
- CryoHealth-api applies alert policy. Creating or overriding an alert requires a human-readable reason, recorded in the audit table.
- The field app and this website read the alert. CHWs acknowledge it, and the app syncs field cases back when online.
Design rules
- One schema authority
Migrations live only in CryoHealth-api. Every other service reads and writes tables defined there and never alters the schema.
- Policy is code and people, never silent ML
The geo service computes scores. Deciding a tier or issuing an alert happens in the API with an audited reason a person can read.
- Safety information is never gated
Open Data endpoints need no login. Every other route requires a signed token and one of four roles: cryohealth_admin, facility_admin, chw, or viewer.
- Offline first
Each case from the field app carries a client-generated ID, so repeated syncs from a device are idempotent. Every sync is logged in sync_log.
- Reproducible
Every observation and hazard score carries the run ID that produced it, and scores keep their inputs so any tier can be recomputed later.
Source documents
- CryoHealth-api/ARCHITECTURE.md ↗ — the API's module layout and the decisions summarised on this page.
- CryoHealth-geo/docs/HAZARD_METHODOLOGY.md ↗ — how hazard scores are computed: inputs, weights, and tier thresholds.
- cryohealth/docs/architecture ↗ — source for the diagram above.
The tables behind this flow are documented in Schema design.