Skip to content

Commit 55cb22f

Browse files
jhfclaude
andcommitted
fix: Add discriminated union types for jsonb_stats aggregates
Types in app/types.d.ts match jsonb_stats README exactly: - NumericStatAgg: int_agg, float_agg, dec2_agg, nat_agg (with sum, min, max, mean, etc.) - CategoricalStatAgg: str_agg, bool_agg (with counts) - ArrayStatAgg: arr_agg (with count + counts) - DateStatAgg: date_agg (with counts + min/max) Remove duplicate type definitions from atoms/search.ts. Fix .sum access with "sum" in metric narrowing across reports and hierarchy. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 49699ec commit 55cb22f

File tree

8 files changed

+58
-51
lines changed

8 files changed

+58
-51
lines changed

app/src/app/enterprises/[id]/statistical-variables/statistical-variables-form.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ export default function StatisticalVariablesForm({
2727
return (
2828
<form className="space-y-4">
2929
{statDefinitions.map((statDefinition) => {
30-
const value = data?.stats_summary?.[statDefinition.code]?.sum;
30+
const metric = data?.stats_summary?.[statDefinition.code];
31+
const value = metric && "sum" in metric ? metric.sum : undefined;
3132
return (
3233
<DisplayFormField
3334
key={statDefinition.code}

app/src/app/reports/ReportsPageClient.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,15 @@ export default function ReportsPageClient({
6565
...(drillDown.available.region?.map((point) =>
6666
value === "count"
6767
? point.count
68-
: ((point.stats_summary?.[value]?.sum as number) ?? 0)
68+
: (() => { const m = point.stats_summary?.[value]; return m && "sum" in m ? (m.sum as number) : 0; })()
6969
) || [])
7070
);
7171
const categoryMax = Math.max(
7272
0,
7373
...(drillDown.available.activity_category?.map((point) =>
7474
value === "count"
7575
? point.count
76-
: ((point.stats_summary?.[value]?.sum as number) ?? 0)
76+
: (() => { const m = point.stats_summary?.[value]; return m && "sum" in m ? (m.sum as number) : 0; })()
7777
) || [])
7878
);
7979

app/src/app/reports/drill-down-chart.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,10 +131,11 @@ export const DrillDownChart = ({
131131
return <div ref={chartContainerRef} />;
132132
};
133133

134-
const getStatValue = (point: DrillDownPoint, variable: string): number =>
135-
variable === "count"
136-
? point.count
137-
: ((point.stats_summary?.[variable]?.sum as number) ?? 0);
134+
const getStatValue = (point: DrillDownPoint, variable: string): number => {
135+
if (variable === "count") return point.count;
136+
const metric = point.stats_summary?.[variable];
137+
return metric && "sum" in metric ? (metric.sum as number) : 0;
138+
};
138139

139140
const toPointOptionObject = (point: DrillDownPoint, variable: string) => ({
140141
name: `${point.path} - ${point.name}`,

app/src/app/search/components/statistical-unit-table-row.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -379,13 +379,10 @@ export const StatisticalUnitTableRow = ({
379379
const metric = unit.stats_summary[column.stat_code];
380380
let valueToDisplay: number | string | null = null;
381381

382-
if (metric && metric.type === "number") {
383-
// metric is NumberStatMetric, sum is number | undefined
382+
if (metric && ("sum" in metric)) {
384383
valueToDisplay = metric.sum !== undefined ? metric.sum : null;
385384
} else {
386-
// For other types or if metric is undefined, display a placeholder
387-
// as we are only asked to display 'sum' from 'number' type metrics.
388-
valueToDisplay = "-"; // Or null for an empty cell
385+
valueToDisplay = "-";
389386
}
390387
return thousandSeparator(valueToDisplay);
391388
})()}

app/src/app/types.d.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,48 @@ interface ExternalIdents {
1212
[key: string]: number | string;
1313
}
1414

15+
// jsonb_stats aggregate types — see jsonb_stats README.md "Structures in Detail"
16+
17+
/** Numeric aggregate (int_agg, float_agg, dec2_agg, nat_agg) */
18+
interface NumericStatAgg {
19+
type: "int_agg" | "float_agg" | "dec2_agg" | "nat_agg";
20+
count: number;
21+
sum: number;
22+
min: number;
23+
max: number;
24+
mean: number;
25+
sum_sq_diff: number;
26+
variance: number | null;
27+
stddev: number | null;
28+
coefficient_of_variation_pct: number | null;
29+
}
30+
31+
/** Categorical aggregate (str_agg, bool_agg) */
32+
interface CategoricalStatAgg {
33+
type: "str_agg" | "bool_agg";
34+
counts: { [value: string]: number };
35+
}
36+
37+
/** Array aggregate */
38+
interface ArrayStatAgg {
39+
type: "arr_agg";
40+
count: number;
41+
counts: { [element: string]: number };
42+
}
43+
44+
/** Date aggregate */
45+
interface DateStatAgg {
46+
type: "date_agg";
47+
counts: { [isoDate: string]: number };
48+
min: string;
49+
max: string;
50+
}
51+
52+
type StatAgg = NumericStatAgg | CategoricalStatAgg | ArrayStatAgg | DateStatAgg;
53+
54+
/** stats_agg object: { type: "stats_agg", [statCode]: StatAgg } */
1555
interface StatsSummary {
16-
[key: string]: {
17-
[key: string]: number | string;
18-
};
56+
[statCode: string]: StatAgg;
1957
}
2058

2159
interface StatisticalUnit extends Tables<"statistical_unit"> {

app/src/atoms/search.ts

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -233,39 +233,9 @@ export const resetSearchInitializationAtom = atom(
233233
// SELECTION ATOMS - Replace SelectionContext
234234
// ============================================================================
235235

236-
interface ExternalIdents {
237-
[key: string]: string;
238-
}
239-
240-
// Define the structure for metrics within stats_summary
241-
interface BaseStatMetric {
242-
type: "array" | "number" | "string" | "boolean";
243-
}
244-
245-
export interface NumberStatMetric extends BaseStatMetric {
246-
type: "number";
247-
max?: number;
248-
min?: number;
249-
sum?: number; // If present, this is always a number
250-
mean?: number;
251-
count?: number;
252-
stddev?: number;
253-
variance?: number;
254-
sum_sq_diff?: number;
255-
coefficient_of_variation_pct?: number;
256-
}
257-
258-
export interface CountsStatMetric extends BaseStatMetric {
259-
type: "array" | "string" | "boolean";
260-
counts: { [key: string]: number };
261-
}
262-
263-
export type StatMetric = NumberStatMetric | CountsStatMetric;
264-
265-
// Refined StatsSummary: Each key is a stat_code, value is its metric object or undefined
266-
interface StatsSummary {
267-
[statCode: string]: StatMetric | undefined;
268-
}
236+
// Re-export discriminated union types from app/types.d.ts for use in search components
237+
export type { NumericStatAgg, CategoricalStatAgg, ArrayStatAgg, DateStatAgg, StatAgg } from '@/app/types';
238+
import type { StatsSummary, ExternalIdents } from '@/app/types';
269239

270240
export type StatisticalUnit = Omit<Tables<"statistical_unit">, 'external_idents' | 'stats_summary'> & {
271241
external_idents: ExternalIdents;

app/src/components/statistical-unit-hierarchy/topology-item.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,8 @@ export function TopologyItem({
110110
/>
111111
{statDefinitions.map(
112112
(statDefinition: Tables<"stat_definition_active">) => {
113-
const statsSum =
114-
stats?.stats_summary?.[statDefinition.code]?.sum;
113+
const metric = stats?.stats_summary?.[statDefinition.code];
114+
const statsSum = metric && "sum" in metric ? metric.sum : undefined;
115115
const stat = unit.stat_for_unit?.find(
116116
(s) => s.stat_definition_id === statDefinition.id
117117
);

app/src/components/statistical-unit-hierarchy/types.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ declare interface StatisticalUnitStats {
266266
unit_id: number;
267267
valid_from: string;
268268
valid_to: string;
269-
stats: { [key: string]: number | string };
269+
stats: { type: "stats"; [statCode: string]: { type: string; value: number | string | boolean } | string };
270270
stats_summary: StatsSummary;
271271
};
272272

0 commit comments

Comments
 (0)