diff --git a/ui/src/components/BridgeView.jsx b/ui/src/components/BridgeView.jsx
index c4781ae..f61b71d 100644
--- a/ui/src/components/BridgeView.jsx
+++ b/ui/src/components/BridgeView.jsx
@@ -106,7 +106,9 @@ export function buildSteps(rows, {
if (isLoad) { loads.value += v; loads.units += u; loads.rows += 1; continue }
const meta = logMeta[r.pf_logid] || {}
- const tag = (meta.tag || '').trim()
+ // label first, the same precedence pf_segment uses, so the bridge and the
+ // pivot call a step by the same name
+ const tag = (meta.label || meta.tag || '').trim()
const label = tag || (meta.note || '').trim() ||
`${(meta.operation || r.pf_iter || 'adj')}${r.pf_logid != null ? ` #${r.pf_logid}` : ''}`
const key = tag ? `tag:${tag}` : `log:${r.pf_logid}`
diff --git a/ui/src/components/OperationPanel.jsx b/ui/src/components/OperationPanel.jsx
index df16b0b..fcdb223 100644
--- a/ui/src/components/OperationPanel.jsx
+++ b/ui/src/components/OperationPanel.jsx
@@ -287,9 +287,16 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se
const loose = []
const byTag = new Map()
for (const e of entries) {
- if (e.key === 'baseline') { baseline.push({ ...e, label: 'Baseline', kind: 'baseline' }); continue }
const meta = logMeta[e.logid] || {}
- const tag = (meta.tag || '').trim()
+ // Named like every other line: the label the pivot shows, then the older
+ // fallbacks. "Baseline" was hardcoded, so a segment called 03 - New Orders
+ // everywhere else read as "Baseline" here alone.
+ if (e.key === 'baseline') {
+ const name = (meta.label || meta.tag || meta.note || '').trim()
+ baseline.push({ ...e, label: name || 'Baseline', kind: 'baseline' })
+ continue
+ }
+ const tag = (meta.label || meta.tag || '').trim()
if (tag) {
const g = byTag.get(tag) ||
{ key: `tag:${tag}`, label: tag, kind: 'tag', value: 0, units: 0, count: 0, first: e.logid }
@@ -302,7 +309,8 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se
const op = meta.operation || e.iter || 'adjustment'
loose.push({
...e, kind: 'entry', count: 1,
- label: (meta.note || '').trim() || `${op.charAt(0).toUpperCase()}${op.slice(1)} #${e.logid}`,
+ label: (meta.label || meta.note || '').trim()
+ || `${op.charAt(0).toUpperCase()}${op.slice(1)} #${e.logid}`,
})
}
}
@@ -327,6 +335,12 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se
// otherwise prints a row of zeros and leaves the reason to be worked out.
const nothingToAdjust = hasExcl && !total.value && !total.units
+ // One line per immovable segment. Falls back to the combined figure for a
+ // selection whose rows carry no segment name.
+ const exclLines = (currentTotals?.excluded?.bySegment?.length
+ ? currentTotals.excluded.bySegment
+ : (hasExcl ? [{ name: exclName, ...excl }] : []))
+
// the basis decides which line the editable rows are measured from
const basisOf = (key) => {
if (!onTotal) return key === 'price' ? curPrice : total[key]
@@ -468,21 +482,21 @@ function ScaleLedger({ currentTotals, scaleInputs, setScaleInputs, scalePlug, se
{/* Rows the pivot shows but operations cannot write. Listed so the
panel's figures reconcile with what the grid displays. */}
- {hasExcl && (
-
-
- {exclName}
-
+ {exclLines.map(seg => (
+
+ |
+ {seg.name}
+
final
|
{measures.map(m => (
- {m.key === 'price' ? fmtNum(priceOf(excl), m.dp) : fmtNum(excl[m.key], m.dp)}
+ {m.key === 'price' ? fmtNum(priceOf(seg), m.dp) : fmtNum(seg[m.key], m.dp)}
|
))}
- )}
+ ))}
{hasExcl && (
diff --git a/ui/src/views/Forecast.jsx b/ui/src/views/Forecast.jsx
index d0f2d9b..3618c8f 100644
--- a/ui/src/views/Forecast.jsx
+++ b/ui/src/views/Forecast.jsx
@@ -497,7 +497,10 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
// rows the pivot shows but operations cannot write (usually 'reference').
// Kept separate rather than filtered away: the grid total includes them,
// so the panel has to account for them or the two disagree.
- const excluded = { value: 0, units: 0, rows: 0, names: new Set() }
+ // Per segment, not one lump: YTD Sales and Open Orders are different
+ // things, and a single "final" line hides which part of the number is
+ // which.
+ const excluded = { value: 0, units: 0, rows: 0, names: new Set(), bySegment: new Map() }
for (const r of rows) {
const k = r.pf_iter || '?'
const val = valueCol ? (parseFloat(r[valueCol]) || 0) : 0
@@ -510,7 +513,13 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
// Name them by what they are, not by the iter band that happens to
// exclude them: "02 - Prior Year" means something to a forecaster,
// "reference" is the mechanism.
- if (r.pf_segment) excluded.names.add(String(r.pf_segment))
+ const name = String(r.pf_segment || 'excluded')
+ excluded.names.add(name)
+ const seg = excluded.bySegment.get(name) || { name, value: 0, units: 0, rows: 0 }
+ seg.value += val
+ seg.units += uni
+ seg.rows += 1
+ excluded.bySegment.set(name, seg)
continue
}
@@ -583,11 +592,24 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
units: acc.units + (ps.excluded?.units || 0),
rows: acc.rows + (ps.excluded?.rows || 0),
names: new Set([...acc.names, ...(ps.excluded?.names || [])]),
- }), { value: 0, units: 0, rows: 0, names: new Set() })
+ bySegment: (() => {
+ const m = acc.bySegment
+ for (const seg of (ps.excluded?.bySegment?.values?.() || [])) {
+ const t = m.get(seg.name) || { name: seg.name, value: 0, units: 0, rows: 0 }
+ t.value += seg.value; t.units += seg.units; t.rows += seg.rows
+ m.set(seg.name, t)
+ }
+ return m
+ })(),
+ }), { value: 0, units: 0, rows: 0, names: new Set(), bySegment: new Map() })
setCurrentTotals({
byIter, byEntry, total, valueCol, unitsCol, perSlice,
- excluded: { ...excluded, names: [...excluded.names].sort() },
+ excluded: {
+ ...excluded,
+ names: [...excluded.names].sort(),
+ bySegment: [...excluded.bySegment.values()].sort((a, b) => a.name.localeCompare(b.name)),
+ },
excludedIters: [...excludeIters],
})
} catch {
@@ -603,8 +625,9 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
const entries = await fetch(`/api/versions/${vid}/log`).then(r => r.json())
const map = {}
for (const e of entries) map[e.id] = {
+ label: e.label || null,
tag: e.tag || null, note: e.note || null, operation: e.operation,
- bucket: e.bucket || null, seq: e.seq ?? null,
+ bucket: e.bucket || null,
}
setLogMeta(map)
} catch { setLogMeta({}) }
@@ -1486,7 +1509,7 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
.map(([id, m]) => ({
id: Number(id),
operation: m.operation,
- label: (m.tag || m.note || '').trim(),
+ label: (m.label || m.tag || m.note || '').trim(),
}))
.sort((a, b) => a.id - b.id)
|