Skip to content

feat(fields): the broker holds the pedestrian wind field itself, per direction - #41

Merged
kastnerp merged 2 commits into
mainfrom
claude/pedestrian-fields-db
Oct 1, 2026
Merged

kastnerp merged 2 commits into
mainfrom
claude/pedestrian-fields-db

Conversation

@kastnerp

Copy link
Copy Markdown
Member

What

The broker now stores the campaign's published answer — |U| at 1.75 m above grade on the 2 m grid over the 1008 m core, per wind direction — in its own database, instead of only pointing at an archive on the Syncthing master. Version 0.24.0.

  • case_fields table (SQLite BLOB / Postgres BYTEA): one row per (case, direction, height). The blob is the node's umag/1 container stored verbatim: gzip("UMAG" | u32 version | u32 header length | header JSON | float32 LE nx*ny), NaN inside a building, ~0.75 MB per direction. The header fills the queryable columns (grid, angle, coverage, U_ref, 99.9th percentile of |U|), so they cannot disagree with the bytes.
  • PUT /v1/cases/{id}/fields/{direction}?lease_id= — from the node the moment a direction is solved. Lease-gated exactly like /v1/parts (409 = lease gone / not this credential's). 413 past CASEBROKER_FIELD_MAX_BYTES (64 MiB default), 422 for a blob that is not a field, naming what is wrong with it. A second upload for the same direction/height replaces the first.
  • GET /v1/cases/{id}/fields (records, no bytes; also on the case record as fields) and GET /v1/cases/{id}/fields/{direction}?height_m= (the blob, strong ETag, Cache-Control: private, max-age=86400, 304 on match).
  • Dashboard: the field viewer asks the broker first and fetches each direction lazily as it is looked at. Exact values under the cursor (a .wfld carried a byte), one colour scale across every direction of a case from the listing's percentiles, no arrows (|U| only). The wind-field source / open-from-disk .wfld path stays as the fallback for cases with nothing in the broker.

Why

Patrick's decision, 2026-09-28: the broker's DB is moving to a 6 TB Postgres on the Synology, so storage is no longer the constraint. The .wfld route required someone to serve a folder off the master; with the field in the DB a case's answer is one authenticated GET away.

Reviewer notes

  • Companion Eddy3D change (node side: MetaFOAM.Deploy.PedestrianField reader, CaseFields uploader, BrokerClient.PutFieldAsync) lives on the Eddy3D branch claude/sim-nodes-wind-probing-db-9aec5f. Old nodes are unaffected; a node from before fields simply never PUTs.
  • db.decode_umag validates the container only (magic, version, header, byte count) — the broker never re-derives values.
  • Found while verifying the dashboard in a browser against a seeded local broker: a direction whose fetch failed was re-fetched on every redraw (495 requests in seconds). Fixed; it offers a retry button now.
  • uv.lock changed only because of the version bump.
  • Tests: tests/test_case_fields.py (14 tests: DB layer, HTTP round trip, refusals, storage). Full suite: 818 passed, 30 skipped.
  • Once the Synology Postgres is live, set CASEBROKER_DB_QUOTA_MB so /v1/storage stops assuming the 500 MB Supabase plan.

🤖 Generated with Claude Code

…direction

Every other artefact of a case lives on the Syncthing master and the broker
records where; the published field -- |U| at 1.75 m above grade on the 2 m
grid over the 1008 m core, per direction -- is what the campaign is for, and at
about a megabyte a direction it fits the fleet's own database now that it is no
longer a 500 MB plan (Patrick, 2026-09-28: a 6 TB Postgres on the Synology).

- case_fields: one row per (case, direction, height); the blob is the node's
  umag/1 container stored verbatim (gzip of "UMAG" | u32 version | u32 header
  length | header JSON | float32 LE nx*ny, NaN inside a building). The header
  fills the columns a browser filters by, so they cannot disagree with the bytes.
- PUT /v1/cases/{id}/fields/{direction}?lease_id= from the node the moment a
  direction is solved, lease-gated as /v1/parts; 413 past the size limit, 422
  for a blob that is not a field, naming what is wrong with it.
- GET /v1/cases/{id}/fields (records, no bytes; also on the case record) and
  GET /v1/cases/{id}/fields/{direction} (the blob, strong ETag, a day of cache).
- Dashboard: the field viewer reads the broker first and fetches each direction
  as it is looked at -- exact values under the cursor, one colour scale across
  the case from the listing's 99.9th percentiles. The .wfld source stays as the
  fallback for cases that have nothing there. A direction whose fetch failed
  used to be fetched again on every redraw (495 requests in seconds, seen in
  the browser); it offers a retry now.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lds; 0.25.0

Only the version lines conflicted. pyproject.toml and uv.lock are 0.25.0, a
feature on top of main's 0.24.1, and the CHANGELOG entry for the fields moves
from [0.24.0] to [0.25.0] above main's 0.24.1 and 0.24.0. app.py, db.py,
DOMAIN.md and dashboard.html merged on their own: a thermal case still hides
the pedestrian-wind section, and the field viewer still reads the broker first.
Full suite on the merged tree: 840 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kastnerp
kastnerp merged commit cda32ab into main Oct 1, 2026
5 checks passed
@kastnerp
kastnerp deleted the claude/pedestrian-fields-db branch October 4, 2026 18:44
kastnerp added a commit that referenced this pull request Oct 6, 2026
…direction (#41)

case_fields table; PUT /v1/cases/{id}/fields/{direction}?lease_id= from the node as each direction is solved (lease-gated, 413 past the size limit, 422 for a blob that is not a field); GET /v1/cases/{id}/fields and /fields/{direction}; the dashboard's field viewer reads the broker first, .wfld stays the fallback. Merged with main after #42 (version 0.25.0); 840 tests green on the merged tree.

Eddy3D #962 (already on dev) is the node half: until this deploys, a dev node's field upload gets 404 and its cases complete without fields in the database.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant