Skip to content

Commit d2a4629

Browse files
committed
feat: format code
1 parent fefa41a commit d2a4629

4 files changed

Lines changed: 118 additions & 29 deletions

File tree

packages/econify/CHANGELOG.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,38 @@
22

33
All notable changes to the econify package will be documented in this file.
44

5-
65
## [1.0.5] - 2025-09-29
76

87
### ✨ Cross‑country, indicator‑level normalization consistency (V2)
98

10-
- Monetary flows now standardize time, currency, and magnitude per indicator across all countries:
11-
- Time: If the majority is monthly, quarterly items are rescaled to monthly (÷3), yearly to monthly (÷12); week/day use calendar factors. Stocks are not divided by time and omit "per <time>" in units.
12-
- Currency & Magnitude: When a single currency or magnitude dominates globally across the batch (≥ 0.8), it is applied to all items to ensure cross‑country comparability.
13-
- Per‑indicator time majority still applies with a prefer‑month tie‑breaker when ambiguous.
9+
- Monetary flows now standardize time, currency, and magnitude per indicator
10+
across all countries:
11+
- Time: If the majority is monthly, quarterly items are rescaled to monthly
12+
(÷3), yearly to monthly (÷12); week/day use calendar factors. Stocks are not
13+
divided by time and omit "per <time>" in units.
14+
- Currency & Magnitude: When a single currency or magnitude dominates globally
15+
across the batch (≥ 0.8), it is applied to all items to ensure cross‑country
16+
comparability.
17+
- Per‑indicator time majority still applies with a prefer‑month tie‑breaker
18+
when ambiguous.
1419
- Explain metadata improved:
15-
- `targetSelection` reflects effective selections (including global overrides), with shares and reason.
16-
- Periodicity conversion direction and factors (e.g., quarter → month, factor 1/3) are recorded.
20+
- `targetSelection` reflects effective selections (including global
21+
overrides), with shares and reason.
22+
- Periodicity conversion direction and factors (e.g., quarter → month, factor
23+
1/3) are recorded.
1724
- Targets selection robustness:
1825
- Majority share ratios use total items as the denominator for consistency.
19-
- Currency detection falls back to metadata (e.g., `currency_code`) when unit text lacks a currency token.
26+
- Currency detection falls back to metadata (e.g., `currency_code`) when unit
27+
text lacks a currency token.
2028

2129
### 🧪 Tests
30+
2231
- Fixed and validated:
2332
- V2 monetary: dominant currency/magnitude selection at 0.8 threshold
2433
- V2 transitions: auto‑target enabled vs disabled (EUR‑dominant)
2534

2635
### Developer note
36+
2737
- Changes primarily in V2 monetary domain batching and targets selection:
2838
- `src/workflowsV2/domains/monetary/monetary.machine.ts`
2939
- `src/workflowsV2/domains/monetary/targets.machine.ts`

packages/econify/src/workflowsV2/__tests__/workflowsV2.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,57 @@ Deno.test("V2 transitions: auto-target enabled vs disabled (EUR-dominant)", asyn
377377
}
378378
});
379379

380+
Deno.test("V2 auto-targeting: cross-country magnitude consistency per indicator", async () => {
381+
const config: PipelineConfig = {
382+
engine: "v2",
383+
autoTargetByIndicator: true,
384+
autoTargetDimensions: ["currency", "magnitude", "time"] as const,
385+
minMajorityShare: 0.6,
386+
tieBreakers: {
387+
currency: "prefer-USD",
388+
magnitude: "prefer-millions",
389+
time: "prefer-month",
390+
},
391+
useLiveFX: false,
392+
fxFallback: { base: "USD", rates: {} } as any,
393+
explain: true,
394+
} as PipelineConfig;
395+
const pipeline = createPipeline(config);
396+
const items = [
397+
{
398+
id: "ARE_BBS",
399+
value: 4.9733,
400+
unit: "USD Billion per month",
401+
name: "Banks Balance Sheet",
402+
},
403+
{
404+
id: "ALB_BBS",
405+
value: 26885.9207729469,
406+
unit: "USD Million per month",
407+
name: "Banks Balance Sheet",
408+
},
409+
{
410+
id: "AUT_BBS",
411+
value: 1354.09,
412+
unit: "USD Million per month",
413+
name: "Banks Balance Sheet",
414+
},
415+
];
416+
const result = await pipeline.run(items as any);
417+
for (const r of result) {
418+
if (String(r.name) !== "Banks Balance Sheet") continue;
419+
const u = String(r.normalizedUnit || r.unit || "");
420+
if (
421+
!u.includes("USD") || !u.toLowerCase().includes("millions") ||
422+
!u.toLowerCase().includes("per month")
423+
) {
424+
throw new Error(
425+
`Expected USD millions per month for all countries; got ${r.normalizedUnit} for ${r.id}`,
426+
);
427+
}
428+
}
429+
});
430+
380431
Deno.test("V2 transitions: router fan-out/fan-in preserves order across domains", async () => {
381432
const f = await import("../__fixtures__/indicators-organized.ts");
382433
const config: PipelineConfig = {

packages/econify/src/workflowsV2/domains/monetary/monetary.machine.ts

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,17 @@ export const monetaryMachine = setup({
5757

5858
if (hasGlobalTargets) {
5959
const indicatorKeyName = (input.config as any).indicatorKey ?? "name";
60-
const targetsMap: Map<string, any> =
61-
input.autoTargets instanceof Map
62-
? input.autoTargets
63-
: new Map<string, any>(Object.entries(input.autoTargets as Record<string, any>));
60+
const targetsMap: Map<string, any> = input.autoTargets instanceof Map
61+
? input.autoTargets
62+
: new Map<string, any>(
63+
Object.entries(input.autoTargets as Record<string, any>),
64+
);
6465

6566
const groups = new Map<string, ParsedData[]>();
6667
for (const item of input.items) {
67-
const key = String((item as any)[indicatorKeyName] || item.name || "");
68+
const key = String(
69+
(item as any)[indicatorKeyName] || item.name || "",
70+
);
6871
if (!groups.has(key)) groups.set(key, []);
6972
groups.get(key)!.push(item);
7073
}
@@ -73,9 +76,11 @@ export const monetaryMachine = setup({
7376
const results: ParsedData[] = [];
7477

7578
const extractUnitTime = (u: string): string | undefined => {
76-
const m = /per\s+(month|quarter|year|week|day|hour)|\/(month|quarter|year|week|day|hour)/i.exec(
77-
u,
78-
);
79+
const m =
80+
/per\s+(month|quarter|year|week|day|hour)|\/(month|quarter|year|week|day|hour)/i
81+
.exec(
82+
u,
83+
);
7984
return (m?.[1] || m?.[2])?.toLowerCase();
8085
};
8186

@@ -85,21 +90,40 @@ export const monetaryMachine = setup({
8590
const magCounts = new Map<string, number>();
8691
for (const it of input.items) {
8792
const parsed = parseUnit((it as any).unit || "");
88-
const cur = (parsed.currency || (it as any).currency_code?.toUpperCase() || undefined) as string | undefined;
89-
const mag = ((parsed.scale as string | undefined) || ((it as any).scale ? getScale((it as any).scale as any) : undefined)) as string | undefined;
90-
if (cur) curCounts.set(cur, (curCounts.get(cur) || 0) + 1);
91-
if (mag && mag !== "ones") magCounts.set(mag, (magCounts.get(mag) || 0) + 1);
93+
const cur =
94+
(parsed.currency || (it as any).currency_code?.toUpperCase() ||
95+
undefined) as string | undefined;
96+
const mag =
97+
((parsed.scale as string | undefined) || ((it as any).scale
98+
? getScale((it as any).scale as any)
99+
: undefined)) as string | undefined;
100+
if (cur) {
101+
curCounts.set(cur, (curCounts.get(cur) || 0) + 1);
102+
}
103+
if (mag && mag !== "ones") {
104+
magCounts.set(mag, (magCounts.get(mag) || 0) + 1);
105+
}
92106
}
93107
const topOf = (m: Map<string, number>) => {
94-
let k: string | undefined; let c = 0;
95-
for (const [kk, vv] of m.entries()) if (vv > c) { k = kk; c = vv; }
108+
let k: string | undefined;
109+
let c = 0;
110+
for (const [kk, vv] of m.entries()) {
111+
if (vv > c) {
112+
k = kk;
113+
c = vv;
114+
}
115+
}
96116
const share = totalN > 0 ? c / totalN : 0;
97117
return { key: k, share };
98118
};
99119
const gCur = topOf(curCounts);
100120
const gMag = topOf(magCounts);
101-
const globalCurrency = gCur.key && gCur.share >= threshold ? gCur.key : undefined;
102-
const globalMagnitude = gMag.key && gMag.share >= threshold ? (gMag.key as Scale) : undefined;
121+
const globalCurrency = gCur.key && gCur.share >= threshold
122+
? gCur.key
123+
: undefined;
124+
const globalMagnitude = gMag.key && gMag.share >= threshold
125+
? (gMag.key as Scale)
126+
: undefined;
103127

104128
for (const [key, items] of groups.entries()) {
105129
const tg = targetsMap.get(key) as any;
@@ -134,7 +158,8 @@ export const monetaryMachine = setup({
134158
indicatorKey: key,
135159
selected: {
136160
currency: (globalCurrency ?? tg.currency),
137-
magnitude: (globalMagnitude ?? (tg.magnitude as Scale | undefined)),
161+
magnitude:
162+
(globalMagnitude ?? (tg.magnitude as Scale | undefined)),
138163
time: resolvedTime,
139164
},
140165
shares: tg.shares || {},
@@ -145,9 +170,11 @@ export const monetaryMachine = setup({
145170

146171
const out = await normalizeMonetaryBatch(items, {
147172
isStock: input.isStock,
148-
toCurrency: (globalCurrency ?? (tg?.currency ?? input.config.targetCurrency)),
149-
toMagnitude: (globalMagnitude ?? ((tg?.magnitude as Scale | undefined) ??
150-
(input.config.targetMagnitude as Scale | undefined))),
173+
toCurrency:
174+
(globalCurrency ?? (tg?.currency ?? input.config.targetCurrency)),
175+
toMagnitude:
176+
(globalMagnitude ?? ((tg?.magnitude as Scale | undefined) ??
177+
(input.config.targetMagnitude as Scale | undefined))),
151178
toTimeScale: resolvedTime,
152179
fx: input.fx,
153180
explain: (input.config as any)?.explain ?? true,

packages/econify/src/workflowsV2/domains/monetary/targets.machine.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ export const targetsMachine = setup({
7676
const currencies = (context.items || []).map((i) => {
7777
const u = (i as any).unit || "";
7878
const parsed = parseUnit(u);
79-
return (parsed.currency || (i as any).currency_code?.toUpperCase() || undefined);
79+
return (parsed.currency || (i as any).currency_code?.toUpperCase() ||
80+
undefined);
8081
});
8182
const scales = (context.items || []).map((i) =>
8283
extractScale((i as any).unit || "") || undefined

0 commit comments

Comments
 (0)