Type a slice's values before filtering the ledger with them
A slice carries every value as a string -- built from filters the grid reports, and shaped to survive JSON on the way to the API. Perspective matches on type, and a string '2027' against an integer column is not a filter that matches nothing, it is a filter that is dropped. So the pivot's own season filter never reached the ledger: with the grid scoped to sseas_e = 2027 the ledger totalled 956,485.13 against a cell reading 921,225.71, the difference being eleven rows of a baseline segment whose shipments fall in the next season. Only dates were being coerced, and only because someone had hit this before with them. Values are now typed against the loaded table's schema rather than against col_meta's role, which is the thing that actually decides the match. The server side was already right -- Postgres casts the literal -- and returns 921,225.71 for the same slice. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f94aa4ec99
commit
c6ef350283
@ -419,22 +419,31 @@ export default function Forecast({ sources = [], sourceId, versions = [], versio
|
||||
const dateNames = new Set(colMetaRef.current.filter(c => c.role === 'date').map(c => c.cname))
|
||||
const ITER_ORDER = ['baseline', 'scale', 'recode', 'clone']
|
||||
|
||||
// A slice carries every value as a string -- it is built from filters the
|
||||
// grid reports and from a payload that has to survive JSON. Perspective
|
||||
// matches on type, so a string '2027' against an integer column does not
|
||||
// filter to nothing, it is dropped: the ledger then totalled rows the pivot
|
||||
// was hiding, which is how a season filter on sseas_e went unnoticed while
|
||||
// the numbers disagreed by exactly the out-of-season rows.
|
||||
const schema = await tableRef.current.schema()
|
||||
const typed = (col, val) => {
|
||||
switch (schema[col]) {
|
||||
case 'integer': case 'float': return Number(val)
|
||||
case 'boolean': return val === true || val === 'true'
|
||||
case 'date': case 'datetime': return Number(val)
|
||||
default: return String(val)
|
||||
}
|
||||
}
|
||||
|
||||
async function totalsFor(sliceObj) {
|
||||
const filters = [
|
||||
// pf_segment and pf_bucket are computed server-side but are ordinary
|
||||
// string columns in the loaded table, so here they filter directly. They
|
||||
// have to be applied, or the ledger totals a wider selection than the
|
||||
// operation will write.
|
||||
...Object.entries(sliceObj)
|
||||
.filter(([col]) => COMPUTED_SLICE_COLS.has(col))
|
||||
.map(([col, val]) => [col, '==', String(val)]),
|
||||
...Object.entries(sliceObj)
|
||||
.filter(([col]) => dimNames.has(col))
|
||||
.map(([col, val]) => [col, '==', val]),
|
||||
...Object.entries(sliceObj)
|
||||
.filter(([col]) => dateNames.has(col))
|
||||
.map(([col, val]) => [col, '==', Number(val)]),
|
||||
]
|
||||
// columns in the loaded table, so here they filter directly. They have to
|
||||
// be applied, or the ledger totals a wider selection than the operation
|
||||
// will write.
|
||||
const filters = Object.entries(sliceObj)
|
||||
.filter(([col]) => COMPUTED_SLICE_COLS.has(col) || dimNames.has(col) || dateNames.has(col)
|
||||
|| schema[col] !== undefined)
|
||||
.map(([col, val]) => [col, '==', typed(col, val)])
|
||||
const view = await tableRef.current.view({ filter: filters })
|
||||
const rows = await view.to_json()
|
||||
await view.delete()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user