Commit graph

4 commits

Author SHA1 Message Date
e3bf53ade4 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
42b3106e97 feat(v0.6-3c): adapter_config REST API + dashboard editor
Closes the audit-doc Section A keystone (the GUI editor). Together with
v0.6-3a foundation, v0.6-3a.1 trim, and v0.6-3b handler wiring, every
Rule-17 CONFIG knob from the audit is now editable in the dashboard
without a container restart.

API (meshai/dashboard/api/adapter_config_routes.py):
  GET  /api/adapter-config              -- {adapter: [{key, value, default,
                                            type, description}]}
  GET  /api/adapter-config/<adapter>    -- one adapter list
  GET  /api/adapter-config/<adapter>/<key> -- single row
  PUT  /api/adapter-config/<adapter>/<key> body {value} -- typed validation
       int: int or whole-number float; rejects bool, fractional float, str
       float: int or float; rejects bool
       str: str only
       bool: bool only
       json: any JSON-serializable value
       Every PUT calls invalidate_cache() so the next handler accessor
       read sees the new value -- no container restart needed.
  POST /api/adapter-config/<adapter>/<key>/reset -- value_json = default_json,
       cache invalidated
  GET  /api/adapter-meta                -- {adapter: {display_name,
                                            include_in_llm_context, description}}
  PUT  /api/adapter-meta/<adapter> partial-update body, fields:
       include_in_llm_context: bool, display_name: non-empty str

Dashboard (dashboard-frontend/src/pages/AdapterConfig.tsx):
  - Per-adapter cards. Header row shows display_name, the include_in_llm_context
    toggle, and an expand chevron. Adapters with zero config keys (e.g. itd_511)
    still render so users can toggle their LLM-context inclusion.
  - Expanded body lists each key with a type-aware widget:
      bool  -> checkbox, commit-on-change
      int/float -> number input, commit-on-blur (or Enter)
      str   -> text input, commit-on-blur
      json  -> textarea, commit-on-blur (JSON.parse with inline error)
    Each row shows the key name, type tag, description, "edited" badge when
    value != default, a per-key Reset button, and a save badge (spinner,
    check, error tooltip, or a small amber dot for unsaved local changes).
  - Auto-save semantics: every blur/change/reset triggers PUT immediately;
    no explicit Save button needed. Reset is one-click per key.

Wiring:
  - meshai/dashboard/server.py registers the new router with prefix /api.
  - dashboard-frontend/src/App.tsx adds the /adapter-config route.
  - dashboard-frontend/src/components/Layout.tsx adds the left-nav entry
    (Sliders icon, label "Adapter Config", after Reference).
  - Vite build produces a fresh meshai/dashboard/static bundle. The
    Dockerfile copies meshai/ so the new bundle ships with the container
    image at next rebuild.

Tests (tests/test_adapter_config_api.py, 30 cases):
  - GET grouped, per-adapter, single key
  - GET per-adapter returns [] for adapters with zero keys (itd_511)
  - PUT updates value, GET shows new value, accessor returns new value
    (proves cache invalidation propagates to the in-process accessor)
  - PUT type validation per (int, float, str, bool, json) incl. edge cases:
    int rejects str + fractional float + bool but accepts whole-number float;
    float accepts int + float, rejects bool; bool rejects int; str rejects
    other types; json accepts list / dict / None
  - PUT 404 on unknown key, 400 on missing value field
  - POST reset restores default + invalidates cache
  - GET /api/adapter-meta: include_in_llm_context defaults match registry
    (central / geocoder false, rest true)
  - PUT meta partial update: only provided fields change
  - PUT meta rejects non-bool include_in_llm_context, empty display_name,
    unknown adapter

Test count: 731 -> 761 (+30 API cases, 0 regressions).

Refs audit doc v0.6-phase1-audit.md Section A keystone + finding #4.
2026-06-05 18:50:30 +00:00
72a7a90f4d fix(notifications): test shows live data, not just canned examples
- Test always shows current data for the rule's feed categories
- RF rules show live SFI/Kp/R/S/G and ducting conditions
- Weather rules show active NWS alert count and headlines
- Fire rules show active fire/hotspot count
- Stream rules show current gauge readings
- Mesh rules show current health score and infra status
- Send Current Conditions delivers live snapshot through channel
- Send Test Alert delivers example through channel
- Send Live Alert available when real conditions match

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-13 18:08:09 -06:00
4331bcb7e1 feat(dashboard): React frontend scaffold with overview page
- Vite + React 18 + TypeScript + Tailwind CSS
- Dashboard overview with health gauge, pillar bars, alerts
- WebSocket hook for real-time updates
- Layout with sidebar navigation and live indicator
- Placeholder pages for Mesh, Environment, Config, Alerts
- Dark theme ops center aesthetic with JetBrains Mono

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-12 10:28:12 -06:00