{"id":51648481,"url":"https://github.com/dpup/sierra-data","last_synced_at":"2026-07-14T01:02:59.960Z","repository":{"id":314676896,"uuid":"1056380738","full_name":"dpup/sierra-data","owner":"dpup","description":"A real-time API server providing road conditions and weather information for the Ebbett's Pass region.","archived":false,"fork":false,"pushed_at":"2026-07-06T19:03:55.000Z","size":26394,"stargazers_count":0,"open_issues_count":6,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-06T21:05:52.197Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dpup.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-14T00:59:41.000Z","updated_at":"2026-07-06T19:04:04.000Z","dependencies_parsed_at":"2025-09-14T03:09:18.625Z","dependency_job_id":"e2af085b-b2a5-47c2-a06c-7b248df11c70","html_url":"https://github.com/dpup/sierra-data","commit_stats":null,"previous_names":["dpup/info.ersn.net","dpup/sierra-data"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dpup/sierra-data","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dpup%2Fsierra-data","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dpup%2Fsierra-data/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dpup%2Fsierra-data/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dpup%2Fsierra-data/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dpup","download_url":"https://codeload.github.com/dpup/sierra-data/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dpup%2Fsierra-data/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35441637,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-13T02:00:06.543Z","response_time":119,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-14T01:02:53.785Z","updated_at":"2026-07-14T01:02:54.985Z","avatar_url":"https://github.com/dpup.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# The Grid\n\nThe data service behind **S.I.E.R.R.A** — a real-time, keyless API providing road, weather, and hazard information for the central Sierra (Ebbett's Pass corridor), combining data from Google Routes API, Caltrans feeds, OpenWeatherMap, USGS, CAL FIRE, and Cal OES.\n\nPrimary home: **https://data.sierragridteam.org**. `info.ersn.net` remains a supported CNAME alias through the transition, and ersn.net is a consuming site (\"powered by S.I.E.R.R.A\").\n\n## Overview\n\nThe server dynamically builds routes between geographic points using the Google Routes API, retrieving real-time traffic data and estimated travel times. Polyline geometry from Google is used to cross-reference Caltrans feeds, with alerts filtered and classified as on-route or nearby based on spatial relevance. To improve usability, OpenAI is integrated to automatically convert technical Caltrans alerts into clear, human-readable summaries.\n\nWeather data combines OpenWeatherMap current conditions (fetched per configured location) with authoritative National Weather Service zone alerts — each location is mapped to its NWS forecast zone, so alerts come from NWS rather than OpenWeather's rate-capped One Call API.\n\nThe architecture is modular and location-agnostic, allowing easy adaptation to other regions or road networks by updating configuration.\n\n**Live API: https://data.sierragridteam.org** (legacy alias: `https://info.ersn.net`)\n\n## Features\n\n- **Real-time Road Conditions**: Live traffic data, travel times, and congestion levels from Google Routes API\n- **Intelligent Road Alerts**: AI-enhanced alerts from Caltrans feeds including lane closures, CHP incidents, and construction\n- **Smart Alert Classification**: Route-aware filtering (ON_ROUTE/NEARBY/DISTANT) based on spatial analysis\n- **AI-Enhanced Descriptions**: Automatic OpenAI conversion of technical alerts into clear, human-readable summaries\n- **Weather Information**: Current conditions from OpenWeatherMap plus authoritative NWS zone alerts for multiple locations\n- **Unified Hazard Layer**: Map-ready GeoJSON aggregating wildfires, earthquakes, evacuations, weather alerts, and road incidents into one standardized interface, with a fail-loud `source_status` and a life-safety evacuation contract\n- **Grid Event Store (v2)**: Every hazard source normalized into a canonical event model, persisted in SQLite with full revision history, and served through a new `/v1` API plus an embedded data site — see [Grid Info Service (v2)](#grid-info-service-v2)\n- **Stale-while-revalidate Caching**: Sub-100ms responses by serving cached data while refreshing in background\n- **REST API**: Clean HTTP endpoints with comprehensive JSON responses\n- **gRPC Support**: Native gRPC services with automatic HTTP gateway\n- **Configurable \u0026 Scalable**: Location-agnostic architecture with simple YAML configuration\n\n## API Endpoints\n\n### Roads API\n\n#### List All Roads\n```http\nGET /api/v1/roads\n```\n\n**Response Example:**\n```json\n{\n  \"roads\": [\n    {\n      \"id\": \"hwy4-angels-murphys\",\n      \"name\": \"Hwy 4\",\n      \"section\": \"Angels Camp to Murphys\",\n      \"status\": \"OPEN\",\n      \"statusExplanation\": \"\",\n      \"durationMinutes\": 11,\n      \"distanceKm\": 13,\n      \"congestionLevel\": \"CLEAR\",\n      \"delayMinutes\": 0,\n      \"alerts\": [\n        {\n          \"type\": \"CONSTRUCTION\",\n          \"severity\": \"INFO\",\n          \"classification\": \"NEARBY\",\n          \"title\": \"Lane Closure Advisory\",\n          \"description\": \"Routine maintenance work on shoulder, no traffic impact expected.\",\n          \"condensedSummary\": \"Shoulder work, no delays\",\n          \"impact\": \"IMPACT_NONE\",\n        }\n      ]\n    }\n  ],\n  \"lastUpdated\": \"2025-09-11T01:52:05.646618Z\"\n}\n```\n\n#### Get Specific Road\n```http\nGET /api/v1/roads/{road_id}\n```\n\n**Response Example:**\n```json\n{\n  \"road\": {\n    \"id\": \"hwy4-murphys-arnold\",\n    \"name\": \"Hwy 4\",\n    \"section\": \"Murphys to Arnold\",\n    \"status\": \"RESTRICTED\",\n    \"statusExplanation\": \"Right lane blocked due to multi-vehicle accident near mile marker 45\",\n    \"durationMinutes\": 15,\n    \"distanceKm\": 20,\n    \"congestionLevel\": \"CLEAR\",\n    \"delayMinutes\": 0,\n    \"alerts\": [\n      {\n        \"id\": \"250911GG0206\",\n        \"type\": \"INCIDENT\",\n        \"severity\": \"WARNING\",\n        \"classification\": \"ON_ROUTE\",\n        \"title\": \"CHP Incident 250911GG0206\",\n        \"description\": \"Multi-vehicle accident blocking right lane near mile marker 45. Expect 15-20 minute delays while emergency crews clear the scene.\",\n        \"condensedSummary\": \"Accident blocking right lane, 15-20 min delays\",\n        \"startTime\": \"2025-09-11T01:30:00Z\",\n        \"location\": {\n          \"latitude\": 38.2345,\n          \"longitude\": -120.1234\n        },\n        \"locationDescription\": \"Highway 4 near mile marker 45\",\n        \"impact\": \"IMPACT_MODERATE\",\n        \"timeReported\": \"2025-09-11T01:30:00Z\",\n        \"lastUpdated\": \"2025-09-11T01:45:00Z\",\n        \"distanceToRouteMeters\": 3.5,\n        \"metadata\": {\n          \"lanes_affected\": \"1 of 2\",\n          \"emergency_services\": \"CHP on scene\"\n        }\n      }\n    ]\n  },\n  \"lastUpdated\": \"2025-09-11T01:52:05.646618Z\"\n}\n```\n\n**Road Status Values:**\n- `OPEN` - Road is open to traffic\n- `CLOSED` - Road is closed\n- `RESTRICTED` - Limited access or restrictions\n- `MAINTENANCE` - Under maintenance\n\n**Status Explanation:**\nWhen a road's status is `RESTRICTED` or `CLOSED`, the `statusExplanation` field provides a clear, human-readable explanation of the reason. The AI intelligently distinguishes between mainline road impacts vs ramp/exit impacts:\n\n**Examples of RESTRICTED status:**\n- \"Right lane blocked due to multi-vehicle accident near mile marker 45\" (mainline lane closure)\n- \"Off-ramp to Treasure Island is closed\" (ramp closure, mainline open)\n- \"On-ramp from Main Street is blocked due to construction\" (ramp closure)\n\n**Examples of CLOSED status:**\n- \"Full mainline closure due to emergency road repairs\"\n- \"Complete highway shutdown between mile markers 15-20\"\n- \"All lanes blocked by major incident\"\n\n**Congestion Levels:**\n- `CLEAR` - Free flowing traffic\n- `LIGHT` - Light traffic\n- `MODERATE` - Moderate traffic\n- `HEAVY` - Heavy traffic\n- `SEVERE` - Severe congestion\n\n### Road Alerts\n\nThe Roads API provides intelligent alerts that combine data from multiple sources and uses AI enhancement for better readability.\n\n**Alert Types:**\n- `CLOSURE` - Road closures and lane restrictions\n- `CONSTRUCTION` - Planned construction activities\n- `INCIDENT` - Traffic accidents and emergency situations\n- `WEATHER` - Weather-related road conditions\n\n**Alert Severity:**\n- `INFO` - Informational notices\n- `WARNING` - Moderate impact on travel\n- `CRITICAL` - Severe impact or safety concerns\n\n**Alert Classification:**\n- `ON_ROUTE` - Directly affects route path (\u003c 100m from route)\n- `NEARBY` - In surrounding area but not blocking route\n- `DISTANT` - Too far from route to be relevant\n\n**Distance Information:**\n- `distanceToRouteMeters` - Distance from alert location to route in meters\n- Provided for all alert classifications to enable client-side proximity rendering\n- ON_ROUTE alerts typically show distances \u003c 100m\n- NEARBY alerts show distances from 100m to several kilometers\n- Useful for client applications to display \"2.1 km from route\" type information\n\n**AI Enhancement Features:**\n- **Smart Road Status Determination**: AI intelligently analyzes incident titles and descriptions to determine accurate road status (open/restricted/closed) with detailed explanations\n- **Mainline vs Ramp Intelligence**: AI distinguishes between:\n  - **Mainline impacts**: Lane closures, accidents, or construction on the main highway (status: RESTRICTED/CLOSED)\n  - **Ramp/Exit impacts**: On-ramp, off-ramp, or exit closures that don't affect main traffic flow (status: RESTRICTED with specific explanation)\n- **Smart Descriptions**: Technical Caltrans alerts automatically converted to human-readable summaries using OpenAI GPT-4\n- **Chain Control Detection**: AI identifies R1/R2 chain requirements from incident text and weather conditions\n- **Impact Assessment**: AI evaluates impact levels (`AlertImpact`): `IMPACT_NONE`, `IMPACT_LIGHT`, `IMPACT_MODERATE`, `IMPACT_SEVERE`\n- **Duration Estimates**: AI provides duration estimates (`AlertDuration`): `DURATION_UNKNOWN`, `DURATION_UNDER_ONE_HOUR`, `DURATION_SEVERAL_HOURS`, `DURATION_ONGOING`\n- **Content-Based Caching**: 24-hour cache prevents duplicate AI processing of identical incident content\n- **Condensed Summaries**: Short format optimized for mobile displays\n- **Structured Metadata**: Additional contextual information like lanes affected, emergency services on scene\n\n**Data Sources:**\n- **Caltrans KML Feeds**: Lane closures, CHP incidents, and chain control status\n- **Google Routes API**: Real-time traffic conditions and route geometry for spatial matching\n- **OpenAI Enhancement**: Automatic conversion of technical alerts into clear, actionable information\n\n**Chain Control:** Roads include a `chainControlInfo` object (level R1/R2/R3,\nlocation, direction, effective time) sourced from the Caltrans chain-control KML\nfeed. Outside winter the feed is typically empty and roads report no chain\nrequirements.\n\n### Incidents API\n\nRegion-wide CHP/Caltrans dispatch incidents, surfaced independently of the\nmonitored roads (issue #7). Unlike road `alerts[]`, this is a flat list scoped by\na configured geographic area rather than per-route. Every incident is\nAI-enhanced with a readable `description` plus `condensedSummary`, `impact`,\nand `metadata` (cached 24h by content, capped per refresh), and `severity` is\nderived from the model's impact assessment. An incident may briefly appear with\nstructural fields and keyword-heuristic severity until its enhancement lands on\na following refresh.\n\n```http\nGET /api/v1/incidents/{area}\n```\n\nThe `area` is a path param (an area id like `mother-lode`); an unknown area\nreturns 404. Areas are defined in `prefab.yaml` under `roads.incidentAreas`.\n\n**Response Example** (`GET /api/v1/incidents/mother-lode`):\n```json\n{\n  \"incidents\": [\n    {\n      \"id\": \"260625SA0982\",\n      \"type\": \"INCIDENT\",\n      \"severity\": \"WARNING\",\n      \"location\": { \"latitude\": 38.07, \"longitude\": -120.54 },\n      \"locationDescription\": \"Sr49 / Monitor Rd\",\n      \"description\": \"Traffic Hazard\",\n      \"status\": \"ACTIVE\",\n      \"logNumber\": \"260625SA0982\",\n      \"started\": \"2026-06-25T17:46:00-07:00\",\n      \"lastUpdated\": \"2026-06-25T18:34:00-07:00\",\n      \"area\": \"mother-lode\"\n    }\n  ],\n  \"lastUpdated\": \"2026-06-26T01:33:38Z\",\n  \"area\": \"mother-lode\"\n}\n```\n\nAreas are bounding boxes defined in `prefab.yaml` under `roads.incidentAreas`.\nThe `mother-lode` area covers the Gold Country foothills while excluding the\nCentral Valley floor.\n\n### Weather API\n\n#### List All Weather Locations\n```http\nGET /api/v1/weather\n```\n\n**Response Example:**\n```json\n{\n  \"weatherData\": [\n    {\n      \"locationId\": \"murphys\",\n      \"locationName\": \"Murphys, CA\",\n      \"weatherMain\": \"Clear\",\n      \"weatherDescription\": \"clear sky\",\n      \"weatherIcon\": \"01d\",\n      \"temperatureCelsius\": 22,\n      \"feelsLikeCelsius\": 21,\n      \"humidityPercent\": 45,\n      \"windSpeedKmh\": 8,\n      \"windDirectionDegrees\": 230,\n      \"visibilityKm\": 16,\n      \"alerts\": []\n    }\n  ],\n  \"lastUpdated\": \"2025-09-11T01:52:05.646618Z\"\n}\n```\n\n#### Get Weather Alerts\n```http\nGET /api/v1/weather/alerts\nGET /api/v1/weather/alerts?zones=CAZ064,CAZ065   # filter to NWS zones\n```\n\nReturns authoritative **NWS** zone alerts (`source: \"NWS\"`), carrying `severity`\nand `zones`. The optional `?zones=` filter narrows the alerts to the given\nforecast zones (issue #4). OpenWeatherMap-sourced alerts were removed 2026-07-04\n(see CHANGELOG) — for US locations they were relabeled NWS data fetched through\na rate-capped endpoint.\n\n**Response Example:**\n```json\n{\n  \"alerts\": [\n    {\n      \"id\": \"urn:oid:2.49.0.1.840.0.abc\",\n      \"source\": \"NWS\",\n      \"event\": \"Red Flag Warning\",\n      \"severity\": \"CRITICAL\",\n      \"zones\": [\"CAZ064\", \"CAZ065\"],\n      \"senderName\": \"NWS Sacramento CA\",\n      \"headline\": \"Red Flag Warning in effect until 8 PM PDT\",\n      \"summary\": \"Gusty winds and low humidity.\",\n      \"startTime\": \"2026-06-25T19:33:00-07:00\",\n      \"endTime\": \"2026-06-28T17:00:00-07:00\"\n    }\n  ],\n  \"lastUpdated\": \"2026-06-26T01:52:05.646618Z\"\n}\n```\n\n#### Fire-Weather Classification (issue #5)\n\n`GET /api/v1/weather` (and `/weather/{id}`) includes a single region-wide\n`fireWeather` object at the response top level, derived from authoritative NWS\nfire-weather products (these products are issued per forecast zone, not per\npoint, so the classification is regional):\n\n```json\n\"fireWeather\": {\n  \"state\": \"RED_FLAG\",           // NORMAL | ELEVATED | RED_FLAG\n  \"sourceEvent\": \"Red Flag Warning\",\n  \"headline\": \"Red Flag Warning in effect until 8 PM PDT\",\n  \"senderName\": \"NWS Sacramento CA\",\n  \"effective\": \"2026-06-26T10:00:00-07:00\",\n  \"expires\": \"2026-06-26T20:00:00-07:00\",\n  \"zones\": [\"CAZ064\", \"CAZ065\"]\n}\n```\n\n`state` escalates `NORMAL` → `ELEVATED` (Fire Weather Watch) → `RED_FLAG` (Red\nFlag Warning). It is only ever `RED_FLAG` when NWS has an active Red Flag Warning\nfor the relevant zone — never a value the feed can't confirm.\n\n### Hazards API\n\nA unified, **map-ready** aggregation layer that re-projects every hazard source\ninto one standardized interface a maps client (MapLibre GL, Leaflet, OpenLayers)\ncan layer directly. Unlike the rest of the API, these endpoints are hand-built\nGeoJSON/JSON, so field names are `snake_case`. Full design:\n[`docs/hazard-aggregation-design.md`](docs/hazard-aggregation-design.md).\n\n#### Per-Layer GeoJSON\n\n```\nGET /api/v1/hazards/{area}/{layer}.geojson\n```\n\nReturns one [RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946)\n`FeatureCollection` (`Content-Type: application/geo+json`) plus a `metadata`\nmember. Layers: `road_incident`, `chain_control`, `road_segment`,\n`weather_alert`, `fire_weather`, `earthquake`, `wildfire`, `evacuation`.\nCoordinates are `[longitude, latitude]`.\n\nEvery feature shares a common `properties` envelope plus a namespaced per-kind\nblock, on a single severity scale `INFO | MINOR | MODERATE | SEVERE | EXTREME`\n(with an integer `severity_rank` 0–4 for sort/color):\n\n```json\n{\n  \"type\": \"FeatureCollection\",\n  \"metadata\": {\n    \"layer\": \"wildfire\", \"area\": \"ebbetts-pass\",\n    \"source_status\": \"OK\",                 // OK | STALE | UNAVAILABLE\n    \"last_source_update\": \"\",              // RFC3339 time of last good fetch (STALE only)\n    \"attribution\": \"CAL FIRE / WFIGS\", \"schema_version\": 1\n  },\n  \"features\": [{\n    \"type\": \"Feature\",\n    \"geometry\": { \"type\": \"Polygon\", \"coordinates\": [ /* [lng,lat] rings */ ] },\n    \"properties\": {\n      \"id\": \"calfire:...\", \"layer\": \"wildfire\", \"kind\": \"Wildfire\",\n      \"severity\": \"SEVERE\", \"severity_rank\": 3,\n      \"headline\": \"Salt Springs Fire — 1,200 ac, 20% contained\",\n      \"wildfire\": { \"acres\": 1200, \"containment\": 20, \"county\": \"Calaveras\", \"has_perimeter\": true },\n      \"source\": { \"id\": \"calfire\", \"name\": \"CAL FIRE\", \"url\": \"...\" }\n    }\n  }]\n}\n```\n\n`metadata.source_status` is the honesty mechanism. Each layer is **fail-loud**:\non a source error it returns `UNAVAILABLE` with empty features (or `STALE` with a\n`last_source_update` when serving the last good cached fetch) — never a fabricated\nclear state.\n\n#### Situation Rollup (one fetch for a dashboard)\n\n```\nGET /api/v1/situation/{area}\n```\n\nA lightweight `application/json` rollup (not a bundle of FeatureCollections — the\nmap still fetches the per-layer `.geojson` above): per-layer `source_status` +\n`feature_count`, and a cross-layer `summary` (`highest_severity`,\n`severity_counts`, `top_headlines`) plus a `scanners` sidecar.\n\n**Life-safety contract:** `summary.active_evacuations` separates *feed health*\nfrom *content*, with `summary.evacuation_status` (`OK` | `STALE` | `UNAVAILABLE`)\nsaying which:\n\n- `UNAVAILABLE` → `active_evacuations: null` — Cal OES errored. Render \"unknown —\n  check the official source\", **never** as zero.\n- `OK` → `active_evacuations: 0` — Cal OES is healthy and reports no active zones.\n  Render \"no active evacuations reported\" (caveated, never a guaranteed all-clear).\n- `OK`/`STALE` → `active_evacuations: N\u003e0` — active zones.\n\nThe invariant is **an error never becomes a `0`**, so a quiet day (`0`) is\ndistinguishable from an outage (`null`). The evacuation layer always carries the\nauthoritative [Genasys](https://protect.genasys.com/) viewer link in its\n`source_url`, in every state.\n\n#### Scanner Feeds\n\n```\nGET /api/v1/scanners/{area}\n```\n\nOperator-configured Broadcastify public-safety scanner feeds for the area\n(`feed_id`, `channel_label`, `agency`, `broadcastify_url`) — link-out only.\n\nAreas (bounds, scanner feeds, incident region) are configured under\n`hazards.areas` in `prefab.yaml`.\n\n## Grid Info Service (v2)\n\nThe v2 layer normalizes every hazard source into a canonical **event** model and\npersists it in SQLite (`grid.dbPath`) with full revision history — every state\nchange, including the all-clear when an event leaves its feed, is a recorded\nrevision, and a restart rehydrates from the store rather than re-fetching.\nEvent-shaped sources (wildfire, evacuation, weather alert, earthquake, road\nincident) flow through an ingest scheduler into the store; the existing\n`/api/v1/hazards` event layers are re-backed by that store (same GeoJSON\nenvelope). A new first-principles API is served at `/v1` (JSON is snake_case,\ntimestamps RFC 3339, ETags everywhere), and an embedded data site at `/` provides\na source-health board, event explorer, place directory + zone resolver, map\npreviewer, and the live API reference. Full docs: the site's `/docs.html` (when\ndeployed) and [`docs/v2-api-spec.md`](docs/v2-api-spec.md); implementation notes\nin [`docs/v2-implementation-plan.md`](docs/v2-implementation-plan.md).\n\n| Endpoint | Returns |\n|---|---|\n| `GET /v1/places/{place}/summary` | Place rollup: `mode`, per-domain status, top events, evac fail-loud invariant, source health |\n| `GET /v1/places/{place}/map/{layer}.geojson` | RFC 7946 FeatureCollection (envelope identical to `/api/v1/hazards`) |\n| `GET /v1/events?place=\u0026layer=\u0026status=\u0026severity_min=\u0026since=\u0026page_token=` | Cross-layer event query (subsumes incidents + weather alerts) |\n| `GET /v1/events/{id}` / `GET /v1/events/{id}/history` | Current revision / revision timeline |\n| `GET /v1/history?place=\u0026from=\u0026to=\u0026layer=` | Cross-event revision archive |\n| `GET /v1/places?kind=\u0026q=` / `GET /v1/places/{place}` | Place directory |\n| `GET /v1/places/resolve?lat=\u0026lng=` \\| `?address=` | Point/address → containing places |\n| `GET /v1/roads?place=` / `GET /v1/weather?place=` | Conditions passthrough (weather minus alerts) |\n| `GET /v1/scanners?place=` | Broadcastify feed config |\n| `GET /v1/sources` | Source registry + per-source health |\n\nWeather alerts have moved off `/v1/weather` — they are events\n(`/v1/events?layer=weather_alert`). `/api/v1` is unchanged in shape and runs\nbeside `/v1` on the same store; it will be retired after consumers cut over (see\n`docs/v2-api-spec.md` §6).\n\n## Quick Start\n\n### Prerequisites\n\n- Go 1.25+\n- Google Routes API key\n- OpenWeatherMap API key\n\n### Installation\n\n1. Clone the repository:\n   ```bash\n   git clone \u003crepository-url\u003e\n   cd info.ersn.net\n   ```\n\n2. Set up environment variables:\n   ```bash\n   export PF__GOOGLE_ROUTES__API_KEY=\"your-google-api-key\"\n   export PF__OPENWEATHER__API_KEY=\"your-openweather-api-key\"\n   export PF__OPENAI__API_KEY=\"your-openai-api-key\"  # Optional, for AI-enhanced alerts\n   ```\n\n3. Build and run:\n   ```bash\n   make build\n   make run\n   ```\n\n4. Test the API:\n   ```bash\n   # Test locally\n   curl http://localhost:8181/api/v1/roads\n   curl http://localhost:8181/api/v1/weather\n   \n   # Or test the live API\n   curl https://data.sierragridteam.org/api/v1/roads\n   curl https://data.sierragridteam.org/api/v1/weather\n   ```\n\n### Configuration\n\nThe server uses `prefab.yaml` for configuration with a **simplified structure** and supports environment variable overrides using the `PF__` prefix. Configuration has been streamlined with top-level client configs and consistent camelCase naming:\n\n```yaml\n# Client Configurations - Top Level\ngoogleRoutes:\n  # apiKey set via PF__GOOGLE_ROUTES__API_KEY\n\nopenai:\n  # apiKey set via PF__OPENAI__API_KEY  \n  model: \"gpt-5-mini\"\n  timeout: \"30s\"\n  maxRetries: 3\n\nopenweather:\n  # apiKey set via PF__OPENWEATHER__API_KEY\n\n# Service Configurations\nroads:\n  refreshInterval: \"5m\"\n  staleThreshold: \"10m\"\n  monitoredRoads:\n    - name: \"Hwy 4\"\n      section: \"Angels Camp to Murphys\"\n      id: \"hwy4-angels-murphys\"\n      origin:\n        latitude: 38.067400\n        longitude: -120.540200\n      destination:\n        latitude: 38.139117\n        longitude: -120.456111\n\nweather:\n  refreshInterval: \"5m\"\n  staleThreshold: \"10m\"\n  locations:\n    - id: \"murphys\"\n      name: \"Murphys, CA\"\n      coordinates:\n        latitude: 38.139117\n        longitude: -120.456111\n```\n\n## Deployment\n\nThe project includes built-in support for AWS ECR and ECS deployment:\n\n### AWS ECR/ECS Deployment\n\nSet the required environment variables:\n```bash\nexport DOCKER_REGISTRY=your-account-id.dkr.ecr.region.amazonaws.com\nexport ECS_CLUSTER=your-cluster-name\nexport ECS_SERVICE=your-service-name\nexport ECS_TASK_DEFINITION=your-task-definition-name\n```\n\nDeploy to ECS:\n```bash\n# Build, push to ECR, and deploy to ECS in one command\nmake ecs-deploy\n\n# Or run individual steps:\nmake ecr-push    # Build and push Docker image to ECR\nmake ecs-deploy  # Update ECS service with latest image\n```\n\n**What happens during deployment:**\n1. Builds Docker image for linux/amd64 platform\n2. Authenticates and pushes to ECR\n3. Updates ECS task definition with new image\n4. Triggers ECS service deployment\n5. Waits for deployment to complete\n\n**Prerequisites:**\n- AWS CLI configured with appropriate permissions\n- Docker installed locally\n- `jq` installed for JSON processing (`brew install jq`)\n\n## Development\n\n### Build Commands\n\n```bash\n# Build all binaries\nmake build\n\n# Build specific components\nmake server    # Main API server\nmake tools     # CLI testing tools\n\n# Generate protobuf code\nmake proto\n\n# Clean build artifacts\nmake clean\n```\n\n### Running the Server\n\n```bash\n# Run in foreground\nmake run\n\n# Run in background for testing\nmake run-bg\n\n# Stop background server\nmake stop\n```\n\n### Testing\n\n```bash\n# Run all tests\nmake test\n\n# Run specific test suites\nmake test-contract      # gRPC contract tests\nmake test-integration   # External API integration tests\nmake test-unit         # Unit tests\n\n# Test individual API clients\nmake test-google       # Test Google Routes API\nmake test-caltrans     # Test Caltrans KML parsing\nmake test-weather      # Test OpenWeatherMap API\n```\n\n### Code Quality\n\n```bash\n# Run linting\nmake lint\n\n# Format code\nmake fmt\n\n# Run both linting and tests\nmake lint \u0026\u0026 make test\n```\n\n### Project Structure\n\n```\n/\n├── api/v1/                     # Protocol Buffer definitions\n│   ├── roads.proto            # gRPC service for road conditions\n│   ├── weather.proto          # gRPC service for weather data\n│   └── common.proto           # Shared proto definitions\n├── cmd/                       # CLI applications\n│   ├── server/                # Main API server\n│   ├── test-google/           # Google Routes API testing tool\n│   ├── test-caltrans/         # Caltrans data testing tool\n│   └── test-weather/          # Weather API testing tool\n├── internal/                  # Private application code\n│   ├── services/              # gRPC service implementations\n│   ├── clients/               # External API clients\n│   ├── cache/                 # In-memory caching with TTL\n│   └── config/                # Configuration management\n├── tests/                     # Test support\n│   └── testdata/              # Static fixture data\n├── prefab.yaml                # Server configuration\n├── Dockerfile                 # Sample docker file\n└── Makefile                   # Build automation\n```\n\n### Adding New Roads\n\n1. Update `prefab.yaml` with new road coordinates (note the simplified structure):\n   ```yaml\n   roads:\n     monitoredRoads:  # Note: camelCase naming\n       - name: \"Highway Name\"\n         section: \"Start to End\"\n         id: \"unique-road-id\"\n         origin:\n           latitude: 0.0\n           longitude: 0.0\n         destination:\n           latitude: 0.0\n           longitude: 0.0\n   ```\n\n2. Test with the Google Routes API tool:\n   ```bash\n  make test-google\n   ```\n\n3. Restart the server to pick up configuration changes:\n   ```bash\n   make stop \u0026\u0026 make run-bg\n   ```\n\n### API Rate Limits\n\n- **Google Routes API**: 3,000 queries per minute\n- **OpenWeatherMap API**: 60 calls per minute (free tier). Only `/data/2.5/weather` is used — the One Call 3.0 endpoint has a separate 1,000 calls/day cap and is deliberately avoided (alerts come from NWS instead)\n- **Caltrans KML Feeds**: No official limits, but feeds are refreshed every 5-30 minutes\n\n### Architecture\n\nThe server uses a hybrid architecture:\n\n- **gRPC Services**: Core business logic implemented as gRPC services\n- **HTTP Gateway**: Automatic REST API generation from gRPC definitions\n- **External Clients**: Dedicated clients for each external API\n- **Caching Layer**: In-memory cache with TTL for performance\n- **Configuration**: Prefab framework for flexible configuration management\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch (`git checkout -b feature/amazing-feature`)\n3. Make your changes following the existing code style\n4. Run tests and linting (`make lint \u0026\u0026 make test`)\n5. Commit your changes (`git commit -m 'Add amazing feature'`)\n6. Push to the branch (`git push origin feature/amazing-feature`)\n7. Open a Pull Request\n\n## Support\n\nFor questions, issues, or feature requests, please open an issue on the GitHub repository.\n\n## License\n\nMIT License\n\nCopyright (c) 2025 Daniel Pupius\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdpup%2Fsierra-data","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdpup%2Fsierra-data","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdpup%2Fsierra-data/lists"}