# Kansas City hail alerts

`org/apps/hail-alerts/` handles the first StormTrust hail-notification use case. It evaluates National Weather Service (NWS) warnings for the Missouri side of the Kansas City metro. It explains decisions, maps warnings, and prepares team email.

The application remains isolated from the command-center UI. It does not change existing frontend or backend contracts.

For configured cadence, queue retries, persistence checkpoints, and recovery limits, see [Polling and triggers](/stormtrust/product/platform-and-data/polling-and-triggers/).

## Application reference

| Property          | Value                                                    |
| ----------------- | -------------------------------------------------------- |
| Production URL    | https://hail.stormtrust.net/                             |
| Local URL         | http://127.0.0.1:8791/                                   |
| Source            | org/apps/hail-alerts/                                    |
| Runtime           | Native WorkerEntrypoint and SQLite-backed Durable Object |
| Frontend          | Vue through the Cloudflare Vite plugin and Static Assets |
| Active source     | NWS active Missouri Severe Thunderstorm Warnings         |
| Historical source | Rolling seven-day Missouri Severe Thunderstorm Warnings  |
| Market            | Missouri warnings intersecting Census CBSA 28140         |
| Access            | Exact-email Cloudflare Access policy                     |
| Verification      | make app-verify APP=hail-alerts                          |

## Runtime flow

```txt
NWS active feed, history, or labeled simulation
                    ↓
Bounded GeoJSON normalization
                    ↓
Kansas City market intersection
                    ↓
VTEC warning-chain reconciliation
                    ↓
Observed-hail qualification and escalation
                    ↓
Decision ledger, warning map, and message preview
                    ↓
Bounded Cloudflare Email Sending delivery
```

Terraform owns the Worker identity and Access resources. Wrangler owns the Worker deployment, Custom Domain, Static Assets, Durable Object migration, cron, Email Sending binding, and observability.

## Qualification rules

A revision qualifies when it is an Actual, Public Severe Thunderstorm Warning. It must intersect the market and contain a valid VTEC identity. It must also include `hailThreat=OBSERVED` and a positive ` maxHailSize`.

There is no minimum hail-size floor. Radar-indicated hail does not qualify. A later revision escalates after a 0.25-inch increase from the delivered size.

The ledger explains every notification, escalation, duplicate, rejection, stale revision, and warning end. A newer unsent revision supersedes the older message. Warning cancellation withdraws an unsent message.

The map treats its external basemap and StormTrust operational geometry as separate layers. If the basemap cannot load, the application switches to a local fallback style and continues showing the Kansas City boundary and NWS warning geometry with a visible degraded-state notice.

The map uses the same production runtime boundary as the existing StormTrust maps. Response headers explicitly allow OpenFreeMap tile requests, while both MapLibre module-worker files are deployed as immutable same-origin assets. The Access-protected hostname does not register a service worker or web manifest; those browser-managed requests can otherwise be redirected to the Access login origin instead of returning the required asset type.

## Delivery controls

Cloudflare Access protects the complete production hostname. Terraform limits Access to named operators. The Email Sending binding independently limits recipients and sender addresses.

HTTP requests cannot provide recipients. State-changing browser requests must be same-origin. A generated deployment token protects manual submission. Daily caps permit 100 live attempts and 10 test attempts.

Only a scheduled live poll may request automatic delivery. Browser polling cannot. Replay always requires explicit confirmation and includes `[TEST REPLAY]` in the subject.

Production begins in shadow mode. The Worker polls and records real NWS data, but it does not send unattended email. Enable scheduled delivery only after Access and a controlled replay email pass verification.

## Run locally

Use Node.js 24 or newer. From `org/apps/hail-alerts/`:

```sh
pnpm install --frozen-lockfile
pnpm dev
```

Open `http://127.0.0.1:8791/`. Select **Replay**, then **Simulation**, to test the complete journey. Local email remains simulated and needs no Cloudflare credential.

## API boundary

| Route                                    | Purpose                           |
| ---------------------------------------- | --------------------------------- |
| GET /api/health                          | Worker health                     |
| GET /api/market                          | Kansas City market geometry       |
| GET /api/state?mode=live                 | Live source and decision state    |
| GET /api/state?mode=replay               | Isolated replay state             |
| POST /api/poll                           | Live NWS evaluation               |
| POST /api/replay                         | Seven-day or deterministic replay |
| POST /api/notifications/:decisionId/send | One confirmed eligible delivery   |

NWS requests allow only `api.weather.gov`. They time out after 12 seconds and reject responses above 8 MiB or 1,000 features. Active polling uses ETags. Affected-zone fallback permits up to 20 zones per warning.

For Worker Custom Domain behavior, review the [Cloudflare Custom Domains documentation](https://developers.cloudflare.com/workers/configuration/routing/custom-domains/). For binding restrictions, review the [Email Sending binding documentation](https://developers.cloudflare.com/email-service/configuration/send-bindings/).

## Operational guides

Use the shared [validation and deployment guides](/stormtrust/engineering/operations/) and [health evidence by app](/stormtrust/engineering/operations/health/#evidence-by-application). For a failed release, follow [recovery procedures](/stormtrust/engineering/operations/recover/) with this application's state and access boundaries.
