pf_app
Go to file
Paul Trowbridge 654a368672 Add static display-grain pre-aggregation (col_meta.in_grain)
Ship rows pre-aggregated to the grain the pivot displays instead of raw
forecast rows. This is Path B from pf_perspective_options.md: it keeps
Perspective's native WASM engine — so expand/collapse/depth/sort/filter
all still work — and fixes load time by cutting rows, not transport.

Measured on pf.fc_osm_stack_20 at pending_rep x customer x smon:
534,902 -> 6,154 rows (~87x), pf_gkey unique across all 6,154, and both
measures reconcile exactly to the raw totals.

The grain is static: flagged once per source in Setup and baked into the
stored pf.sql templates, so load and operations agree by construction.
Sources with no flagged column keep the previous raw-row behaviour, so
this is backward compatible.

- pf.col_meta gains in_grain; grainOf() in lib/sql_generator.js is the
  single definition of the grain and is reused by routes/log.js.
- New get_agg template + GET /api/versions/:id/agg, generated only when a
  grain is defined. Regenerating drops templates no longer produced, so
  clearing the grain falls back to /data.
- scale/recode/clone now aggregate their own new rows to grain before
  returning. Because pf_logid is part of pf_gkey those keys are always
  new, so table.update() appends and the view re-sums — the Excel
  pivot-cache pattern, no bucket recomputation.
- Undo reports pf_gkeys (RETURNING cannot take DISTINCT, so the delete
  feeds a CTE that reduces to distinct keys); the client removes those
  index values and the view re-sums.
- pf_gkey is concat_ws(chr(31), COALESCE(col::text, chr(30)), ...).
  The separator and NULL sentinel are load-bearing: plain concat_ws skips
  NULLs, so ('a',NULL) and (NULL,'a') would collide and silently merge two
  groups into one indexed row.
- Forecast.jsx reads col_meta first to pick /agg vs /data; the Arrow
  streaming logic is extracted to fetchArrow() since both share it.
- Setup.jsx gains a grain checkbox and shows the resulting grain.
- 01_schema.sql: move the col_meta ALTERs after its CREATE TABLE — they
  referenced the table before it existed on a fresh install.

All six generated statements verified to plan against the real forecast
table; the in_grain column has been added to the dev database.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 16:25:38 -04:00
lib Add static display-grain pre-aggregation (col_meta.in_grain) 2026-08-18 16:25:38 -04:00
public Add UX mockup and update spec with navigation direction 2026-04-25 14:56:29 -04:00
routes Add static display-grain pre-aggregation (col_meta.in_grain) 2026-08-18 16:25:38 -04:00
setup_sql Add static display-grain pre-aggregation (col_meta.in_grain) 2026-08-18 16:25:38 -04:00
ui Add static display-grain pre-aggregation (col_meta.in_grain) 2026-08-18 16:25:38 -04:00
.env.example Initial commit — pivot forecast application 2026-04-01 07:59:05 -04:00
.gitignore Scaffold React/Vite/Tailwind UI with 3-step Setup → Baseline → Forecast flow 2026-04-25 16:28:45 -04:00
CLAUDE.md Add static display-grain pre-aggregation (col_meta.in_grain) 2026-08-18 16:25:38 -04:00
install.sh Initial commit — pivot forecast application 2026-04-01 07:59:05 -04:00
package-lock.json Fix forecast data load and add byte-progress UI 2026-04-28 19:51:39 -04:00
package.json Add CLAUDE.md project instructions and license field to package.json 2026-05-02 20:47:15 -04:00
PERSPECTIVE.md Add PERSPECTIVE.md config/deploy reference; fix CLAUDE.md distribution link 2026-06-15 23:49:53 -04:00
pf_perspective_options.md Record DuckDB virtual-server spike findings; choose display-grain pre-aggregation 2026-08-17 21:47:55 -04:00
pf_spec.md Add static display-grain pre-aggregation (col_meta.in_grain) 2026-08-18 16:25:38 -04:00
pf.sh Add pf.sh — interactive deployment and service management script 2026-05-02 20:58:39 -04:00
server.js Fix forecast data load and add byte-progress UI 2026-04-28 19:51:39 -04:00