Expose load segments and adjustment notes as separate pivot columns

The label on a baseline or reference load ("Open Orders", "Prior Year")
lived only on the pf.log row; the data stream was a straight dump of the
forecast table, so Perspective saw pf_logid and never the name.

The stream now joins pf.log and emits two columns rather than one, because
commingling them makes neither useful: pf_segment names the load a row came
from, and is '(adjustment)' for everything else; pf_note carries the free
text on scale/recode/clone and is null on loads. The operation routes stamp
the same two fields on the rows they push back incrementally.

The tag column those labels belong in is declared in 01_schema.sql but
predates some installs, so the backfill seeding it from note joins the
ALTER already there. Adjustment notes are free text, not labels, and are
left alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Trowbridge 2026-09-16 17:42:22 -04:00
parent 0e87f22e24
commit 81c2324220
2 changed files with 27 additions and 4 deletions

View File

@ -267,7 +267,16 @@ module.exports = function(pool) {
await client.query('BEGIN'); await client.query('BEGIN');
await client.query(` await client.query(`
DECLARE pf_cur CURSOR FOR DECLARE pf_cur CURSOR FOR
SELECT * FROM ${tbl} SELECT t.*
,CASE WHEN l.operation IN ('baseline','reference')
THEN COALESCE(NULLIF(l.tag, ''), NULLIF(l.note, ''), '(unlabeled load)')
ELSE '(adjustment)' END AS pf_segment
,CASE WHEN l.operation IN ('baseline','reference')
THEN NULL
ELSE COALESCE(NULLIF(l.tag, ''), NULLIF(l.note, '')) END AS pf_note
FROM ${tbl} t
LEFT JOIN pf.log l
ON l.id = t.pf_logid
`); `);
// Accumulate into column arrays (not row objects) to avoid allocating one JS // Accumulate into column arrays (not row objects) to avoid allocating one JS
@ -550,7 +559,8 @@ module.exports = function(pool) {
await client.query('COMMIT'); await client.query('COMMIT');
committed = true; committed = true;
const rows = allRows.map(r => ({ ...r, pf_note: note || null, pf_op: 'scale' })); const opLabel = (req.body.tag || '').trim() || note || null;
const rows = allRows.map(r => ({ ...r, pf_segment: '(adjustment)', pf_note: opLabel, pf_op: 'scale' }));
res.json({ res.json({
rows, rows,
rows_affected: rows.length, rows_affected: rows.length,
@ -609,7 +619,8 @@ module.exports = function(pool) {
} }
await client.query('COMMIT'); await client.query('COMMIT');
committed = true; committed = true;
const rows = allRows.map(r => ({ ...r, pf_note: note || null, pf_op: 'recode' })); const opLabel = (req.body.tag || '').trim() || note || null;
const rows = allRows.map(r => ({ ...r, pf_segment: '(adjustment)', pf_note: opLabel, pf_op: 'recode' }));
res.json({ rows, rows_affected: rows.length, slices_applied: units.length }); res.json({ rows, rows_affected: rows.length, slices_applied: units.length });
} finally { } finally {
if (!committed) try { await client.query('ROLLBACK'); } catch {} if (!committed) try { await client.query('ROLLBACK'); } catch {}
@ -665,7 +676,8 @@ module.exports = function(pool) {
} }
await client.query('COMMIT'); await client.query('COMMIT');
committed = true; committed = true;
const rows = allRows.map(r => ({ ...r, pf_note: note || null, pf_op: 'clone' })); const opLabel = (req.body.tag || '').trim() || note || null;
const rows = allRows.map(r => ({ ...r, pf_segment: '(adjustment)', pf_note: opLabel, pf_op: 'clone' }));
res.json({ rows, rows_affected: rows.length, slices_applied: units.length }); res.json({ rows, rows_affected: rows.length, slices_applied: units.length });
} finally { } finally {
if (!committed) try { await client.query('ROLLBACK'); } catch {} if (!committed) try { await client.query('ROLLBACK'); } catch {}

View File

@ -64,6 +64,17 @@ CREATE TABLE IF NOT EXISTS pf.log (
ALTER TABLE pf.log ADD COLUMN IF NOT EXISTS tag text; ALTER TABLE pf.log ADD COLUMN IF NOT EXISTS tag text;
CREATE INDEX IF NOT EXISTS log_tag_idx ON pf.log (tag) WHERE tag IS NOT NULL; CREATE INDEX IF NOT EXISTS log_tag_idx ON pf.log (tag) WHERE tag IS NOT NULL;
-- seed tags for loads that predate the column: a baseline/reference note is the
-- segment's name ('Open Orders', 'Prior Year'), which is exactly what tag holds.
-- Adjustment notes are free text, not labels, so they are left alone.
UPDATE pf.log
SET tag = note
WHERE TRUE
AND tag IS NULL
AND note IS NOT NULL
AND note <> ''
AND operation IN ('baseline', 'reference');
-- generated operation SQL per source, stored after col_meta is configured -- generated operation SQL per source, stored after col_meta is configured
CREATE TABLE IF NOT EXISTS pf.sql ( CREATE TABLE IF NOT EXISTS pf.sql (
id serial PRIMARY KEY, id serial PRIMARY KEY,