fix: gate the "real posterior draws" claim on per-group coverage
useDrawDistribution's status is an any-group signal -- 'ready' means at least
one group came back with draws -- but both charts used it chart-wide to hide
<ApproxNote /> and print a caption claiming every box/ridge is real draws.
Individual boxes and ridges already fall back per-group, so the note and
caption were the only things over-claiming.
This is reachable with real data, not just in theory: for LEAID 0400311 (AZ,
unified_m4_mod) the estimates API returns all 8 race x sex groups but the
parquet shard contains draws for WH_M only. The chart reported 'ready' and hid
the note while 7 of its 8 ridges were the analytic approximation.
New src/utils/drawGroups.js owns the "RACE_SEX" key format (previously
duplicated across the hook and both charts) and a hasDrawsForAll() coverage
check. Each chart now checks the groups it actually renders -- RACE_ORDER x its
sex panels/columns -- rather than everything the API returned. A null map
(loading, or a fetch that failed outright) fails the check, so the error path
still shows the note.
Also fixes a stale-state hazard in the same hook: the input guard ran before
setStatus('loading')/setDrawsByGroup(null), so switching to a model whose
groups list is empty (that model's upstream fetch failed) left status at
'ready' with the previous model's draws still in state. Verified by
instrumenting the hook: with the old ordering, switching from a ready model to
an empty one kept status 'ready' and all 8 previous draw keys; with the reset
moved above the guard it correctly resets to 'loading' with no draws.
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Shared key format and coverage check for the per-group posterior draws
|
||||
* returned by `useDrawDistribution`. Both the hook (which builds the map) and
|
||||
* the charts (which read it, and decide whether to claim "real draws") go
|
||||
* through here so the key format lives in exactly one place.
|
||||
*/
|
||||
|
||||
/** Canonical key for one race×sex group in a `drawsByGroup` map. */
|
||||
export function groupKey(race, sex) {
|
||||
return `${race}_${sex}`
|
||||
}
|
||||
|
||||
/**
|
||||
* True only when *every* group in `groups` has a non-empty draw array.
|
||||
*
|
||||
* `useDrawDistribution`'s `status` is an any-group signal: it reports 'ready'
|
||||
* as soon as one group has real draws. Charts fall back per-group, so the
|
||||
* chart-wide "these are real posterior draws" note/caption must be gated on
|
||||
* complete coverage instead — a group can be missing because its enrollment is
|
||||
* falsy or because its (LEAID, RACE, SEX) isn't in the parquet shard.
|
||||
*
|
||||
* Returns false for an empty group list (nothing rendered means nothing to
|
||||
* claim) and for a null map (loading, or the fetch failed outright).
|
||||
*
|
||||
* @param {Record<string, number[]> | null | undefined} drawsByGroup
|
||||
* @param {Array<{race: string, sex: string}>} groups - the groups a chart is
|
||||
* actually rendering, not everything the API returned.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function hasDrawsForAll(drawsByGroup, groups) {
|
||||
if (!drawsByGroup || !groups?.length) return false
|
||||
return groups.every((g) => (drawsByGroup[groupKey(g.race, g.sex)]?.length ?? 0) > 0)
|
||||
}
|
||||
Reference in New Issue
Block a user