@policyengine/calibration-tree 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -2
  3. package/package.json +73 -4
  4. package/src/calibration-build-comparison.ts +77 -0
  5. package/src/calibration-comparison-blob.ts +367 -0
  6. package/src/calibration-comparison-bundle.ts +149 -0
  7. package/src/calibration-explorer.ts +202 -0
  8. package/src/calibration-release-locator.ts +353 -0
  9. package/src/calibration-tree-artifact.ts +1974 -0
  10. package/src/calibration-tree-blob.ts +403 -0
  11. package/src/calibration-tree-bundle.ts +569 -0
  12. package/src/calibration-tree-manifest.ts +349 -0
  13. package/src/calibration-tree-publication.ts +399 -0
  14. package/src/calibration-tree-worker.ts +637 -0
  15. package/src/calibration-tree.ts +1284 -0
  16. package/src/chronicle-metadata.ts +42 -0
  17. package/src/countries.ts +355 -0
  18. package/src/geography-labels.ts +59 -0
  19. package/src/hierarchy-catalog-reader.ts +297 -0
  20. package/src/hierarchy-target-reader.ts +243 -0
  21. package/src/historical-weight-summary.ts +53 -0
  22. package/src/latest-artifact.ts +3767 -0
  23. package/src/legacy-target-reader.ts +337 -0
  24. package/src/program-label.ts +64 -0
  25. package/src/publish-calibration-tree.ts +1984 -0
  26. package/src/reforms.ts +224 -0
  27. package/src/release-cut.ts +32 -0
  28. package/src/release-record.ts +277 -0
  29. package/src/source-label.ts +5 -0
  30. package/src/source-labels.ts +71 -0
  31. package/src/staging-artifact.ts +1411 -0
  32. package/src/staging-contract.ts +916 -0
  33. package/src/structured-dimension-reader.ts +137 -0
  34. package/src/structured-target-reader.ts +76 -0
  35. package/src/target-change.ts +421 -0
  36. package/src/target-loss-attribution-manifest.ts +436 -0
  37. package/src/target-loss-attribution.ts +1012 -0
  38. package/src/target-representation.ts +69 -0
  39. package/src/target-surface-matcher.ts +266 -0
  40. package/types/next-fetch-cache.d.ts +8 -0
@@ -0,0 +1,42 @@
1
+ export type ChronicleMetadata = Record<string, unknown>;
2
+
3
+ const CURRENT_PREFIX = "chronicle_";
4
+ const LEGACY_PREFIX = "ledger_";
5
+
6
+ function asMetadata(value: unknown): ChronicleMetadata {
7
+ return value && typeof value === "object" && !Array.isArray(value)
8
+ ? (value as ChronicleMetadata)
9
+ : {};
10
+ }
11
+
12
+ function hasValue(value: unknown): boolean {
13
+ return typeof value === "string" ? value.trim().length > 0 : value != null;
14
+ }
15
+
16
+ /**
17
+ * Canonicalize artifact metadata after Ledger's rename to Chronicle.
18
+ *
19
+ * Published releases can contain either namespace. Preserve the original
20
+ * fields for diagnostics, while exposing every legacy `ledger_*` field under
21
+ * its canonical `chronicle_*` name. A populated Chronicle value always wins
22
+ * when both forms are present.
23
+ *
24
+ * This intentionally aliases arbitrary suffixes rather than maintaining a
25
+ * field allowlist, so newly published metadata remains backward-compatible.
26
+ */
27
+ export function normalizeChronicleMetadata(
28
+ value: unknown,
29
+ ): ChronicleMetadata {
30
+ const metadata = asMetadata(value);
31
+ const normalized = { ...metadata };
32
+
33
+ for (const [key, legacyValue] of Object.entries(metadata)) {
34
+ if (!key.startsWith(LEGACY_PREFIX) || !hasValue(legacyValue)) continue;
35
+ const canonicalKey = `${CURRENT_PREFIX}${key.slice(LEGACY_PREFIX.length)}`;
36
+ if (!hasValue(normalized[canonicalKey])) {
37
+ normalized[canonicalKey] = legacyValue;
38
+ }
39
+ }
40
+
41
+ return normalized;
42
+ }
@@ -0,0 +1,355 @@
1
+ // Country registry for the Microcosm dashboard: the one place a country is
2
+ // registered. Adding a country is one entry here; everything else (selectors,
3
+ // navigation, repository resolution, national geography, link behaviour) reads
4
+ // the registration or the release artifact's typed `country` block.
5
+ //
6
+ // Isomorphic: imported by client components and API routes alike, so it must
7
+ // not import server-only modules or read `process.env`. Repository resolvers
8
+ // accept an explicit environment object from server-side callers.
9
+
10
+ export const COUNTRY_CAPABILITIES = [
11
+ "calibration",
12
+ "targets",
13
+ "compare",
14
+ "cross_dataset",
15
+ "staging",
16
+ "model_coverage",
17
+ "pipeline",
18
+ "variables",
19
+ "external_checks",
20
+ ] as const;
21
+ export type CountryCapability = (typeof COUNTRY_CAPABILITIES)[number];
22
+
23
+ export type RepositoryVisibility = "public" | "private";
24
+
25
+ export type RepositoryEnvironment = Readonly<
26
+ Record<string, string | undefined>
27
+ >;
28
+
29
+ export interface ResolvedRepository {
30
+ repo: string;
31
+ revision: string;
32
+ }
33
+
34
+ export interface CountryRegistration {
35
+ // Default HF dataset repository and revision (the server may override both
36
+ // through the deployment variables named in repo_env / revision_env).
37
+ repo: string;
38
+ revision: string;
39
+ repo_env?: string;
40
+ revision_env?: string;
41
+ // The repository-root pointer naming the country's current release:
42
+ // `latest.json` unless the country promotes its default line through a
43
+ // per-line pointer (`latest-<line>.json`, microcosm#823) whose payload also
44
+ // names the immutable tag the release is read at (`cut_tag`). Read only
45
+ // while the dataset repository has no `releases.json`; a repository with
46
+ // one is read from it and its build folders alone, whatever the
47
+ // registration says (release-record.ts).
48
+ pointer_path?: string;
49
+ // Country display name ("United States").
50
+ label: string;
51
+ // Sidebar dataset line ("Microcosm US").
52
+ dataset_label: string;
53
+ // National geography label and id (null when the id is unknown).
54
+ geography: string;
55
+ geography_id: string | null;
56
+ visibility: RepositoryVisibility;
57
+ capabilities: readonly CountryCapability[];
58
+ // Optional staging telemetry repository. A staging-capable country must
59
+ // declare this instead of inheriting another country's repository.
60
+ staging?: {
61
+ repo: string;
62
+ revision: string;
63
+ repo_env?: string;
64
+ revision_env?: string;
65
+ token_env?: string;
66
+ // Staging pipelines, by the `pipeline.id` their run manifests record,
67
+ // whose candidates are local-area builds. The Staging page compares those
68
+ // candidates with the newest published local-area release instead of the
69
+ // current (national) release.
70
+ local_area_pipelines?: readonly string[];
71
+ };
72
+ // Other jurisdiction codes a cross-dataset bundle may use for this country.
73
+ jurisdiction_aliases?: readonly string[];
74
+ // Conformance-only registration: a valid country that is never listed in
75
+ // selectors or alert allowlists.
76
+ fixture?: true;
77
+ }
78
+
79
+ const ALL_CAPABILITIES: readonly CountryCapability[] = COUNTRY_CAPABILITIES;
80
+
81
+ const CALIBRATION_CAPABILITIES: readonly CountryCapability[] = [
82
+ "calibration",
83
+ "targets",
84
+ "compare",
85
+ "cross_dataset",
86
+ ];
87
+
88
+ // Deprecated upstream identifiers: Microcosm's published HF repositories and
89
+ // deployment variables still use the former Populace names.
90
+ export const COUNTRY_REGISTRY = {
91
+ us: {
92
+ repo: "policyengine/populace-us",
93
+ revision: "main",
94
+ repo_env: "POPULACE_HF_REPO",
95
+ label: "United States",
96
+ dataset_label: "Microcosm US",
97
+ geography: "United States",
98
+ geography_id: "0100000US",
99
+ visibility: "public",
100
+ capabilities: ALL_CAPABILITIES,
101
+ staging: {
102
+ // Deprecated upstream identifier: the US staging publisher still uses
103
+ // the former Populace repository and deployment-variable names.
104
+ repo: "policyengine/populace-us-staging",
105
+ revision: "main",
106
+ repo_env: "POPULACE_STAGING_HF_REPO",
107
+ revision_env: "POPULACE_STAGING_HF_REVISION",
108
+ },
109
+ },
110
+ uk: {
111
+ repo: "policyengine/populace-uk-private",
112
+ revision: "main",
113
+ repo_env: "POPULACE_UK_HF_REPO",
114
+ revision_env: "POPULACE_UK_HF_REVISION",
115
+ label: "United Kingdom",
116
+ dataset_label: "Microcosm UK",
117
+ geography: "United Kingdom",
118
+ geography_id: null,
119
+ visibility: "private",
120
+ capabilities: [...CALIBRATION_CAPABILITIES, "staging"],
121
+ // microcosm#823: the UK default line is the 2024-25 national dataset,
122
+ // promoted through its own pointer; `latest.json` stays on the June 2023
123
+ // release and is never moved. Unread once the repository has
124
+ // releases.json: Microcosm's switch writes it and deletes the pointer
125
+ // files in one commit.
126
+ pointer_path: "latest-national.json",
127
+ staging: {
128
+ repo: "policyengine/populace-uk-staging",
129
+ revision: "main",
130
+ repo_env: "POPULACE_UK_STAGING_HF_REPO",
131
+ revision_env: "POPULACE_UK_STAGING_HF_REVISION",
132
+ token_env: "POPULACE_UK_STAGING_HF_TOKEN",
133
+ // Microcosm's rowwise local-area candidates (published as the dense
134
+ // release). National attempts run the uk-frs-calibration pipeline.
135
+ local_area_pipelines: ["uk-local-candidate"],
136
+ },
137
+ jurisdiction_aliases: ["GB"],
138
+ },
139
+ be: {
140
+ repo: "policyengine/populace-be-private",
141
+ revision: "main",
142
+ repo_env: "POPULACE_BE_HF_REPO",
143
+ revision_env: "POPULACE_BE_HF_REVISION",
144
+ label: "Belgium",
145
+ dataset_label: "Microcosm Belgium",
146
+ geography: "Belgium",
147
+ geography_id: null,
148
+ visibility: "private",
149
+ capabilities: CALIBRATION_CAPABILITIES,
150
+ },
151
+ // Synthetic repository used only by the third-country conformance fixture.
152
+ zz: {
153
+ repo: "policyengine/microcosm-zz-fixture",
154
+ revision: "main",
155
+ label: "Zedland",
156
+ dataset_label: "Microcosm ZZ",
157
+ geography: "Zedland",
158
+ geography_id: null,
159
+ visibility: "private",
160
+ capabilities: ["calibration", "targets", "compare"],
161
+ fixture: true,
162
+ },
163
+ // Synthetic non-US staging registration. It verifies that staging artifact
164
+ // resolution is country-scoped without exposing an unfinished country in
165
+ // selectors or treating its fixture repository as a published dataset.
166
+ am: {
167
+ repo: "policyengine/microcosm-am-fixture",
168
+ revision: "main",
169
+ label: "Armenia",
170
+ dataset_label: "Microcosm Armenia",
171
+ geography: "Armenia",
172
+ geography_id: null,
173
+ visibility: "private",
174
+ capabilities: ["calibration", "targets", "compare", "staging"],
175
+ staging: {
176
+ repo: "policyengine/microcosm-am-staging-fixture",
177
+ revision: "main",
178
+ },
179
+ fixture: true,
180
+ },
181
+ } satisfies Record<string, CountryRegistration>;
182
+
183
+ export type MicrocosmCountry = keyof typeof COUNTRY_REGISTRY;
184
+
185
+ export const DEFAULT_COUNTRY: MicrocosmCountry = "us";
186
+
187
+ const COUNTRY_CODES = Object.keys(COUNTRY_REGISTRY) as MicrocosmCountry[];
188
+
189
+ export function isCountry(value: string | null | undefined): value is MicrocosmCountry {
190
+ return value != null && Object.hasOwn(COUNTRY_REGISTRY, value);
191
+ }
192
+
193
+ export function countryRegistration(country: MicrocosmCountry): CountryRegistration {
194
+ return COUNTRY_REGISTRY[country];
195
+ }
196
+
197
+ // A registered country the dashboard offers: every registration except the
198
+ // conformance fixtures.
199
+ export function isSelectableCountry(
200
+ value: string | null | undefined,
201
+ ): value is MicrocosmCountry {
202
+ return isCountry(value) && !countryRegistration(value).fixture;
203
+ }
204
+
205
+ // What a request's `country` parameter names. Absent (null or "") selects the
206
+ // registry default. A present value must equal a selectable registration key
207
+ // exactly; anything else (an unregistered code such as "nz", another spelling
208
+ // such as "US", or a conformance fixture) is not configured. It never resolves
209
+ // to a different country, so one country's release is never served under
210
+ // another country's code.
211
+ export type CountryParam =
212
+ | { configured: true; country: MicrocosmCountry; explicit: boolean }
213
+ | { configured: false; requested: string };
214
+
215
+ export function countryParam(value: string | null | undefined): CountryParam {
216
+ if (value == null || value === "") {
217
+ return { configured: true, country: DEFAULT_COUNTRY, explicit: false };
218
+ }
219
+ return isSelectableCountry(value)
220
+ ? { configured: true, country: value, explicit: true }
221
+ : { configured: false, requested: value };
222
+ }
223
+
224
+ // "unknown_country": the dashboard has no such country. "no_published_release":
225
+ // the country is registered but the dashboard manifest names no release for it
226
+ // yet (a newly registered country before its first publication).
227
+ export type CountryNotConfiguredReason = "unknown_country" | "no_published_release";
228
+
229
+ export const COUNTRY_NOT_CONFIGURED = "country_not_configured";
230
+
231
+ export interface CountryNotConfiguredBody {
232
+ error: typeof COUNTRY_NOT_CONFIGURED;
233
+ requested_country: string;
234
+ reason: CountryNotConfiguredReason;
235
+ detail: string;
236
+ }
237
+
238
+ export class CountryNotConfiguredError extends Error {
239
+ readonly requested: string;
240
+ readonly reason: CountryNotConfiguredReason;
241
+
242
+ constructor(requested: string, reason?: CountryNotConfiguredReason) {
243
+ const resolvedReason =
244
+ reason ?? (isSelectableCountry(requested) ? "no_published_release" : "unknown_country");
245
+ super(countryNotConfiguredDetail(requested, resolvedReason));
246
+ this.name = "CountryNotConfiguredError";
247
+ this.requested = requested;
248
+ this.reason = resolvedReason;
249
+ }
250
+
251
+ body(): CountryNotConfiguredBody {
252
+ return {
253
+ error: COUNTRY_NOT_CONFIGURED,
254
+ requested_country: this.requested,
255
+ reason: this.reason,
256
+ detail: this.message,
257
+ };
258
+ }
259
+ }
260
+
261
+ function countryNotConfiguredDetail(
262
+ requested: string,
263
+ reason: CountryNotConfiguredReason,
264
+ ): string {
265
+ if (reason === "no_published_release" && isCountry(requested)) {
266
+ return `No Microcosm calibration release has been published for ${countryRegistration(requested).label} yet.`;
267
+ }
268
+ return `No Microcosm calibration release is configured for country "${requested}".`;
269
+ }
270
+
271
+ export function isCountryNotConfiguredBody(value: unknown): value is CountryNotConfiguredBody {
272
+ return (
273
+ value != null &&
274
+ typeof value === "object" &&
275
+ (value as { error?: unknown }).error === COUNTRY_NOT_CONFIGURED &&
276
+ typeof (value as { requested_country?: unknown }).requested_country === "string"
277
+ );
278
+ }
279
+
280
+ export const DEFAULT_POINTER_PATH = "latest.json";
281
+
282
+ // The repository-root pointer a country's current release is resolved through.
283
+ export function countryPointerPath(country: MicrocosmCountry): string {
284
+ return countryRegistration(country).pointer_path ?? DEFAULT_POINTER_PATH;
285
+ }
286
+
287
+ // Countries offered in selectors, in registry order; fixtures are excluded.
288
+ export function selectableCountries(): MicrocosmCountry[] {
289
+ return COUNTRY_CODES.filter(isSelectableCountry);
290
+ }
291
+
292
+ export function countryCapabilities(country: MicrocosmCountry): readonly CountryCapability[] {
293
+ return COUNTRY_REGISTRY[country].capabilities;
294
+ }
295
+
296
+ // Staging pipeline ids whose candidates are local-area builds; empty for
297
+ // countries that register none.
298
+ export function stagingLocalAreaPipelines(country: MicrocosmCountry): readonly string[] {
299
+ return countryRegistration(country).staging?.local_area_pipelines ?? [];
300
+ }
301
+
302
+ export function hasCapability(country: MicrocosmCountry, capability: CountryCapability): boolean {
303
+ return countryCapabilities(country).includes(capability);
304
+ }
305
+
306
+ function configuredValue(
307
+ environment: RepositoryEnvironment,
308
+ variableName: string | undefined,
309
+ fallback: string,
310
+ ): string {
311
+ return (variableName ? environment[variableName] : undefined) ?? fallback;
312
+ }
313
+
314
+ export function resolveRegisteredRepository(
315
+ country: MicrocosmCountry,
316
+ environment: RepositoryEnvironment,
317
+ ): ResolvedRepository {
318
+ const registration = countryRegistration(country);
319
+ return {
320
+ repo: configuredValue(
321
+ environment,
322
+ registration.repo_env,
323
+ registration.repo,
324
+ ),
325
+ revision: configuredValue(
326
+ environment,
327
+ registration.revision_env,
328
+ registration.revision,
329
+ ),
330
+ };
331
+ }
332
+
333
+ export function resolveRegisteredStagingRepository(
334
+ country: MicrocosmCountry,
335
+ environment: RepositoryEnvironment,
336
+ ): ResolvedRepository | null {
337
+ const registration = countryRegistration(country);
338
+ if (!hasCapability(country, "staging") || !registration.staging) return null;
339
+ return {
340
+ repo: configuredValue(
341
+ environment,
342
+ registration.staging.repo_env,
343
+ registration.staging.repo,
344
+ ),
345
+ revision: configuredValue(
346
+ environment,
347
+ registration.staging.revision_env,
348
+ registration.staging.revision,
349
+ ),
350
+ };
351
+ }
352
+
353
+ export function isCountryCapability(value: unknown): value is CountryCapability {
354
+ return typeof value === "string" && (COUNTRY_CAPABILITIES as readonly string[]).includes(value);
355
+ }
@@ -0,0 +1,59 @@
1
+ // A target's geography is identified by its level and code, but the dashboard
2
+ // shows, filters and groups targets by the geography's label. Microcosm's UK
3
+ // local-area builds calibrate parliamentary constituencies and local
4
+ // authorities side by side, and many of them share a name (Ashfield,
5
+ // Colchester, Midlothian, ...). A label used at more than one geography level
6
+ // therefore names its level as well, "Ashfield (constituency)" and "Ashfield
7
+ // (local authority)", so the explorer's tiles and filters, the targets page
8
+ // and the comparison maps keep the two areas apart. A label that only one
9
+ // level uses is left exactly as published, and so is the country's own
10
+ // national geography label: a target with no resolvable geography falls back
11
+ // to it at the national level, and it still means the same, whole country as
12
+ // a target that names it at the country level.
13
+
14
+ interface GeographyLabelledRow {
15
+ geography?: unknown;
16
+ level?: unknown;
17
+ }
18
+
19
+ function trimmedText(value: unknown): string {
20
+ return typeof value === "string" ? value.trim() : "";
21
+ }
22
+
23
+ function levelPhrase(level: string): string {
24
+ return level.replace(/[_-]+/g, " ");
25
+ }
26
+
27
+ /**
28
+ * Returns the rows with every geography label that more than one level uses,
29
+ * other than the national geography label, qualified by its level. Returns
30
+ * the same array when no label is shared.
31
+ */
32
+ export function levelQualifiedGeographyLabels<Row extends GeographyLabelledRow>(
33
+ rows: Row[],
34
+ nationalGeography: string,
35
+ ): Row[] {
36
+ const nationalLabel = nationalGeography.trim();
37
+ const levelsByLabel = new Map<string, Set<string>>();
38
+ for (const row of rows) {
39
+ const label = trimmedText(row.geography);
40
+ const level = trimmedText(row.level);
41
+ if (!label || !level || label === nationalLabel) continue;
42
+ const levels = levelsByLabel.get(label) ?? new Set<string>();
43
+ levels.add(level);
44
+ levelsByLabel.set(label, levels);
45
+ }
46
+ const sharedLabels = new Set(
47
+ [...levelsByLabel]
48
+ .filter(([, levels]) => levels.size > 1)
49
+ .map(([label]) => label),
50
+ );
51
+ if (sharedLabels.size === 0) return rows;
52
+ return rows.map((row) => {
53
+ const label = trimmedText(row.geography);
54
+ const level = trimmedText(row.level);
55
+ return sharedLabels.has(label) && level
56
+ ? { ...row, geography: `${label} (${levelPhrase(level)})` }
57
+ : row;
58
+ });
59
+ }