Found while working #16203. ⛔ Not that card's defect class (that one is the operand's TYPE) and ⛔ deliberately left unchanged there; the min/max half of this same idiom moved with that card because min/max were its subject.
The divergence, driven
packages/services/service-analytics/src/preview-evaluator.ts, aggregate():
case 'avg': return nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : 0;
The : 0 is the whole defect. emptyGroupValueFor (packages/spec/src/data/aggregation-policy.ts) is the platform's own ruling on this question and says the opposite in prose that reads like it was written for this line:
Counting no rows is 0 and summing them is 0: those are measured facts, not missing data. Averaging, minimising or maximising no rows is undefined — there is nothing to average — and must stay null rather than be flattened to a zero that reads as a real measurement.
Measured on one dataset, one row set, two AnalyticsService instances differing only in draftRowsResolver (the live half is NativeSQLStrategy's SQL on a real SQLite through sql.js, seeded from the same rows). The meals group's amount is NULL in every row:
LIVE [{"category":"meals","avg_amount":null,"sum_amount":0, ...}]
PREVIEW [{"category":"meals","avg_amount":0, "sum_amount":0, ...}]
sum agrees at 0 — that is the identity, and fillEmptyGroups writes it deliberately. avg does not: the preview reports a measurement nobody made, and 0 is a plausible-looking average that a reader cannot tell from a real one.
Scope note
The same : 0 idiom stood on the min/max arms until #16203, which replaced it with null on those two and cites the same policy function. avg was left untouched there on purpose: it was outside that card's statement, and moving it is a value change on a published wire surface that deserves its own review rather than a rider.
Dedup
Searched (search_issues, repo-scoped; repo-scoped REST answers 403 for this session). The zero-match is a reading, not a silent zeroing — the same query returned #15546, #11065, #11151 and #6815, four adjacent aggregate-divergence cards known to exist. ⛔ Not a duplicate of #15546: that is sum (where 0 IS the ruled identity) across driver-sql and the objectql in-memory tier — a different aggregate on different faces. The relationship is worth reading together, though: both are "which face decides what an empty group means".
Related
#16203 · #15546 · #4708 (the card fillEmptyGroups and emptyGroupValueFor came from).
Found while working #16203. ⛔ Not that card's defect class (that one is the operand's TYPE) and ⛔ deliberately left unchanged there; the
min/maxhalf of this same idiom moved with that card becausemin/maxwere its subject.The divergence, driven
packages/services/service-analytics/src/preview-evaluator.ts,aggregate():The
: 0is the whole defect.emptyGroupValueFor(packages/spec/src/data/aggregation-policy.ts) is the platform's own ruling on this question and says the opposite in prose that reads like it was written for this line:Measured on one dataset, one row set, two
AnalyticsServiceinstances differing only indraftRowsResolver(the live half isNativeSQLStrategy's SQL on a real SQLite through sql.js, seeded from the same rows). Themealsgroup'samountis NULL in every row:sumagrees at0— that is the identity, andfillEmptyGroupswrites it deliberately.avgdoes not: the preview reports a measurement nobody made, and0is a plausible-looking average that a reader cannot tell from a real one.Scope note
The same
: 0idiom stood on themin/maxarms until #16203, which replaced it withnullon those two and cites the same policy function.avgwas left untouched there on purpose: it was outside that card's statement, and moving it is a value change on a published wire surface that deserves its own review rather than a rider.Dedup
Searched (
search_issues, repo-scoped; repo-scoped REST answers 403 for this session). The zero-match is a reading, not a silent zeroing — the same query returned #15546, #11065, #11151 and #6815, four adjacent aggregate-divergence cards known to exist. ⛔ Not a duplicate of #15546: that issum(where0IS the ruled identity) across driver-sql and the objectql in-memory tier — a different aggregate on different faces. The relationship is worth reading together, though: both are "which face decides what an empty group means".Related
#16203 · #15546 · #4708 (the card
fillEmptyGroupsandemptyGroupValueForcame from).