Commit Graph

32 Commits

Author SHA1 Message Date
1a3209cbc2 Document Perspective architecture options
Captures current /data path (with bug history that forced single-batch
encoding), and four candidate redesigns: optimize the existing encoder,
DuckDB-WASM with Parquet, server-side DuckDB virtual server, and the
hybrid read-from-WASM/write-via-deltas variant. Each option weighed
against the forecasting write path, not just initial load. Intended as
a decision record so context survives a lost conversation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 20:21:19 -04:00
6b69b00645 Undo deletes rows in place instead of reloading the forecast
Perspective table is now created with index: 'pf_id'. Delete endpoints
return the pf_ids they removed; the client calls table.remove(pf_ids)
in undoEntry. Avoids the full /data refetch that dominated undo time.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 20:21:13 -04:00
bf85f11b5f Expose pf_note/pf_op in forecast data; fix tables list duplicates
/data now joins pf.log to surface note text and operation type as
pf_note/pf_op so users can pivot/bridge by assumption. Joining at
fetch time avoids storing notes per row and keeps edits live.

/api/tables joined pg_class by name only with namespace filtered in
a separate LEFT JOIN, which cross-producted table names that exist in
multiple schemas. Restructured so namespace participates in the join.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 19:51:45 -04:00
a9ca58a845 Fix forecast data load and add byte-progress UI
pg now returns bigint/numeric as JS numbers so Arrow infers Int/Float64
instead of Dictionary<Utf8>. /data accumulates rows and emits a single
record batch to avoid dictionary REPLACEMENT messages that crash
Perspective's WASM reader. Forecast view streams the response body and
shows received/total bytes while loading. Drops stale public/ static
middleware that was shadowing the React build at /.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 19:51:39 -04:00
a6e6efd36e Switch /data endpoint to Arrow IPC stream with pg cursor batching
Server streams rows from a pg cursor in 10k-row batches, building Arrow
record batches incrementally and piping them as chunked HTTP response —
Node.js heap stays bounded regardless of dataset size.

Client fetches as arrayBuffer() and loads directly into Perspective worker
(native Arrow path, no JSON deserialization). X-Row-Count header drives
a non-blocking banner for datasets >= 500k rows. validCols now derived
from col_meta rather than from row keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 22:57:47 -04:00
11f5b02fc4 Spec: add OR filter groups, raw_where escape hatch, and Arrow IPC streaming for large datasets
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 22:50:02 -04:00
4a4cb80189 Add light/dark mode with theme toggle 2026-04-25 23:29:25 -04:00
bd5ea1c60e Restore functional changes lost in dark theme revert
Re-applies fetchCurrentTotals dimension-only filter (prevents Perspective errors
from split_by/expression columns in the filter), toolbar three-group reorganization
(Layout | Expand | Data with dividers), always-visible Save as…, msg in toolbar,
resizable panel, change log modal with undo and inline note editing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 21:32:00 -04:00
8f009e468e Fix cleanLayout stripping expression columns (e.g. Year) on restore
Expression columns (bucket, computed) are defined in cfg.expressions and
are valid pivot axes, but weren't in validCols (raw table columns), so
they were filtered out of group_by/split_by on every layout restore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 21:24:01 -04:00
742d4b4cc4 Revert "Dark theme for Forecast view controls and operation panel"
This reverts commit cda3943515.
2026-04-25 21:23:43 -04:00
cda3943515 Dark theme for Forecast view controls and operation panel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 21:20:40 -04:00
6449fff573 Add change history modal with undo and note editing
- GET /api/versions/:id/log — log entries with row counts via JOIN
- DELETE /api/log/:logid — undo in a transaction (delete fc rows + log entry)
- PATCH /api/log/:logid — update note text
- History button opens a modal: op badge, slice, editable note, row count, Undo per entry
- Undo triggers full Perspective table reload via initViewer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:36:18 -04:00
3bdd7d0028 Fix Forecast pivot row click, buildWhere, and add resizable panel
- Fix perspective-click handler to use event filter triples instead of
  __ROW_PATH__ — Perspective encodes row position as [col,'==',val] in
  detail.config.filter
- buildWhere now skips unrecognised slice keys (e.g. pf_iter) instead of
  throwing, so only dimension columns reach the WHERE clause
- Add draggable resize handle on the operation panel (160–480px)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 20:21:13 -04:00
af52845523 Unify baseline/reference into one form; fix timeline for both types
- Baseline.jsx: merge Reference section into Add Segment form with baseline/reference toggle; segment rows now clickable to expand stored WHERE clause + timeline; date filter inputs use type="date" for date-role columns
- Timeline.jsx: add type prop ('baseline'|'reference'); reference band uses purple; single-band height shrinks to 52px; canvas uses requestAnimationFrame to fix offsetWidth=0 on mount
- operations.js: reference route now accepts where_clause like baseline (drops date_from/date_to)
- sql_generator.js: reference SQL template uses {{filter_clause}} instead of hardcoded BETWEEN

Note: existing sources need Generate SQL re-run to pick up the new reference template.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:45:03 -04:00
dc090fe394 Scaffold React/Vite/Tailwind UI with 3-step Setup → Baseline → Forecast flow
- ui/: React + Vite + Tailwind app (Setup, Baseline, Forecast views, collapsible sidebar, status bar, canvas timeline)
- server.js: serve built UI from public/app/
- package.json: add build script (cd ui && npm run build)
- routes/sources.js: default new col_meta role to 'dimension' instead of 'ignore'
- .gitignore: exclude public/app/ build output
- pf_spec.md: update tech stack, nav, frontend section, and project status to reflect current implementation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:28:45 -04:00
dd993e989c Add UX mockup and update spec with navigation direction
- HTML mockup with collapsible side nav, 3-step flow (Setup/Baseline/Forecast)
- Canvas-based timeline preview in baseline segment form
- Table peek modal, status bar, help popovers
- Spec updated: 3-step mental model, AG Grid replacement note

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 14:56:29 -04:00
9084a87ea5 Update spec: Perspective pivot, current project status
- Replace AG Grid pivot references with Perspective throughout
- Document pivot interaction flow, default layout, slice extraction,
  incremental row streaming
- Add Project Status section: what's working, known UX issues,
  branch status

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 07:24:20 -04:00
4c71049bf0 Fix Perspective viewer: pass table name in restore, simplify listener
- restore() must include table: tableName so viewer knows which table
- Replace cloneNode listener reset with named _pspClickHandler pattern
- Always inject table name into saved layouts on restore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 23:14:22 -04:00
5171b9770c Fix: restore AG Grid script tag removed by mistake
Sources/Versions/Log grids still use AG Grid — only the Forecast
pivot was replaced with Perspective.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 23:13:14 -04:00
368127e098 Replace AG Grid pivot with Perspective viewer in Forecast view
- Swap AG Grid CSS/JS for Perspective CDN imports (4.4.0)
- Replace #pivot-grid div with <perspective-viewer> web component
- Add loadPerspective() singleton, initPerspectiveViewer(data)
- Build default layout from col_meta (dims → group_by, date → split_by)
- Extract slice from perspective-click event.detail.config.filter
  (only == filters on role=dimension columns feed the operation panel)
- table.update() appends operation result rows without full reload
- Save/reset layout buttons per version in localStorage
- Remove expand/collapse buttons (Perspective handles natively)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 23:11:56 -04:00
1df37a5ff1 Refactor sources UI, rename pf_ system cols, replace filter builder with raw SQL
- Sources page: left column with stacked DB tables + registered sources panels,
  right column as full-height column mapping workbench
- Add compact table search, column search, table preview button, delete source button
- Rename fc_table system columns to pf_ prefix (pf_id, pf_iter, pf_logid,
  pf_created_at) to avoid collisions with source table columns like 'id'
- Remove 'filter' col_meta role — any non-ignore column usable in baseline filters
- Replace structured filter row builder with free-form SQL WHERE clause textarea
  and clickable column chips for insertion; fully flexible AND/OR logic
- Baseline segment cards now display raw WHERE clause text + offset

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 00:47:57 -04:00
d49aac70e4 Replace offset year/month spinners with type picklist + value input
Offset Type: [Year|Month|Week|Day]  Offset Value: [n]
Cleaner UX, handles all interval types uniformly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 14:05:25 -04:00
d4962f4376 Fix baseline workbench layout — stack form above segments
Switches from side-by-side panels to stacked layout so the filter
builder has full width and input fields are no longer cramped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 13:45:25 -04:00
5550a57f97 Add baseline workbench — multi-segment additive baseline with filter builder
- New Baseline nav view replaces the simple Load Baseline modal
- Baseline loads are now additive; each segment is independently undoable
- Filter builder: any date/filter-role column, full operator set
- Timeline preview shows source → projected period bars for date BETWEEN filters
- Clear Baseline action deletes all baseline rows and log entries
- DELETE /api/versions/:id/baseline route
- buildFilterClause() added to sql_generator
- filter role added to col_meta editor
- Reminder: re-run generate-sql for each source after this change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 13:27:36 -04:00
7e9ea456b6 Fix baseline request body in spec — unified filters array
Removes the redundant date_from/date_to/date_col fields from the request
body. Period selection is now expressed as a filter condition in the
filters array like any other condition. SQL pattern updated to match
(single {{filter_clause}} token instead of date_range + filter split).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 13:15:17 -04:00
f0c51096ff Update spec: generalize baseline segment filters, timeline preview
Period selection (date range, season, etc.) is now expressed as a
filter condition like any other — no separate date range section.
Preview uses a timeline/number-line bar instead of month chips.
Documents the unified filter builder approach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 13:12:52 -04:00
e9f37e09f2 Add pf_spec.md — application specification
Covers architecture, data model, API routes, SQL patterns, and UI design.
Includes baseline workbench design with multi-segment additive loads,
filter role for col_meta, and date offset for projecting actuals into
the forecast period.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 13:05:39 -04:00
6d8b052eb6 Add date offset to baseline — project actuals into forecast period
The baseline operation now accepts a date_offset interval (e.g. "1 year",
"6 months") and applies it to every date when inserting rows, shifting
historical actuals into the target forecast period.

SQL: {date_col} + '{{date_offset}}'::interval)::date at insert time.
Route: defaults to '0 days' if omitted so existing calls are unaffected.
UI: year/month spinners with a live before→after month chip preview so
the projected landing period is visible before submitting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 12:48:28 -04:00
ddd16bc7a0 Show params column in log grid
Exposes the stored params (e.g. date_from/date_to for baseline/reference)
so the date range used in each operation is visible in the log.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 12:28:03 -04:00
10441a4761 Show baseline/reference form in a modal with live month preview
Replaces the small inline form with a centred modal dialog. When both
dates are selected, a live chip list shows every month covered (up to
36 months) so it is immediately clear what periods will be loaded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 12:24:20 -04:00
cfee3e96b9 Return inserted rows from change operations for incremental grid updates
Instead of re-fetching all forecast data after scale/recode/clone/reference,
the routes now return the inserted rows directly. The frontend uses ag-Grid's
applyTransaction to add only the new rows, eliminating the full reload round-trip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 12:04:28 -04:00
08dc415bfd Initial commit — pivot forecast application
Node.js/Express + PostgreSQL forecasting app with AG Grid Enterprise pivot UI.
Supports baseline, scale, recode, clone operations on configurable source tables.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 07:59:05 -04:00