Skip to main content
Version: v0.1.8

v0.0.77 — Bulk Diagnose apply + asyncpg coerce + persistent fact tables

Released: 2026-05-18. Three commits.

Diagnose drawer — bulk Apply-to-all

Slice 4c on top of the v0.0.76 dashboard-level aggregator. In the dashboard Diagnose drawer, every expanded code section gets an Apply to all (N) button when every affected card has a fix payload. The iterator walks cards sequentially (backend writes serialize cleanly), source-tagged diagnose_drawer:<CODE> so slice 5b autofix telemetry captures every Apply with its originating finding code.

Per-card row inline UI:

  • Spinner while the iterator is mid-flight and this card hasn't been reached yet.
  • ✓ on success, ✗ + error message on failure.
  • "no fix" label (italic, grey) on cards whose finding lacks an auto-fix (e.g. UNBOUND_PARAM_NO_MATCH).

Mixed groups (some cards fixable, some not) disable the bulk button with a tooltip steering the operator to drill per-card for the no-fix subset — the bulk action is all-or-nothing by design so partial-success doesn't masquerade as a clean fix. Drill-through is locked during the bulk apply so a stacked per-card drawer can't race against the active mutation. After the loop: reload + re-execute + re-run aggregator so the drawer's by_code shrinks (fully-healed cards drop off).

Dashboards — date/number parameter coercion for asyncpg

Bug 5 from the the team post-v0.0.76 cluster: after the wire-filter double-wrap fix in v0.0.76 cleared the column-not-exist error, the inner SQL's parameter binding hit a new asyncpg DataError:

'str' object has no attribute 'toordinal'
SELECT COUNT(*) FROM marts.fact_appointment
WHERE appointment_date BETWEEN $1 AND $2

asyncpg's native type adapters call obj.toordinal() on bind values targeting DATE / TIMESTAMP columns. Parameter values were flowing through as ISO strings, so the adapter raised at bind time. Earlier drivers (psycopg2) tolerated string → date auto-coercion; asyncpg does not.

Fix: new _coerce_parameter_value / _coerce_parameter_values helpers in routers/dashboards.py that map declared parameter types (datedatetime.date, numberint / float) before _apply_parameters substitutes binds. Both execute paths (single card + execute-all) coerce after _merge_parameter_defaults. Text / list / unknown types pass through unchanged — drivers accept strings for VARCHAR/TEXT/array columns natively. Malformed values fall through too: let the DB driver surface a more informative error than the parser would.

Webapp — mock cards swap onto persistent fact tables

A pattern for moving demo cards off inline VALUES and onto real per-tenant upload tables. Before: cards embedded mock rows directly in SQL — swapping to real data meant editing each card's SQL. After: each card SELECTs from a per-tenant _uploads table. Real-data swap is now a re-upload (xlsx with the same column shape) — no card edits, no rebuild. Seed + swap scripts generate / upload / materialize the tables and PATCH the cards over to them.