Skip to content

harsharajkumar/FernOS

Repository files navigation

FernOS

FernOS is a crisis operating system for Nashville built around one shared live graph. Citizens use StormPath to find safe routes to warmth, food, water, and rides. Responders use CrewIQ to sequence tree crews ahead of electrical crews, prioritize circuits by vulnerability, and react to live crowd reports in the same data model.

Screenshots

Shared Entry

FernOS auth page

The shared sign-in flow routes citizens into StormPath and responders into CrewIQ from one front door.

Citizen Experience

StormPath routing Community support chat
StormPath routing view StormPath community chat

Citizens can find safer routes to food, heat, water, and rides while also opening support threads and neighborhood conversations.

Responder Experience

CrewIQ dashboard Circuit detail and dispatch
CrewIQ responder dashboard CrewIQ circuit detail

Responders get neighborhood risk scoring, likely next hotspots, pre-staging recommendations, and live circuit-level operations.

What Is Built

  • Shared backend graph for roads, resources, circuits, crews, jobs, crowd reports, needs requests, storm-watch state, chat threads, and recommendations.
  • Modified Dijkstra router with time-decaying edge weights and top-3 ranked route responses.
  • Citizen StormPath map with live road coloring, resource pins, route overlay, report modal, support-request workflow, community chat, and surplus resource posting.
  • Responder CrewIQ dashboard with live metrics, priority map, assignment flow, needs matching, job sequencing, storm-watch toggle, and actionable pre-staging recommendations.
  • Socket.io synchronization between citizen and responder views.
  • Anthropic-backed NLP classifier with heuristic fallback.
  • FastAPI ML microservice scaffold with XGBoost training and prediction endpoints.
  • Postgres/PostGIS schema, DB-backed shared graph runtime, responder account storage, and import tooling.
  • File-backed demo graph plus demo citizen and responder registries so runtime data is no longer embedded in source arrays.
  • Admin import routes and a CLI bootstrap flow for refreshing demo data without code edits.

What Was Missing Before This Pass

The repo already had a strong UI scaffold and a small in-memory responder demo, but several core files were placeholders:

  • server/src/engines/* were mostly empty.
  • server/src/db/*, auth middleware, workers, and SQL assets were blank.
  • ml-service/ files were placeholders.
  • Top-level docs, scripts, env example, and Docker compose were empty.
  • The citizen UI still depended on mock route/resource data instead of shared state.

Stack

  • Frontend: React, TypeScript, Vite, Leaflet, Socket.io client
  • Backend: Node.js, Express, Socket.io, pg
  • Database: PostgreSQL + PostGIS
  • ML service: FastAPI + XGBoost
  • NLP: Anthropic Claude API with heuristic fallback

System Design

FernOS is designed as one shared operational graph with two different operator experiences on top of it:

  • StormPath for citizens
  • CrewIQ for responders

Both experiences read from and write back into the same live state model so that road reports, resource availability, crew actions, outage status, and support requests propagate through one system instead of being split across separate tools.

1. Architecture Overview

flowchart LR
    Citizen["Citizen UI<br/>StormPath"] --> Client["React + Vite client"]
    Responder["Responder UI<br/>CrewIQ"] --> Client
    Client <-->|REST + Socket.io| API["Node.js + Express API"]
    API --> Router["Routing engine<br/>modified Dijkstra"]
    API --> NLP["NLP classifier<br/>Claude or heuristic fallback"]
    API --> Agent["Resilience agent<br/>risk scoring + hotspot logic"]
    API --> Workers["Background workers<br/>decay, scoring, prediction"]
    API --> Store["Shared graph store abstraction"]
    Store --> PG["Postgres + PostGIS"]
    Store --> Demo["Demo JSON graph fallback"]
    Workers --> ML["FastAPI ML service<br/>failure prediction"]
    ML --> API
Loading

2. Core Design Principle: The Shared Live Graph

The shared graph is the system spine. Every major workflow resolves to one or more of these entities:

  • road_segments: traversable network edges used by StormPath routing
  • resource_nodes: shelters, food banks, businesses, neighbors, and support sites
  • circuit_segments: outage and vulnerability regions for CrewIQ prioritization
  • crew_units: live responder units and current status
  • jobs: dispatch work items, including blocked tree-first and ready electrical jobs
  • crowd_reports: citizen or crew hazard signals that update route penalties
  • needs_requests: support requests for food, heat, rides, water, or medical help
  • chat_threads and chat_messages: community rooms plus private support threads
  • app_state: global flags such as storm_watch_mode

This graph exists in two interchangeable runtime backends:

  • PostgresGraphStore for Postgres/PostGIS-backed operation
  • DemoGraphStore for file-backed demo mode when DATABASE_URL is unavailable

The store manager chooses Postgres when it can bootstrap successfully, otherwise it falls back to the demo graph without changing the API contract.

3. Logical Components

Frontend

The client is a single React app with route-level experiences:

  • / shared auth entry point
  • /door role-based launcher after citizen login
  • /citizen StormPath map, routing, reporting, support requests, and chat
  • /responder CrewIQ dashboard, dispatch, needs queue, AI brief, and outage map

Frontend state is partitioned into small stores:

  • graph store for roads, routes, resources, reports, and needs
  • responder store for circuits, crews, jobs, recommendations, and metrics
  • chat store for support threads and community rooms
  • user/session store for citizen and responder identity

Socket.io pushes shared graph updates into those stores at the app root through useSocketSync.

Backend API

Express is organized around product surfaces instead of technical layers:

  • /api/auth for citizen registration, citizen login, responder login, and session introspection
  • /api/citizen for graph reads, route computation, surplus resource creation, and support requests
  • /api/reports for crowd hazard submissions and reconfirmation
  • /api/chat for authenticated thread and message access
  • /api/responder for dashboard reads, dispatch actions, storm-watch control, and AI operations
  • /api/admin for dataset bootstrap and import flows

Shared Graph Store

Every route writes through the same GraphStore contract, which is responsible for:

  • querying the shared snapshot
  • computing routes
  • creating and matching needs requests
  • managing jobs and crews
  • mutating circuit state
  • updating road penalties
  • applying prediction results

This keeps business behavior consistent across demo mode and Postgres mode.

4. Data Model and Persistence

Postgres + PostGIS is the canonical persistence model. Geometry is stored with SRID 4326 and used for:

  • LineString road geometry
  • Point resource, crew, and report locations
  • Polygon circuit operating areas

The main persistence patterns are:

  • route edges live in road_segments
  • destinations live in resource_nodes
  • outage zones live in circuit_segments
  • assignment state lives in jobs and crew_units
  • citizen support state lives in needs_requests
  • authenticated social coordination lives in chat_threads and chat_messages

Operationally, the README schema now reflects the real runtime fields, including:

  • priority_score
  • failure_probability
  • coordinating_with_crew_id
  • blocked_by_job_id
  • support-thread linkage from needs_requests.thread_id
  • hashed citizen and responder credentials in citizen_accounts and responder_accounts

5. Realtime Synchronization Model

FernOS uses Socket.io as the live graph fan-out layer.

When a client connects:

  1. the server emits graph:snapshot
  2. the client hydrates citizen and responder stores from the same snapshot
  3. subsequent deltas arrive as entity-specific events

Primary broadcast events include:

  • road:updated
  • report:created
  • report:updated
  • resource:updated
  • needs_request:updated
  • circuit:updated
  • jobs:updated
  • crew:updated
  • dashboard:updated
  • storm_watch:updated
  • zone:cleared
  • power:restored

Chat is intentionally handled a little differently right now:

  • support and community messages are authenticated over HTTP
  • private support-thread safety is favored over broad socket fan-out
  • the client store already has socket handlers for chat events, so room-based realtime chat can be layered in later without changing the high-level shape

6. Citizen Flow Design

StormPath is built around three loops that all touch the same graph.

A. Safe routing loop

  1. citizen selects needs such as heat, food, water, or ride
  2. client sends origin, needs, and currentTime to /api/citizen/routes
  3. backend computes top routes against current road penalties and resource fit
  4. UI renders the ranked destinations and animated map route

Routing is graph-based rather than external-map-API based. The server builds an in-memory graph from road_segments and runs a modified Dijkstra search over weighted edges.

Edge weight:

base_weight + decayed_ice_penalty + decayed_debris_penalty

Decay model:

decay_factor = e^(-1.5 * age_hours)

Ranking model:

rank_score = travel_weight / max(resource_match_score, 0.25)

This means safer and shorter routes still matter, but a destination that actually satisfies the requested needs will rank better than a slightly closer but less useful destination.

B. Hazard reporting loop

  1. citizen submits a free-text or quick-tap report
  2. backend classifies the report with Claude when an API key is present
  3. if Claude is unavailable, the heuristic classifier extracts hazard, urgency, and summary
  4. urgency maps into road penalties and condition changes
  5. the updated road segment is broadcast to all connected clients

Penalty mapping:

  • urgency 1-2: minor ice penalty
  • urgency 3: larger ice penalty
  • urgency 4: heavy debris penalty
  • urgency 5: effectively blocked edge

C. Support and community loop

  1. citizen opens a support request
  2. backend creates a needs_request
  3. backend also creates a private support thread linked to that request
  4. responders can match the request to a resource and later resolve it
  5. the citizen sees status changes in the same shared graph

Parallel to that, citizens also participate in community rooms and post non-routing neighborhood updates.

7. Responder Flow Design

CrewIQ is structured around circuit-level operations, job sequencing, and daily resilience monitoring.

A. Dashboard model

The responder dashboard combines:

  • live circuit map
  • active job queue
  • crew availability
  • support-request queue
  • storm-watch recommendation state
  • AI resilience brief

The top metric cards derive from shared graph state:

  • active zones
  • available crews
  • critical jobs
  • households affected

B. Priority scoring

Circuit priority is recomputed by worker and on major workflow changes using:

priority_score =
(
  (population_affected * 0.35) +
  (vulnerability_score * 0.30) +
  (hours_without_power * 0.20) +
  (re_outage_risk * 0.15)
) / repair_complexity_estimate

This pushes vulnerable, high-population, long-duration outages upward while still discounting zones with unusually complex repairs.

C. Sequenced dispatch

The responder workflow enforces tree-first dependency handling:

  1. tree crews are assigned to blocked circuits
  2. electrical work remains blocked until the zone is cleared
  3. tree_cleared updates the circuit
  4. affected jobs move from blocked toward ready/in-progress state
  5. updated circuits, jobs, roads, and metrics are broadcast immediately

This is the design core of the product: dispatch logic and citizen routing are linked through the same live operating picture.

D. Needs matching

Responders can also operate as social-good coordinators:

  1. view unresolved needs requests
  2. match a request to the best resource node
  3. resolve after handoff is complete

This extends FernOS beyond outage repair into daily neighborhood resilience operations.

8. AI and Prediction Design

FernOS has two intelligence layers plus one rules-based operational layer.

A. NLP triage layer

Purpose:

  • classify unstructured crowd reports
  • infer urgency, hazard type, confidence, and short summary
  • translate free text into route-impacting penalties

Runtime path:

  • server/src/engines/nlpClassifier.ts
  • Anthropic Claude API when configured
  • heuristic classifier when not configured

B. ML failure prediction layer

Purpose:

  • estimate which circuits are most likely to fail during storm watch

Runtime path:

  • FastAPI service exposes /predict
  • Node prediction worker polls the ML service every 60 minutes while storm_watch_mode is enabled
  • returned failure_probability values are written back into circuit_segments
  • responder recommendations are regenerated from that updated graph

This makes failure probability part of the same dispatch view rather than a separate analytics dashboard.

C. Resilience agent layer

Purpose:

  • neighborhood risk scoring
  • likely next hotspot prediction
  • pre-staging suggestions
  • daily “what should responders do next?” recommendations

The resilience agent is not a separate model server. It is a backend intelligence layer that synthesizes:

  • active outages
  • vulnerability scores
  • route disruptions
  • crowd-report volume
  • resource strain
  • pending support requests
  • ML failure probabilities

It groups signals by neighborhood, computes a composite risk score, predicts the dominant next issue type, and proposes pre-stage actions for available crews.

9. Background Workers and Time Horizons

FernOS uses three background workers:

  • decay worker every 10 minutes
  • priority scoring worker every 5 minutes
  • prediction worker every 60 minutes

Their responsibilities are:

  • clear fully stale road penalties after the 2-hour report lifetime
  • keep circuit and job priority scores fresh as outage duration increases
  • ingest ML failure predictions during storm watch

This gives the system three time horizons:

  • immediate: websocket deltas and direct route recomputation
  • short-term: decayed road validity and priority refresh
  • medium-term: forecast-driven pre-staging

10. Authentication and Access Model

FernOS has one shared auth surface with two identity classes:

  • citizens
  • responders

Citizens:

  • register with name, email, and password
  • receive signed session tokens
  • can create needs requests and participate in chat

Responders:

  • authenticate with crew_id and responder code
  • receive signed session tokens with responder role
  • are required for CrewIQ and dispatch actions

The server supports:

  • DB-backed hashed credentials in Postgres
  • demo-file-backed credentials when running without Postgres

11. Deployment Modes

FernOS is intentionally designed to run in two modes.

Demo mode

  • no Postgres required
  • file-backed JSON graph
  • demo citizens and responders
  • fastest path for judging and hackathon demos

Postgres mode

  • PostGIS-backed shared graph
  • import/bootstrap pipeline
  • persistent auth tables
  • canonical path for a city or nonprofit pilot

Because both modes implement the same GraphStore contract, the product surface stays stable while the infrastructure matures.

12. Why This Design Fits Social Good

The system is intentionally not just a once-a-year storm app. The same architecture supports:

  • daily hazard reporting
  • mutual aid and support matching
  • community operations
  • public-works style dispatch
  • storm escalation when needed

That is the core product design choice: FernOS is useful on ordinary days, then becomes a crisis operating system without forcing the city or community to adopt a second platform.

Quick Start

  1. Export the environment variables you need from .env.example. Fill in ANTHROPIC_API_KEY if you want live NLP classification.
  2. If you want Postgres mode, start a Postgres/PostGIS instance and export DATABASE_URL.
  3. Start the backend:
    • cd server
    • npm install
    • npm run dev
  4. Start the frontend:
    • cd client
    • npm install
    • npm run dev
  5. Start the ML service:
    • cd ml-service
    • python3 -m venv .venv && source .venv/bin/activate
    • pip install -r requirements.txt
    • uvicorn main:app --reload --port 8000
  6. To bootstrap Postgres from the demo assets:
    • export DATABASE_URL=...
    • ./scripts/seed_db.sh

If DATABASE_URL is unset or Postgres is unavailable, FernOS falls back to the file-backed demo graph in server/data/demo/graph.json.

Common local URLs during development are usually:

  • Client: http://localhost:5173
  • Server: http://localhost:3001
  • ML service: http://localhost:8000

Demo auth registries now live in responders.json and citizens.json instead of being embedded in the UI.

Demo logins:

  • Citizen: maya@fernos.demo / citizen2026
  • Responder: crew_tree_001 / tree2026

Environment Variables

DATABASE_URL=postgresql://postgres:postgres@localhost:5432/fernos
ANTHROPIC_API_KEY=your_key_here
ML_SERVICE_URL=http://localhost:8000
JWT_SECRET=replace_with_a_real_secret
ADMIN_API_TOKEN=replace_with_an_admin_token
DB_AUTO_BOOTSTRAP_DEMO=true
SOCKET_PORT=3001
PORT=3001
CLIENT_ORIGINS=
RESPONDER_CREDENTIALS_PATH=
CITIZEN_CREDENTIALS_PATH=
DEMO_GRAPH_PATH=
VITE_API_BASE_URL=
VITE_SOCKET_URL=
NOAA_FORECAST_URL=
FERNOS_CIRCUIT_SEEDS_PATH=
FERNOS_TREE_CANOPY_DEFAULTS_PATH=

Deployment

The easiest way to deploy the full FernOS stack is with Render using the root render.yaml Blueprint.

Recommended Production Layout

  • fernos-client: Render Static Site for the Vite frontend
  • fernos-server: Render Web Service for Express + Socket.io
  • fernos-ml: Render Private Service for FastAPI predictions
  • fernos-db: Render Postgres database

Why Render

FernOS uses:

  • a long-running Express server
  • Socket.io realtime updates
  • a separate Python ML service
  • Postgres with PostGIS

That makes Render a better fit for the full stack than Vercel, which is better used for the frontend only.

One-Click-ish Render Deploy

  1. Push the latest main branch to GitHub.
  2. Go to Render Blueprint Setup.
  3. Click New Blueprint Instance.
  4. Connect the GitHub repo:
  5. Render will detect render.yaml automatically.
  6. Approve the 4 resources:
    • static site
    • Node web service
    • Python private service
    • Postgres database
  7. Add optional environment variables only if you want them:
    • ANTHROPIC_API_KEY on fernos-server
    • custom domain settings later if needed
  8. Click Apply.
  9. Wait for the services to finish deploying.
  10. Open the fernos-client URL first.

What The Blueprint Already Handles

  • builds the React app from client/
  • deploys the Express backend from server/
  • deploys the FastAPI ML service from ml-service/
  • provisions Postgres
  • connects DATABASE_URL from Render Postgres automatically
  • connects the backend to the ML private service automatically
  • points the frontend at the backend automatically
  • rewrites SPA routes like /citizen and /responder back to index.html
  • bootstraps demo graph data into Postgres on first startup

Post-Deploy Checklist

After Render finishes:

  1. Open the frontend URL.
  2. Confirm the health endpoint on the backend:
    • https://your-backend-url/health
  3. Log in with the demo accounts:
    • Citizen: maya@fernos.demo / citizen2026
    • Responder: crew_tree_001 / tree2026
  4. If you want Claude-backed report classification, add ANTHROPIC_API_KEY to the backend service in Render and redeploy that service.

Current Tradeoff

The Blueprint is designed to get FernOS live with the least manual work. The frontend, backend, ML service, and database all deploy from one repo, but Render account access is still required for the final Blueprint launch.

Database Assets

FernOS now ships with:

  • server/src/db/schema.sql
  • server/src/db/seed.sql
  • server/data/demo/graph.json
  • server/data/demo/responders.json
  • server/data/demo/citizens.json
  • scripts/seed_db.sh
  • scripts/reset_db.sh
  • scripts/bootstrap_demo_db.sh
  • server/src/admin/importGraphCli.ts

server/src/db/seed.sql is now a compatibility shim. The canonical bootstrap path is the JSON import pipeline, which loads the shared graph and responder registry into Postgres from the files above.

Admin Import

To import demo data directly into Postgres without editing code:

  • CLI: cd server && npm run admin:import -- --replace
  • Script: ./scripts/bootstrap_demo_db.sh
  • API: POST /api/admin/bootstrap with x-admin-token: $ADMIN_API_TOKEN

Additional admin endpoints:

  • GET /api/admin/status
  • POST /api/admin/import/graph
  • POST /api/admin/import/responders

Important Implementation Notes

  • Route ranking uses weighted path cost divided by resource match score. This is an intentional correction of the prompt’s ambiguous "shortest weighted path x resource relevance" wording so better resource matches rank higher instead of lower.
  • Storm watch predictions only affect recommendations when storm_watch_mode is enabled.
  • If Anthropic or the ML service is unavailable, FernOS falls back to local heuristics instead of failing closed.
  • The client now prefers same-origin API and socket connections unless VITE_API_BASE_URL or VITE_SOCKET_URL is explicitly set.
  • Responder auth uses hashed codes in Postgres when DATABASE_URL is configured, and falls back to the file registry only when FernOS is running in demo mode.
  • Citizen auth now uses the same shared auth entry point as responders, with hashed passwords in Postgres or the demo registry.
  • Community chat is authenticated and fetched over HTTP so private support threads do not get broadcast to every socket client.

Verification

  • cd server && npm run build
  • cd client && npm run build
  • cd ml-service && python3 -m compileall .
  • Demo-mode smoke checks:
    • GET /health
    • GET /api/citizen/graph
    • POST /api/auth/login

FernOS

About

FernOS is a crisis operating system for Nashville built around one shared live graph. Citizens use StormPath to find safe routes to warmth, food, water, and rides. Responders use CrewIQ to sequence tree crews ahead of electrical crews, prioritize circuits by vulnerability, and react to live crowd reports in the same data model.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors