meshai/dashboard-frontend/src/pages/TownAnchors.tsx

156 lines
7.9 KiB
TypeScript
Raw Normal View History

feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
// v0.6-4 TownAnchors table editor.
import { useEffect, useState, useCallback } from 'react'
import { Loader2, Plus, Trash2, Check, X, MapPin } from 'lucide-react'
interface TownAnchor {
anchor_id: number
name: string
lat: number
lon: number
state: string | null
enabled: boolean
updated_at: number
}
const EMPTY_DRAFT: TownAnchor = {
anchor_id: 0, name: '', lat: 0, lon: 0, state: 'ID', enabled: true, updated_at: 0,
}
export default function TownAnchors() {
const [rows, setRows] = useState<TownAnchor[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [editing, setEditing] = useState<number | null>(null)
const [adding, setAdding] = useState(false)
const [draft, setDraft] = useState<TownAnchor>(EMPTY_DRAFT)
const refresh = useCallback(async () => {
setLoading(true); setError(null)
try {
const res = await fetch('/api/town-anchors')
if (!res.ok) throw new Error(`GET: ${res.status}`)
setRows(await res.json())
} catch (e) { setError(String(e)) } finally { setLoading(false) }
}, [])
useEffect(() => { refresh() }, [refresh])
const beginEdit = (r: TownAnchor) => { setEditing(r.anchor_id); setDraft({ ...r }); setAdding(false) }
const beginAdd = () => { setAdding(true); setEditing(null); setDraft({ ...EMPTY_DRAFT }) }
const cancel = () => { setEditing(null); setAdding(false); setDraft(EMPTY_DRAFT) }
const save = async () => {
const url = adding ? '/api/town-anchors' : `/api/town-anchors/${editing}`
const method = adding ? 'POST' : 'PUT'
const res = await fetch(url, {
method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(draft),
})
if (!res.ok) {
const b = await res.json().catch(() => ({}))
alert(`save failed: ${b.detail || res.statusText}`); return
}
cancel(); refresh()
}
const remove = async (id: number) => {
if (!confirm(`Delete anchor ${id}?`)) return
const res = await fetch(`/api/town-anchors/${id}`, { method: 'DELETE' })
if (!res.ok) { alert(`delete failed: ${res.status}`); return }
refresh()
}
if (loading) return <div className="p-6 text-slate-400"><Loader2 className="w-5 h-5 animate-spin inline mr-2" />Loading</div>
if (error) return <div className="p-6 text-red-400">Load failed: {error}</div>
return (
<div className="p-6 space-y-4">
<div className="flex items-center gap-2">
<MapPin className="w-5 h-5 text-accent" />
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
<h1 className="text-xl font-semibold text-slate-100">Town Anchors</h1>
<span className="text-xs text-slate-500 ml-2">{rows.length} towns</span>
<button onClick={beginAdd}
className="ml-auto flex items-center gap-1 px-3 py-1 bg-[#f59e0b] hover:bg-[#d97706] text-black font-sans font-medium text-sm">
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
<Plus className="w-4 h-4" /> Add town
</button>
</div>
<p className="text-xs text-slate-400 max-w-3xl">
docs(v0.7): comprehensive dashboard docs rewrite -- Reference +8 sections, per-page tooltips, component polish All three approved tiers in one commit. Reference.tsx is the deep docs hub (8 new sections); the 10 other pages get short helper text + tooltips that cross-reference back into Reference; 3 components get operational-context tooltips. No new features land here -- this is the copy that catches the GUI up to v0.6 + v0.7 system behavior. Decisions applied per Matt's call: - Keep both bang commands AND the LLM DM path (bangs are short on a mesh-constrained interface; LLM is the anything-else path). Cross- references between the two land in Reference -> Commands and Reference -> LLM DM Queries. - Rename "wire-string rendering" to "broadcast text" in user-facing copy on TownAnchors.tsx, GaugeSites.tsx, and the Curation section of Reference.tsx. - Keep the "AND-model anti-pattern" tooltip as-is on Environment.tsx + GaugeSites.tsx (specificity is the value for advanced users); the OR-not-AND Reference section is its home definition that other tooltips can link to. Ham terminology preserved: - Reference.tsx solar/Kp section retains "Quiet sun" / "Quiet HF conditions" language (SFI/Kp vocabulary, not the deleted Quiet Hours feature -- confirmed via direct grep before writing). Tier 1: Reference.tsx (the depth doc) -- 8 new sections, ordered for readability: - "Fire Tracker (Fusion)": Phases 1-4 unified. Six fire-family alert categories with example wire strings (wildfire_declared, wildfire_growth, wildfire_halted, wildfire_spotting, unattributed_hotspot_cluster, wildfire_incident). Attribution mechanics (spread_radius_mi default, centroid as 24h median). Movement mechanics (pass_id bucketing, per-pass centroid, 8-way bearing, mi/h drift). Spotting mechanics (convex-hull perimeter + vertex-distance approximation + per-fire cooldown). Daily LLM digest (twice-daily summary broadcaster). The 10 fires.* adapter_config knobs with defaults. - "Broadcast Types": the three prefix categories -- New: (first sight), Update: (material change), Active: (clock-driven reminder). - "Reminder System": cadences per adapter (WFIGS 8h, SWPC 8h, ITD 511 per-zone). The tombstone (fires.tombstoned_at) termination. The per-adapter reminder_enabled flag. - "LLM DM (Natural-Language Queries)": all 7 env_reporter adapter blocks (build_fires_detail / build_alerts_detail / build_quakes_detail / build_traffic_detail / build_gauges_detail / build_swpc_detail / build_drop_audit) with example questions that hit each one. The grounding clause behavior ("No active X right now" when an adapter block is empty -- the v0.7-fire-tracker-4-final clamp). The include_in_llm_context per-adapter toggle. - "OR-not-AND Architecture": the per-adapter Central vs native contract. Mutually exclusive. The AND-mode anti-pattern definition (referenced by the Environment + GaugeSites tooltips). The Spokane fix context. - "Adapter Config & the CODE Rule": the GUI knob hub. The CONFIG-vs- CODE split (thresholds in CONFIG, sentence templates / emoji / translation maps in CODE). Restart-required vs live keys. The include_in_llm_context toggle. - "Curation: Gauges & Towns": Gauge Sites (NWS-AHPS thresholds, USGS lookup, Action/Minor/Moderate/Major). Town Anchors (broadcast text suffix lookup chain: Photon -> this table -> landclass -> county -> coords). Example output "3 mi N of Almo". - "Schema Migrations": light touch. v11-v16 schema additions tagged with the phase they shipped under. Tier 2: per-page tooltips and cross-references (10 pages): - AdapterConfig.tsx: header paragraph extended with the CODE rule pointer + LLM context toggle explanation. - Alerts.tsx: !subscribe blurb extended with the three broadcast types and links to Reference -> Broadcast Types + Reminder System. - Config.tsx: environmental section description updated to point at Environment.tsx for adapter knobs + Reference -> OR-not-AND for the architecture. - Dashboard.tsx: RF Propagation title carries SWPC R/S/G + Kp legend tooltip; LOCAL badge defines what counts as local. - Environment.tsx: Central region-token helper now references the OR-not-AND section; tick_seconds defined inline as the native-mode poll interval. - GaugeSites.tsx: page description rewritten -- replaces "envelope time" jargon with operational language, explains USGS lookup mechanics, points at Reference -> OR-not-AND for the central-feed disable. - Mesh.tsx: Topology + Geographic buttons get tooltips defining the rendering model. - Notifications.tsx: band-conditions block extended with the daily fire digest pointer + Reference -> Fire Tracker + Broadcast Types cross-refs. - TownAnchors.tsx: page description rewritten -- "wire-string rendering" -> "broadcast text", chain fallback explained ("Photon -> this table -> landclass -> county/state -> coords"), example output included. Tier 3: component tooltip polish (3 components): - NodeTable.tsx: Battery + Last Heard column headers get title-bearing spans with the voltage chart + offline-threshold legend. - NodeDetail.tsx: SNR quality bands documented as a comment in the neighbor render block (the legend lives next to where the colored quality dots are computed). - RestartBanner.tsx: banner copy extended with the restart-required catalog (Config -> environmental, LLM backend swap, dispatcher cold-start grace) so operators know what touched it. Build verification: - tsc + vite build green (one warning about chunk size > 500kB -- pre-existing). - All 8 new TOPICS ids resolve in the served bundle: adapter-config, broadcast-types, curation, fire-tracker, llm-dm, or-not-and, reminders, schema. - Distinctive new strings present in the bundle ("3 mi N of Almo", "Photon nearest-town", "AND-mode anti-pattern", "R (Radio Blackouts"). - "Quiet sun" preserved (the ham SFI/Kp vocabulary in the Solar section, not the deleted Quiet Hours feature). - Container Up healthy, 0 tracebacks in 2 min post-rebuild. Changelog: v0.7-docs-rewrite.md (per-page strip / rewrite / add table). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-06 15:24:34 +00:00
Lookup table for the &quot;X mi &lt;bearing&gt; of &lt;town&gt;&quot; suffix in the bot&apos;s broadcast text.
When a fire or NWS alert renders, the bot walks: Photon nearest-town &rarr; this table &rarr; landclass &rarr;
county/state &rarr; bare coords. Disabled rows fall through to the next anchor in the chain; the
broadcast still goes out, it just uses a different anchor. Example: &quot;3 mi N of Almo&quot;.
See Reference &rarr; Curation: Gauges &amp; Towns for the full chain.
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
</p>
{adding && <RowEditor draft={draft} setDraft={setDraft} onSave={save} onCancel={cancel} adding />}
<div className="bg-bg-card border border-border overflow-x-auto">
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
<table className="w-full text-sm text-slate-200">
<thead className="bg-[#161616] border-b border-border">
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
<tr>
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-left">Name</th>
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-right">Lat</th>
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-right">Lon</th>
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-center">State</th>
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666] text-center">On</th>
<th className="px-3 py-2 font-sans text-[9px] uppercase tracking-widest text-[#666]"></th>
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
</tr>
</thead>
<tbody className="divide-y divide-border">
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
{rows.map(r => editing === r.anchor_id ? (
<tr key={r.anchor_id} className="bg-bg-card border-b border-border hover:bg-bg-hover">
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
<td colSpan={6} className="px-3 py-2"><RowEditor draft={draft} setDraft={setDraft} onSave={save} onCancel={cancel} /></td>
</tr>
) : (
<tr key={r.anchor_id} className="hover:bg-bg-hover">
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
<td className="px-3 py-2 capitalize">{r.name}</td>
<td className="px-3 py-2 text-right text-xs">{r.lat.toFixed(4)}</td>
<td className="px-3 py-2 text-right text-xs">{r.lon.toFixed(4)}</td>
<td className="px-3 py-2 text-center text-xs">{r.state || '-'}</td>
<td className="px-3 py-2 text-center">{r.enabled ? <Check className="w-4 h-4 text-emerald-400 inline" /> : <X className="w-4 h-4 text-slate-500 inline" />}</td>
<td className="px-3 py-2 text-right">
<button onClick={() => beginEdit(r)} className="text-accent hover:text-accent text-xs mr-3">Edit</button>
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
<button onClick={() => remove(r.anchor_id)} className="text-red-400 hover:text-red-300"><Trash2 className="w-4 h-4 inline" /></button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)
}
function RowEditor({ draft, setDraft, onSave, onCancel, adding }: {
draft: TownAnchor, setDraft: (t: TownAnchor) => void,
onSave: () => void, onCancel: () => void, adding?: boolean,
}) {
const upd = (k: keyof TownAnchor, v: unknown) => setDraft({ ...draft, [k]: v })
return (
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 p-3 bg-[#1a1a1a]">
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
<label className="text-xs text-slate-400 col-span-2">Name (lowercased on save)
<input className="block w-full mt-1 bg-bg border border-border px-2 py-1 text-slate-100"
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
value={draft.name} onChange={e => upd('name', e.target.value)} disabled={!adding} />
</label>
<label className="text-xs text-slate-400">State
<input className="block w-full mt-1 bg-bg border border-border px-2 py-1 text-slate-100"
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
value={draft.state ?? ''} onChange={e => upd('state', e.target.value)} />
</label>
<label className="text-xs text-slate-400 flex items-center gap-2">
<input type="checkbox" checked={draft.enabled} onChange={e => upd('enabled', e.target.checked)} className="accent-[#f59e0b] mt-4" />
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
Enabled
</label>
<label className="text-xs text-slate-400">Lat
<input type="number" step="any" className="block w-full mt-1 bg-bg border border-border px-2 py-1 text-slate-100"
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
value={draft.lat} onChange={e => upd('lat', parseFloat(e.target.value))} />
</label>
<label className="text-xs text-slate-400">Lon
<input type="number" step="any" className="block w-full mt-1 bg-bg border border-border px-2 py-1 text-slate-100"
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
value={draft.lon} onChange={e => upd('lon', parseFloat(e.target.value))} />
</label>
<div className="col-span-2 flex items-center justify-end gap-2 mt-2">
<button onClick={onCancel} className="px-3 py-1 text-slate-300 hover:bg-bg-hover text-sm">Cancel</button>
<button onClick={onSave} className="px-3 py-1 bg-[#f59e0b] hover:bg-[#d97706] text-black font-sans font-medium text-sm">Save</button>
feat(v0.6-4): gauge_sites + town_anchors curation tables + GUI CRUD Closes Section A.5 (gauge_sites) and A.12 (town_anchors) of the audit doc by lifting both Python-dict curation lists into editable SQLite tables. Operators can add/edit/disable rows from the dashboard without a deploy; runtime reads go through cached accessors that invalidate when the REST API mutates state. Schema: v8.sql adds gauge_sites(site_id PK, gauge_name, lat, lon, action_ft, flood_minor_ft, flood_moderate_ft, flood_major_ft, enabled, updated_at). v9.sql adds town_anchors(anchor_id AUTOINC PK, name UNIQUE, lat, lon, state, enabled, updated_at). SCHEMA_VERSION 7 -> 9. Seed (meshai/persistence/curation.py): _GAUGE_SITES_SEED carries the original 9 Idaho rows from IDAHO_CURATED_SITES verbatim. _TOWN_ANCHORS_SEED carries the 29 Idaho-and-neighbor towns from _TOWN_COORDS verbatim. seed_gauge_sites() / seed_town_anchors() INSERT OR IGNORE -- safe to re-run; never overwrites user edits. Handler integration: - meshai/central/idaho_gauge_sites.py: IDAHO_CURATED_SITES dict deleted. lookup_site() now calls meshai.persistence.curation.lookup_gauge_site() which reads the table. THRESHOLD_RANK, normalize_site_id, and compute_threshold_state remain in this module (CODE per Matt s rule). - meshai/central/nwis_handler.py drops IDAHO_CURATED_SITES from its import list; the table-backed lookup_site() is API-compatible. - meshai/central_normalizer.py: _TOWN_COORDS dict deleted. _compute_distance_bearing() now calls meshai.persistence.curation.lookup_town_anchor() with the same lowercased-name semantics it always used. REST API (meshai/dashboard/api/curation_routes.py): /api/gauge-sites GET list, GET one, POST add, PUT update, DELETE /api/town-anchors GET list, GET one, POST add, PUT update, DELETE Every mutation calls invalidate_curation_cache() so handler reads see the new state on the next call -- no container restart. Dashboard (dashboard-frontend/src/pages/): - GaugeSites.tsx: table view with Add row / Edit row inline / Delete confirm + per-row enabled toggle. 8 columns mirror the schema. - TownAnchors.tsx: same pattern, 5 columns. Name is lowercased on save to match the lookup key. - Left-nav entries "Gauge Sites" (Droplets icon) and "Town Anchors" (MapPin icon) added to Layout.tsx; routes added to App.tsx. Tests (tests/test_curation.py, 18 cases): - v8/v9 tables exist - Seed lands every row from both dicts - Seed idempotent; never overwrites user edits - lookup_gauge_site hits/miss, disabled rows are invisible - lookup_town_anchor case-insensitive - REST API: GET list, GET one, GET 404, POST add, PUT update, DELETE, POST missing-field 400; both gauge_sites + town_anchors - Accessor reflects API mutations after invalidate_curation_cache() tests/test_nwis_handler.py back-compat: IDAHO_CURATED_SITES dict alias points at _GAUGE_SITES_SEED so the existing assertion suite still passes. tests/test_adapter_config_foundation.py schema_meta v7 -> v9 bump. Test count: 797 -> 819 (+18 curation cases + 4 maintenance updates).
2026-06-05 20:19:13 +00:00
</div>
</div>
)
}