# akumascoffin/nswpsn-public-safety architecture

> Architecture diagram of the GitHub repository akumascoffin/nswpsn-public-safety, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-07-26). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/akumascoffin/nswpsn-public-safety
- Repository: https://github.com/akumascoffin/nswpsn-public-safety

## Overview

#### Purpose and Entry Points
NSW PSN is a real-time NSW public-safety monitoring platform: it aggregates emergency, weather, traffic, utility, aviation, marine, pager, scanner, and community incident data for static map/dashboard pages and Discord alerts. The current backend implementation is TypeScript/Node, despite stale README references to Flask.

Public entry pages are static root HTML such as `index.html`, `map.html`, `live.html`, `logs.html`, and `dashboard.html`. Backend startup is `backends/node/src/index.ts` and `backends/node/src/server.ts`. The Discord worker starts at `discord-bot/bot.py`. Distributed radio feeder software starts at `feeder-nodes/radio-node/cmd/nodeagent/main.go`.

#### Static Client and Identity Boundary
The frontend is build-free HTML/JS/CSS, with Leaflet-oriented map/editor experiences and client-side API calls. Shared client auth uses Supabase, while management access is mediated through backend Discord OAuth/session APIs.

Key paths: `auth-common.js`, `map-editor-module.js`, `analytics.js`. Frontend configuration is deployment-local (`config.sample.js` template); the static host is also a security/routing boundary via `.htaccess`.

#### Node API and Live Aggregation
The Node service exposes HTTP APIs for source data, maps, history, dashboard administration, incidents, status, and feeder operations. Route modules are split by domain; `server.ts` is the composition boundary between transport and application services.

Key paths: `backends/node/src/server.ts`, `backends/node/src/api/status.ts`, `backends/node/src/api/dashboard.ts`. APIs serve live/current data and operational controls while applying API-key, ingest-key, node-token, role, or Supabase JWT authentication as appropriate.

#### Source Polling, Normalization, and Caching
Source adapters obtain upstream feeds and normalize them into the platform’s source registry. A poller coordinates refresh cycles; source health and stale-while-revalidate caching isolate consumers from upstream failures. Waze differs: browsers running a userscript post regional payloads to an authenticated ingest endpoint rather than being scraped server-side.

Key paths: `backends/node/src/services/poller.ts`, `backends/node/src/services/sourceRegistry.ts`, `backends/node/src/sources/registerAll.ts`; Waze ingress: `backends/node/src/api/waze-ingest.ts`.

#### Live, Archive, and Analytics Data Stores
The service separates current in-memory/live state from persistent historical archives and derived filter/statistical caches. PostgreSQL is the durable backend store, initialized and evolved through SQL migrations. Archive “latest” sidecar/backfill/recompute services support efficient current-versus-history queries.

Key paths: `backends/node/src/store/live.ts`, `backends/node/src/store/archive.ts`, `backends/node/src/db/migrate.ts`. Schema history is under `backends/node/src/db/migrations/`, including incident, archive, dashboard, and feeder-node tables.

#### Incident, Scanner, and Alert Processing
Pager/radio and user incidents flow through domain APIs and services; scanner transcripts can be validated, quality-checked, summarized with an LLM, and converted into incident alerts. Images and Central Watch browser capture are handled as backend integrations rather than frontend responsibilities.

Key paths: `backends/node/src/api/incidents.ts`, `backends/node/src/services/rdioIncidentAlerts.ts`, `backends/node/src/services/llm.ts`. Browser-backed external integrations include `backends/node/src/services/centralwatchBrowser.ts` and `backends/node/src/services/marinetrafficBrowser.ts`.

#### Discord and Management Plane
The Python Discord bot polls platform data, resolves channel/preset subscriptions and mute/filter rules, and sends Discord messages. The Node dashboard APIs share a bot-data PostgreSQL database for dashboard state and queued actions; Discord OAuth links the web management UI to Discord identity.

Key paths: `discord-bot/bot.py`, `discord-bot/alert_poller.py`, `discord-bot/database.py`; backend-side integration: `backends/node/src/services/discordApi.ts` and `backends/node/src/services/discordOauth.ts`.

#### Feeder-Node Control Plane and Runtime
Remote radio nodes run a Go agent that maintains a WebSocket connection to the hub, relays scanner-related data, receives signed configuration updates, supervises local decoder processes, and supports self-update. The backend owns node registration, protocol, configuration merge/push, and WebSocket endpoints.

Key paths: `feeder-nodes/radio-node/internal/wsclient/client.go`, `feeder-nodes/radio-node/internal/supervise/supervisor.go`, `backends/node/src/services/nodes/hub.ts`. Material infrastructure includes Node.js, PostgreSQL, Python/Discord, Go feeder agents, Supabase authentication, Discord APIs, external government/community feeds, browser automation, and PM2 deployment configuration (`backends/ecosystem.config.js`).

Main entry points: [`backends/node/src/server.ts`](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/server.ts) (Node API server), [`backends/node/src/api/dashboard.ts`](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/api/dashboard.ts) (Status & dashboard APIs), [`backends/node/src/services/poller.ts`](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/poller.ts) (Poller & source registry), [`index.html`](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/index.html) (Public static pages)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_client["Static client"]
  node_public_pages["Public static pages<br/>HTML/JS client<br/>[index.html]"]
  node_client_auth["Client auth<br/>Supabase client<br/>[auth-common.js]"]
  node_map_editor["Map editor<br/>Leaflet module"]
  node_static_host["Static host boundary<br/>routing/security<br/>[.htaccess]"]
end

subgraph group_node["Node platform"]
  node_node_server["Node API server<br/>TypeScript HTTP service<br/>[server.ts]"]
  node_status_dashboard_api["Status &amp; dashboard APIs<br/>route modules<br/>[dashboard.ts]"]
  node_incident_api["Incident API<br/>route module<br/>[incidents.ts]"]
  node_waze_ingest["Waze ingest<br/>authenticated endpoint<br/>[waze-ingest.ts]"]
  node_poller_registry["Poller &amp; source registry<br/>aggregation service<br/>[poller.ts]"]
  node_live_store[("Live state<br/>in-memory store<br/>[live.ts]")]
  node_archive_store["Archive service<br/>history store<br/>[archive.ts]"]
  node_postgres[("PostgreSQL<br/>durable database<br/>[migrate.ts]")]
  node_scanner_alerts["Scanner alert pipeline<br/>incident service"]
  node_node_hub["Feeder node hub<br/>WebSocket control service<br/>[hub.ts]"]
end

subgraph group_discord["Discord plane"]
  node_discord_bot["Discord bot<br/>Python worker<br/>[bot.py]"]
  node_bot_alert_poller["Alert poller<br/>bot worker<br/>[alert_poller.py]"]
  node_discord_identity["Discord OAuth &amp; API<br/>integration service<br/>[discordOauth.ts]"]
end

subgraph group_feeder["Feeder runtime"]
  node_radio_agent["Radio node agent<br/>Go runtime<br/>[main.go]"]
  node_agent_supervisor["Decoder supervisor<br/>local process control<br/>[supervisor.go]"]
end

subgraph group_external["External systems"]
  node_supabase[("Supabase<br/>identity provider")]
  node_upstream_feeds["Emergency &amp; transport feeds<br/>external data sources"]
  node_browser_integrations["Browser integrations<br/>automation sources"]
  node_discord_api["Discord APIs<br/>external platform"]
end

node_static_host -->|"serves"| node_public_pages
node_public_pages -->|"uses"| node_client_auth
node_client_auth -->|"authenticates with"| node_supabase
node_public_pages -->|"API calls"| node_node_server
node_map_editor -->|"map APIs"| node_node_server
node_node_server -->|"routes to"| node_status_dashboard_api
node_node_server -->|"routes to"| node_incident_api
node_node_server -->|"routes to"| node_waze_ingest
node_node_server -->|"hosts"| node_node_hub
node_status_dashboard_api -->|"reads current state"| node_live_store
node_status_dashboard_api -->|"manages dashboard state"| node_postgres
node_incident_api -->|"submits incidents"| node_scanner_alerts
node_scanner_alerts -->|"records alerts"| node_archive_store
node_poller_registry -->|"polls"| node_upstream_feeds
node_browser_integrations -->|"supplies captures"| node_poller_registry
node_waze_ingest -->|"supplies regional data"| node_poller_registry
node_poller_registry -->|"updates"| node_live_store
node_live_store -->|"archives"| node_archive_store
node_archive_store -->|"persists history"| node_postgres
node_discord_bot -->|"runs"| node_bot_alert_poller
node_bot_alert_poller -->|"reads subscriptions"| node_postgres
node_bot_alert_poller -->|"polls platform data"| node_node_server
node_discord_bot -->|"sends messages"| node_discord_api
node_discord_identity -->|"OAuth and actions"| node_discord_api
node_status_dashboard_api -->|"management identity"| node_discord_identity
node_radio_agent -->|"WebSocket connection"| node_node_hub
node_node_hub -->|"signed config"| node_radio_agent
node_radio_agent -->|"supervises"| node_agent_supervisor

click node_public_pages "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/index.html"
click node_client_auth "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/auth-common.js"
click node_map_editor "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/map-editor-module.js"
click node_static_host "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/.htaccess"
click node_node_server "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/server.ts"
click node_status_dashboard_api "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/api/dashboard.ts"
click node_incident_api "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/api/incidents.ts"
click node_waze_ingest "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/api/waze-ingest.ts"
click node_poller_registry "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/poller.ts"
click node_live_store "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/store/live.ts"
click node_archive_store "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/store/archive.ts"
click node_postgres "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/db/migrate.ts"
click node_scanner_alerts "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/rdioIncidentAlerts.ts"
click node_node_hub "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/nodes/hub.ts"
click node_discord_bot "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/discord-bot/bot.py"
click node_bot_alert_poller "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/discord-bot/alert_poller.py"
click node_discord_identity "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/discordOauth.ts"
click node_radio_agent "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/feeder-nodes/radio-node/cmd/nodeagent/main.go"
click node_agent_supervisor "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/feeder-nodes/radio-node/internal/supervise/supervisor.go"
click node_browser_integrations "https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/centralwatchBrowser.ts"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_public_pages,node_client_auth,node_map_editor,node_static_host toneBlue
class node_node_server,node_status_dashboard_api,node_incident_api,node_waze_ingest,node_poller_registry,node_live_store,node_archive_store,node_postgres,node_scanner_alerts,node_node_hub toneAmber
class node_discord_bot,node_bot_alert_poller,node_discord_identity toneMint
class node_radio_agent,node_agent_supervisor toneRose
class node_supabase,node_upstream_feeds,node_browser_integrations,node_discord_api toneIndigo
```

## Components

### Static client

Build-free public maps, dashboards, and management UI.

- [Public static pages](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/index.html): HTML/JS client · Maps, live views, logs, and dashboards call platform APIs. · `index.html`
- [Client auth](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/auth-common.js): Supabase client · Shared browser authentication for protected client features. · `auth-common.js`
- [Map editor](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/map-editor-module.js): Leaflet module · `map-editor-module.js`
- [Static host boundary](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/.htaccess): routing/security · Serves static assets and applies host-level access rules. · `.htaccess`

### Node platform

HTTP API, live aggregation, persistence, and control planes.

- [Node API server](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/server.ts): TypeScript HTTP service · Composes routes, application services, and authentication boundaries. · `backends/node/src/server.ts`
- [Status & dashboard APIs](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/api/dashboard.ts): route modules · Serves operational status and dashboard administration. · `backends/node/src/api/dashboard.ts`
- [Incident API](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/api/incidents.ts): route module · Accepts and exposes pager, radio, and user incidents. · `backends/node/src/api/incidents.ts`
- [Waze ingest](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/api/waze-ingest.ts): authenticated endpoint · Receives regional payloads from browser userscripts. · `backends/node/src/api/waze-ingest.ts`
- [Poller & source registry](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/poller.ts): aggregation service · Refreshes, normalizes, caches, and tracks health of upstream sources. · `backends/node/src/services/poller.ts`
- [Live state](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/store/live.ts): in-memory store · Holds current source and operational state. · `backends/node/src/store/live.ts`
- [Archive service](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/store/archive.ts): history store · Manages historical queries plus latest/backfill/recompute sidecars. · `backends/node/src/store/archive.ts`
- [PostgreSQL](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/db/migrate.ts): durable database · Stores archives, incidents, dashboards, feeder records, and bot state. · `backends/node/src/db/migrate.ts`
- [Scanner alert pipeline](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/rdioIncidentAlerts.ts): incident service · Validates transcripts, applies quality checks, summarizes, and emits alerts. · `backends/node/src/services/rdioIncidentAlerts.ts`
- [Feeder node hub](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/nodes/hub.ts): WebSocket control service · Registers nodes and pushes merged signed configuration. · `backends/node/src/services/nodes/hub.ts`

### Discord plane

Alert delivery and Discord-linked management.

- [Discord bot](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/discord-bot/bot.py): Python worker · Polls platform data and delivers subscription-filtered alerts. · `discord-bot/bot.py`
- [Alert poller](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/discord-bot/alert_poller.py): bot worker · Resolves subscriptions, mute rules, and outbound alerts. · `discord-bot/alert_poller.py`
- [Discord OAuth & API](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/discordOauth.ts): integration service · Links dashboard users to Discord identity and actions. · `backends/node/src/services/discordOauth.ts`

### Feeder runtime

Remote radio-node connection and local decoder supervision.

- [Radio node agent](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/feeder-nodes/radio-node/cmd/nodeagent/main.go): Go runtime · Maintains hub connection and relays scanner-related data. · `feeder-nodes/radio-node/cmd/nodeagent/main.go`
- [Decoder supervisor](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/feeder-nodes/radio-node/internal/supervise/supervisor.go): local process control · Supervises decoder processes and supports node self-update. · `feeder-nodes/radio-node/internal/supervise/supervisor.go`

### External systems

Identity providers, upstream feeds, and browser-backed sources.

- **Supabase**: identity provider
- **Emergency & transport feeds**: external data sources · Government, community, weather, traffic, aviation, marine, pager, and scanner inputs.
- [Browser integrations](https://github.com/akumascoffin/nswpsn-public-safety/blob/main/backends/node/src/services/centralwatchBrowser.ts): automation sources · Captures Central Watch and MarineTraffic data outside the frontend. · `backends/node/src/services/centralwatchBrowser.ts`
- **Discord APIs**: external platform

## Connections

- Static host boundary → Public static pages: serves
- Public static pages → Client auth: uses
- Client auth → Supabase: authenticates with
- Public static pages → Node API server: API calls
- Map editor → Node API server: map APIs
- Node API server → Status & dashboard APIs: routes to
- Node API server → Incident API: routes to
- Node API server → Waze ingest: routes to
- Node API server → Feeder node hub: hosts
- Status & dashboard APIs → Live state: reads current state
- Status & dashboard APIs → PostgreSQL: manages dashboard state
- Incident API → Scanner alert pipeline: submits incidents
- Scanner alert pipeline → Archive service: records alerts
- Poller & source registry → Emergency & transport feeds: polls
- Browser integrations → Poller & source registry: supplies captures
- Waze ingest → Poller & source registry: supplies regional data
- Poller & source registry → Live state: updates
- Live state → Archive service: archives
- Archive service → PostgreSQL: persists history
- Discord bot → Alert poller: runs
- Alert poller → PostgreSQL: reads subscriptions
- Alert poller → Node API server: polls platform data
- Discord bot → Discord APIs: sends messages
- Discord OAuth & API → Discord APIs: OAuth and actions
- Status & dashboard APIs → Discord OAuth & API: management identity
- Radio node agent → Feeder node hub: WebSocket connection
- Feeder node hub → Radio node agent: signed config
- Radio node agent → Decoder supervisor: supervises
