Skip to content

Fire Path AI - Developer Documentation

Last reviewed against the codebase: 2026-07-25.

Private developer documentation for Fire Path AI, an Australian bushfire prediction and asset-protection system. This site is for authorised technical reviewers and developers; it is access-controlled (Cloudflare Access) and not search-indexed (noindex), so it can be published while sections are still being filled in.

This landing page is the 10-minute orientation: what the system is, how it is put together, where the code lives, the five mechanics you must understand before touching anything, and where to go next. It is deliberately shallow - it links into the deeper pages rather than duplicating them, so there is a single source of truth for each topic.

What it is

Fire Path AI is a situational-awareness and decision-support system for Australian bushfires. It turns live satellite fire detections, weather, terrain and a property’s own details into property-specific risk, spread estimates, time-to-impact, and alerts - for residents and landholders today, with an operations view for wider situational awareness. It complements official agency warnings; its predictions are physics-based estimates, not authoritative agency products. See Overview & architecture for the full system context, the fire-science model, and the capability-status table.

Architecture in one diagram

Mobile + Web app ──HTTPS──▶ API server ──▶ PostGIS (Postgres)
(Expo / MapLibre) (Node/Express) ▲ │
▲ │ │ └─▶ Redis queue ──▶ Wind-worker
│ │ │ (WindNinja, terrain wind)
└────── push/alerts ◀──────┤ │
├─ Ingest scheduler ─▶ FIRMS / DEA / OpenMeteo (external feeds)
└─ Alert evaluator ──▶ push (FCM/APNs) + SMS/email fallback

The client is a universal Expo app (iOS, Android, web SPA). It talks only to the API server, which owns all business logic and is the only thing that touches the PostGIS database. A scheduled ingest loop pulls external feeds; terrain-wind computation is offloaded to a separate wind-worker over a Redis/BullMQ queue so a slow WindNinja run never blocks an HTTP request. Full narrative, tiers, and external-feed detail live in Overview & architecture; the hosting topology (which of these run where, and what hibernates) is in Infrastructure.

The technology stack

LayerChoice
ClientExpo / React Native (iOS, Android, web SPA), MapLibre
APINode.js / Express, TypeScript, Drizzle ORM
DatabasePostgreSQL + PostGIS (spatial)
Queue / cacheRedis (BullMQ)
Terrain windWindNinja (containerised worker)
Package managerpnpm (workspaces monorepo)
InfraAWS ECS Fargate, ALB, RDS, ElastiCache, Terraform (IaC)
Static sitesCloudflare Pages / Workers (marketing, docs, web app shell)

Repository map

The code is a pnpm-workspaces monorepo rooted at Fire-Path-AI/ (packageManager: pnpm@11, packages under artifacts/*, lib/*, and scripts). The workspace name in the root package.json is literally workspace, and packages reference each other as @workspace/<name>.

PathWhat it is
artifacts/api-serverThe Node/Express API - routes, ingest, alert evaluator
artifacts/firepath-aiThe Expo / React Native app (iOS, Android, web SPA)
artifacts/docs-siteThis documentation site (Astro + Starlight)
artifacts/marketing-siteThe public marketing site (Astro)
lib/dbDrizzle schema + the push migration scripts (@workspace/db)
lib/api-zodZod request/response schemas shared by server + client
lib/api-specThe OpenAPI spec, derived from the Zod schemas
lib/api-client-reactTyped React Query client generated from the spec
lib/spread-modelThe McArthur-based fire-spread math
lib/web-brandShared brand tokens for the web surfaces
scriptsRepo tooling (typechecked as a workspace package)
infra/ · .github/Terraform + wake/hibernate scripts · CI workflows

The Repository guide has the full generated tree and a one-liner for every package; Where everything is maps common tasks (“I want to change a route / a table / a screen”) to the exact directory.

The five things to know

Understand these five and the rest of the docs will make sense.

1. Auth and RBAC

Every non-public route is authenticated with a JWT, and authorisation is role-based. There are four roles, defined in the schema (lib/db/src/schema/enums.ts): homeowner, agency_officer, council_admin, and system_admin. Ownership is enforced on top of roles - a homeowner can only see their own org’s assets (IDOR protection), not just “any authenticated user’s”. The auth model, the RBAC matrix, and the endpoints that are still 503 stubs (Microsoft SSO, phone-OTP) are documented in the generated API reference and the Security page.

2. The ingest → predict → alert flow

This is the core life-safety pipeline:

  1. Ingest - a scheduled, overlap-guarded loop pulls FIRMS (NASA satellite fire detections), DEA Hotspots (Digital Earth Australia), and OpenMeteo weather, writing an ingest_cycles row per source per run.
  2. Predict - a clustering pass tracks fires over time; the spread path (lib/spread-model) computes footprint + ETA; terrain wind runs asynchronously on the wind-worker via the Redis queue.
  3. Alert - an evaluator matches active fires to a user’s assets and raises alerts, delivered by push (FCM/APNs) with SMS/email fallback. A threat registry is the single writer for fire↔asset threats, so alerts can’t be double-raised by two concurrent runs.

The Operations page covers the runtime behaviour and alarms for this pipeline.

3. COLD / WARM / HOT - the cost model

Only the AWS API backend costs real money to run, so it is deliberately hibernated between work sessions. The three Cloudflare surfaces (marketing, docs, web-app shell) are static and always on for ~$0.

  • COLD - AWS torn down to ~$0; the web app loads but shows a “Service is paused” banner because api. resolves to nothing.
  • WARM / HOT - the data tier or the full stack is awake and billable.

The topology, the Terraform module map, and the exact wake/hibernate mechanics are in Infrastructure and Operations.

4. Config is env-driven, not hard-coded

There are no config literals scattered through the code. Every setting - database URL, secrets, feature flags, contact identity - is an environment variable or a Terraform variable, with a single place to change it per environment. Third-party keys (FIRMS, OAuth, Resend, Twilio) can stay blank locally; those features degrade gracefully when unset. Feature flags such as enable_data_tier, enable_compute, and dev_mode gate whole tiers. The full, generated table of every variable, its default, and whether it is required/optional is the Configuration reference.

5. CI gates keep the docs and code honest

Quality is enforced in CI, not by convention: a coverage ratchet (CI floors today ~59% statements / 50% branches / 62% functions / 61% lines, ratcheting up toward the ≥85% statements-lines / ≥90% route-integration resale target - never allowed to regress), typecheck, the test suites, and - specific to this site - the doc-drift gate (generated reference must match the source) and the redaction gate (no paths, account IDs, resource IDs, or names in the published build). CI never receives real production secrets; tests must pass with them unset. See Testing & quality and Contributing & release.

Run it in three commands

Full setup (env file, keys, device builds) is in Run it locally; the short version, from the repo root:

Terminal window
docker compose up -d postgres # Postgres/PostGIS on :5432
cd Fire-Path-AI && set -a; source .env; set +a # after: cp .env.example .env
pnpm --filter @workspace/api-server run dev # builds + starts API on :3000

Then, in a second terminal, start the app:

Terminal window
pnpm --filter @workspace/firepath-ai run web # Expo web on :8081

Verify it’s alive - the API exposes two health endpoints:

Terminal window
curl -s localhost:3000/healthz # {"status":"ok"} - liveness, dependency-free
curl -s localhost:3000/readyz # readiness - 200 only if Postgres is reachable

/healthz intentionally never touches the database (a transient DB blip must not make the orchestrator kill a healthy task); /readyz actually pings Postgres and reports ingest freshness, returning 503 when the DB is down. The full verification checklist (typecheck, vitest, health) is in Verify.

Reference (generated)

These sections are generated directly from the codebase on every build and drift-checked, so they cannot silently fall out of sync:

Where to go next