@policyengine/calibration-tree 0.0.0-stage → 0.1.0

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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -2
  3. package/package.json +71 -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-worker.ts +445 -0
  14. package/src/calibration-tree.ts +1284 -0
  15. package/src/chronicle-metadata.ts +42 -0
  16. package/src/countries.ts +355 -0
  17. package/src/geography-labels.ts +59 -0
  18. package/src/hierarchy-catalog-reader.ts +297 -0
  19. package/src/hierarchy-target-reader.ts +243 -0
  20. package/src/historical-weight-summary.ts +53 -0
  21. package/src/latest-artifact.ts +3767 -0
  22. package/src/legacy-target-reader.ts +337 -0
  23. package/src/program-label.ts +64 -0
  24. package/src/publish-calibration-tree.ts +1984 -0
  25. package/src/reforms.ts +224 -0
  26. package/src/release-cut.ts +32 -0
  27. package/src/release-record.ts +277 -0
  28. package/src/source-label.ts +5 -0
  29. package/src/source-labels.ts +71 -0
  30. package/src/staging-artifact.ts +1411 -0
  31. package/src/staging-contract.ts +916 -0
  32. package/src/structured-dimension-reader.ts +137 -0
  33. package/src/structured-target-reader.ts +76 -0
  34. package/src/target-change.ts +421 -0
  35. package/src/target-loss-attribution-manifest.ts +436 -0
  36. package/src/target-loss-attribution.ts +1012 -0
  37. package/src/target-representation.ts +69 -0
  38. package/src/target-surface-matcher.ts +266 -0
  39. package/types/next-fetch-cache.d.ts +8 -0
package/src/reforms.ts ADDED
@@ -0,0 +1,224 @@
1
+ // Reform-validation data layer. Where calibration_diagnostics.json answers
2
+ // "does the dataset reproduce its calibration targets", reform_validation.json
3
+ // answers a downstream question: "does the dataset reproduce the budget effects
4
+ // of policy reforms that an authority (JCT) has officially scored?".
5
+ //
6
+ // The cross-release external-comparison surface (the retired "External
7
+ // checks" tab, its committed overrides, and the backfill automation) moved to
8
+ // PolicyEngine/policyengine-scorecard (scorecard issue #15). What remains here
9
+ // is the producer/consumer contract parsing the staging view uses to render a
10
+ // candidate run's own reform_validation.json as release-gate telemetry.
11
+ //
12
+ // ---------------------------------------------------------------------------
13
+ // reform_validation.json schema (v1) — the producer/consumer contract:
14
+ //
15
+ // {
16
+ // "schema_version": 1,
17
+ // "release_id": "populace-us-2024-<sha>-...",
18
+ // "baseline_period": 2026,
19
+ // "scoring_window": "FY2025-2034",
20
+ // "reforms": [
21
+ // {
22
+ // "id": "obbba", // stable across releases
23
+ // "name": "One Big Beautiful Bill Act",
24
+ // "category": "OBBBA", // grouping label
25
+ // "description": "…", // optional
26
+ // "jct": {
27
+ // "score": -3700000000000, // budget effect, USD (− = cost)
28
+ // "score_type": "conventional", // or "dynamic"
29
+ // "window": "FY2025-2034",
30
+ // "source": "JCX-29-25",
31
+ // "source_url": "https://www.jct.gov/…",
32
+ // "published": "2025-05-..." // optional ISO date
33
+ // },
34
+ // "microcosm": {
35
+ // "budget_effect": -3650000000000, // Microcosm microsim, same window
36
+ // "window": "FY2025-2034",
37
+ // "annual": { "2025": -1.2e11, … } // optional per-year series
38
+ // }
39
+ // }
40
+ // ]
41
+ // }
42
+ // ---------------------------------------------------------------------------
43
+
44
+ import { asObject } from "./latest-artifact";
45
+
46
+ type JsonObject = Record<string, unknown>;
47
+
48
+ export const REFORM_VALIDATION_FILE = "reform_validation.json";
49
+
50
+ function numberOrNull(value: unknown): number | null {
51
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
52
+ }
53
+
54
+ function stringOrNull(value: unknown): string | null {
55
+ return typeof value === "string" && value ? value : null;
56
+ }
57
+
58
+ export interface ReformValidationRow {
59
+ id: string;
60
+ name: string;
61
+ category: string | null;
62
+ description: string | null;
63
+ // in-sample reforms are JCT tax-expenditure *calibration targets* (the
64
+ // dataset was tuned to them); out-of-sample reforms (OBBBA provisions) are
65
+ // the genuine fidelity test.
66
+ in_sample: boolean;
67
+ period: number | null;
68
+ // How score/estimate are denominated. Budget-effect rows are
69
+ // "currency-USD"; baseline-level rate backtests (Census state SPM poverty
70
+ // rates) are "percent", carrying fractions (0.134 = 13.4%) in the same
71
+ // score fields. Missing unit (older payloads) means currency.
72
+ unit: "currency-USD" | "percent";
73
+ // The JCT figure we benchmark against — JCT's first full fiscal year
74
+ // (FY2027) for provisions effective 1/1/2026, since FY2026 is a partial ramp
75
+ // year vs microcosm's calendar-year liability. In-sample rows fall back to
76
+ // their annual figure. Error metrics below are all relative to this.
77
+ jct_score: number | null;
78
+ // The FY2026 figure, kept for reference (the partial ramp year).
79
+ jct_score_fy2026: number | null;
80
+ jct_score_type: string | null;
81
+ jct_window: string | null;
82
+ // The year/window the benchmark (jct_score) actually refers to — "FY2027"
83
+ // for OBBBA rows (the full-year default), the annual window for in-sample.
84
+ // Differs row to row, so it's shown as its own column.
85
+ jct_benchmark_window: string | null;
86
+ jct_source: string | null;
87
+ jct_source_url: string | null;
88
+ jct_published: string | null;
89
+ microcosm_estimate: number | null;
90
+ microcosm_window: string | null;
91
+ microcosm_annual: Record<string, number> | null;
92
+ // Derived (relative to jct_score, i.e. the FY2027 benchmark where available).
93
+ abs_error: number | null; // microcosm − jct (USD)
94
+ relative_error: number | null; // (microcosm − jct) / |jct|
95
+ abs_relative_error: number | null;
96
+ within_10pct: boolean | null;
97
+ direction: "over" | "under" | "exact" | null;
98
+ }
99
+
100
+ export interface ReformValidation {
101
+ available: true;
102
+ source: "huggingface_live";
103
+ release_id: string;
104
+ updated_at: string | null;
105
+ schema_version: unknown;
106
+ baseline_period: number | null;
107
+ scoring_window: string | null;
108
+ rows: ReformValidationRow[];
109
+ summary: {
110
+ n_reforms: number;
111
+ n_scored: number; // reforms with both a JCT score and a microcosm estimate
112
+ within_10pct: number;
113
+ mean_abs_relative_error: number | null;
114
+ median_abs_relative_error: number | null;
115
+ // The out-of-sample reforms only — the genuine fidelity test.
116
+ n_out_of_sample: number;
117
+ n_out_of_sample_scored: number;
118
+ out_of_sample_within_10pct: number;
119
+ out_of_sample_mean_abs_relative_error: number | null;
120
+ };
121
+ }
122
+
123
+ function enrichReform(raw: JsonObject): ReformValidationRow {
124
+ const jct = asObject(raw.jct);
125
+ const microcosm = asObject(raw.microcosm);
126
+ const calculation = Object.keys(microcosm).length
127
+ ? microcosm
128
+ : asObject(raw.populace);
129
+ const jctFy2026 = numberOrNull(jct.score);
130
+ const jctFy2027 = numberOrNull(jct.score_fy2027);
131
+ // Benchmark defaults to JCT's first full fiscal year (FY2027). FY2026 is a
132
+ // partial ramp year for provisions effective 1/1/2026, so error vs FY2026
133
+ // overstates the gap against microcosm's calendar-year liability. In-sample
134
+ // rows have no FY2027 figure, so they fall back to their annual (FY2026) one.
135
+ const benchmark = jctFy2027 ?? jctFy2026;
136
+ const estimate = numberOrNull(calculation.budget_effect);
137
+ const absError = benchmark != null && estimate != null ? estimate - benchmark : null;
138
+ // A zero benchmark has no meaningful relative error — leave it unscored
139
+ // rather than storing the raw dollar delta, which would otherwise be treated
140
+ // as a fraction and blow up within_10pct and the mean/median aggregates.
141
+ const relError =
142
+ benchmark != null && estimate != null && benchmark !== 0
143
+ ? (estimate - benchmark) / Math.abs(benchmark)
144
+ : null;
145
+ const absRel = relError == null ? null : Math.abs(relError);
146
+ const annual = asObject(calculation.annual);
147
+ const annualClean: Record<string, number> = {};
148
+ for (const [k, v] of Object.entries(annual)) {
149
+ const n = numberOrNull(v);
150
+ if (n != null) annualClean[k] = n;
151
+ }
152
+ return {
153
+ id: String(raw.id ?? raw.name ?? ""),
154
+ name: String(raw.name ?? raw.id ?? ""),
155
+ category: stringOrNull(raw.category),
156
+ description: stringOrNull(raw.description),
157
+ in_sample: raw.in_sample === true,
158
+ period: numberOrNull(raw.period),
159
+ unit: raw.unit === "percent" ? "percent" : "currency-USD",
160
+ jct_score: benchmark,
161
+ jct_score_fy2026: jctFy2026,
162
+ jct_score_type: stringOrNull(jct.score_type),
163
+ jct_window: stringOrNull(jct.window),
164
+ jct_benchmark_window: jctFy2027 != null ? "FY2027" : stringOrNull(jct.window),
165
+ jct_source: stringOrNull(jct.source),
166
+ jct_source_url: stringOrNull(jct.source_url),
167
+ jct_published: stringOrNull(jct.published),
168
+ microcosm_estimate: estimate,
169
+ microcosm_window: stringOrNull(calculation.window),
170
+ microcosm_annual: Object.keys(annualClean).length ? annualClean : null,
171
+ abs_error: absError,
172
+ relative_error: relError,
173
+ abs_relative_error: absRel,
174
+ within_10pct: absRel == null ? null : absRel <= 0.1,
175
+ direction:
176
+ absError == null ? null : absError > 0 ? "over" : absError < 0 ? "under" : "exact",
177
+ };
178
+ }
179
+
180
+ function median(values: number[]): number | null {
181
+ if (!values.length) return null;
182
+ const sorted = [...values].sort((a, b) => a - b);
183
+ const mid = Math.floor(sorted.length / 2);
184
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
185
+ }
186
+
187
+ export function buildReformValidation(
188
+ raw: JsonObject,
189
+ releaseId: string,
190
+ updatedAt: string | null = null,
191
+ ): ReformValidation {
192
+ const reforms = Array.isArray(raw.reforms) ? (raw.reforms as JsonObject[]) : [];
193
+ const rows = reforms.map((r) => enrichReform(asObject(r)));
194
+ const scored = rows.filter((r) => r.abs_relative_error != null);
195
+ const absRels = scored.map((r) => r.abs_relative_error as number);
196
+ const oos = rows.filter((r) => !r.in_sample);
197
+ const oosScored = oos.filter((r) => r.abs_relative_error != null);
198
+ const oosAbsRels = oosScored.map((r) => r.abs_relative_error as number);
199
+ return {
200
+ available: true,
201
+ source: "huggingface_live",
202
+ release_id: String(raw.release_id ?? releaseId),
203
+ updated_at: updatedAt,
204
+ schema_version: raw.schema_version ?? null,
205
+ baseline_period: numberOrNull(raw.baseline_period),
206
+ scoring_window: stringOrNull(raw.scoring_window),
207
+ rows,
208
+ summary: {
209
+ n_reforms: rows.length,
210
+ n_scored: scored.length,
211
+ within_10pct: scored.filter((r) => r.within_10pct === true).length,
212
+ mean_abs_relative_error: absRels.length
213
+ ? absRels.reduce((s, v) => s + v, 0) / absRels.length
214
+ : null,
215
+ median_abs_relative_error: median(absRels),
216
+ n_out_of_sample: oos.length,
217
+ n_out_of_sample_scored: oosScored.length,
218
+ out_of_sample_within_10pct: oosScored.filter((r) => r.within_10pct === true).length,
219
+ out_of_sample_mean_abs_relative_error: oosAbsRels.length
220
+ ? oosAbsRels.reduce((s, v) => s + v, 0) / oosAbsRels.length
221
+ : null,
222
+ },
223
+ };
224
+ }
@@ -0,0 +1,32 @@
1
+ // Microcosm publishes a constant-id release line (microcosm#823) as one
2
+ // immutable Hugging Face tag per cut, `<release id>-<YYYYMMDDTHHMMSSZ>-<attempt>`,
3
+ // while the cut's files stay under the line's own folder, `releases/<release id>/`.
4
+ // The tag names the build; the folder names where its files live. Isomorphic:
5
+ // the release picker labels cuts with the same grammar the publisher reads.
6
+
7
+ export interface ReleaseCut {
8
+ /** The line's constant release id, which is also its `releases/` folder. */
9
+ releaseId: string;
10
+ /** The calibration attempt's instant, `YYYYMMDDTHHMMSSZ`. */
11
+ cutAt: string;
12
+ /** The calibration attempt's short identifier (8 hex digits). */
13
+ attempt: string;
14
+ }
15
+
16
+ const RELEASE_CUT_TAG_RE =
17
+ /^([A-Za-z0-9][A-Za-z0-9._-]*)-(\d{8}T\d{6}Z)-([0-9a-f]{8})$/;
18
+
19
+ export function parseReleaseCutTag(tag: string): ReleaseCut | null {
20
+ const match = RELEASE_CUT_TAG_RE.exec(tag);
21
+ return match
22
+ ? { releaseId: match[1], cutAt: match[2], attempt: match[3] }
23
+ : null;
24
+ }
25
+
26
+ // A release folder is interpolated into Hugging Face URLs that carry the
27
+ // server's token, so it passes the same allowlist as a release id.
28
+ const RELEASE_DIRECTORY_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
29
+
30
+ export function isReleaseDirectory(value: unknown): value is string {
31
+ return typeof value === "string" && RELEASE_DIRECTORY_RE.test(value);
32
+ }
@@ -0,0 +1,277 @@
1
+ // `releases.json`: the one record of which build of each release a Microcosm
2
+ // dataset repository has published and which is current. The repository
3
+ // publishes every build in its own folder, never changed after publishing:
4
+ //
5
+ // releases.json
6
+ // releases/<release>/<build>/release_manifest.json
7
+ // releases/<release>/<build>/...
8
+ //
9
+ // Every reader finds the layout this way: a repository with releases.json is
10
+ // read from it and its build folders only; a repository without one is read
11
+ // from its tags and pointer files (`latest*.json`). No country registration
12
+ // names the layout, so a repository's switch needs no dashboard change.
13
+ //
14
+ // The record's shape and rules are microcosm-emitter's
15
+ // `microcosm_emitter_interface.releases` (microcosm-emitter#24), whose release
16
+ // writer is the only program that changes the file. This module reads the
17
+ // record with the same rules, so a record the writer would refuse is refused
18
+ // here too, with one exception: JSON.parse reads `1.0` as the number 1, so a
19
+ // `schema_version` of `1.0` passes here although the writer, which requires
20
+ // an integer, refuses it. The writer itself always writes `1`. Isomorphic:
21
+ // the release picker and the tree publisher share it.
22
+
23
+ export const RELEASES_RECORD_PATH = "releases.json";
24
+ export const RELEASES_RECORD_SCHEMA_VERSION = 1;
25
+ export const RELEASE_MANIFEST_NAME = "release_manifest.json";
26
+ // The interface's MAX_RECORD_BYTES: the largest releases.json the writer's
27
+ // parser accepts, and the size its checks keep every record within.
28
+ export const MAX_RELEASES_RECORD_BYTES = 4 * 1024 * 1024;
29
+
30
+ // The interface's SAFE_IDENTIFIER and ROLE_IDENTIFIER.
31
+ const SAFE_IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
32
+ const ROLE_IDENTIFIER_RE = /^[a-z][a-z0-9_]{0,63}$/;
33
+ const RECORD_KEYS = ["default", "releases", "schema_version"];
34
+ const RELEASE_KEYS = ["builds", "current", "role"];
35
+ const BUILD_KEYS = ["published_at", "withdrawn"];
36
+
37
+ export interface RecordedBuildEntry {
38
+ /** As the writer recorded it; not reformatted. */
39
+ publishedAt: string;
40
+ withdrawn: boolean;
41
+ }
42
+
43
+ export interface RecordedRelease {
44
+ role: string;
45
+ /** Null while the release has no current build. */
46
+ current: string | null;
47
+ builds: Record<string, RecordedBuildEntry>;
48
+ }
49
+
50
+ export interface ReleasesRecord {
51
+ /** The release a repository's default dataset comes from, or null. */
52
+ default: string | null;
53
+ releases: Record<string, RecordedRelease>;
54
+ }
55
+
56
+ /** One listed build, with what the record says about it and its release. */
57
+ export interface RecordedBuild {
58
+ release: string;
59
+ build: string;
60
+ /** The build's folder under `releases/`: `<release>/<build>`. */
61
+ id: string;
62
+ role: string;
63
+ publishedAt: string;
64
+ withdrawn: boolean;
65
+ /** The build is its release's current build. */
66
+ current: boolean;
67
+ /** The build's release is the record's default release. */
68
+ inDefaultRelease: boolean;
69
+ }
70
+
71
+ export class ReleasesRecordError extends Error {}
72
+
73
+ function isSafeIdentifier(value: unknown): value is string {
74
+ return typeof value === "string" && SAFE_IDENTIFIER_RE.test(value);
75
+ }
76
+
77
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
78
+ return value != null && typeof value === "object" && !Array.isArray(value);
79
+ }
80
+
81
+ function hasExactly(value: Record<string, unknown>, keys: readonly string[]): boolean {
82
+ const present = Object.keys(value).sort();
83
+ return present.length === keys.length && present.every((key, index) => key === keys[index]);
84
+ }
85
+
86
+ function refuse(message: string): never {
87
+ throw new ReleasesRecordError(message);
88
+ }
89
+
90
+ /**
91
+ * Parse `releases.json` and check it as the writer does: its exact shape,
92
+ * safe release and build names, and (`check_record`) that each `current`
93
+ * names a listed build that is not withdrawn and that `default` names a
94
+ * listed release.
95
+ */
96
+ export function parseReleasesRecord(document: unknown): ReleasesRecord {
97
+ if (!isPlainObject(document) || !hasExactly(document, RECORD_KEYS)) {
98
+ refuse(`${RELEASES_RECORD_PATH} must have exactly ${RECORD_KEYS.join(", ")}.`);
99
+ }
100
+ if (document.schema_version !== RELEASES_RECORD_SCHEMA_VERSION) {
101
+ refuse(`${RELEASES_RECORD_PATH} schema_version must be ${RELEASES_RECORD_SCHEMA_VERSION}.`);
102
+ }
103
+ if (!isPlainObject(document.releases)) refuse("releases must be an object.");
104
+ const releases: Record<string, RecordedRelease> = {};
105
+ for (const [release, entry] of Object.entries(document.releases)) {
106
+ if (!isSafeIdentifier(release)) refuse("A release name is not a safe identifier.");
107
+ if (!isPlainObject(entry) || !hasExactly(entry, RELEASE_KEYS)) {
108
+ refuse(`Release ${release} must have ${RELEASE_KEYS.join(", ")}.`);
109
+ }
110
+ if (typeof entry.role !== "string" || !ROLE_IDENTIFIER_RE.test(entry.role)) {
111
+ refuse(`Release ${release} has an invalid role.`);
112
+ }
113
+ if (entry.current !== null && typeof entry.current !== "string") {
114
+ refuse(`Release ${release} current must be a build or null.`);
115
+ }
116
+ if (!isPlainObject(entry.builds)) refuse(`Release ${release} builds must be an object.`);
117
+ const builds: Record<string, RecordedBuildEntry> = {};
118
+ for (const [build, buildEntry] of Object.entries(entry.builds)) {
119
+ if (!isSafeIdentifier(build)) refuse("A build name is not a safe identifier.");
120
+ if (
121
+ !isPlainObject(buildEntry) ||
122
+ !hasExactly(buildEntry, BUILD_KEYS) ||
123
+ typeof buildEntry.published_at !== "string" ||
124
+ !buildEntry.published_at ||
125
+ typeof buildEntry.withdrawn !== "boolean"
126
+ ) {
127
+ refuse(`Build ${build} of ${release} is malformed.`);
128
+ }
129
+ builds[build] = {
130
+ publishedAt: buildEntry.published_at,
131
+ withdrawn: buildEntry.withdrawn,
132
+ };
133
+ }
134
+ releases[release] = { role: entry.role, current: entry.current, builds };
135
+ }
136
+ if (document.default !== null && typeof document.default !== "string") {
137
+ refuse("default must be a release name or null.");
138
+ }
139
+ const record: ReleasesRecord = { default: document.default, releases };
140
+ if (record.default != null && !Object.hasOwn(releases, record.default)) {
141
+ refuse(`Default ${record.default} is not listed.`);
142
+ }
143
+ for (const [release, entry] of Object.entries(releases)) {
144
+ if (entry.current == null) continue;
145
+ // Only the listed builds: `builds` is an ordinary object, so a name such
146
+ // as "constructor" would otherwise find an inherited property.
147
+ const current = Object.hasOwn(entry.builds, entry.current)
148
+ ? entry.builds[entry.current]
149
+ : undefined;
150
+ if (!current) refuse(`Current build ${entry.current} of ${release} is not listed.`);
151
+ if (current.withdrawn) refuse(`Current build ${entry.current} of ${release} is withdrawn.`);
152
+ }
153
+ return record;
154
+ }
155
+
156
+ /**
157
+ * Read `releases.json` from its bytes as the writer's `ReleasesRecord.parse`
158
+ * does: refuse a record larger than MAX_RELEASES_RECORD_BYTES or one that is
159
+ * not a readable JSON document, then check it with parseReleasesRecord.
160
+ */
161
+ export function parseReleasesRecordBytes(bytes: Uint8Array): ReleasesRecord {
162
+ if (bytes.byteLength > MAX_RELEASES_RECORD_BYTES) {
163
+ refuse(`${RELEASES_RECORD_PATH} is larger than ${MAX_RELEASES_RECORD_BYTES} bytes.`);
164
+ }
165
+ let parsed: unknown;
166
+ try {
167
+ parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
168
+ } catch {
169
+ refuse(`${RELEASES_RECORD_PATH} is not a readable JSON document.`);
170
+ }
171
+ return parseReleasesRecord(parsed);
172
+ }
173
+
174
+ // Hugging Face's error codes for a 404 that is not about the file: the
175
+ // repository or the revision is missing (or unreadable with this token).
176
+ const MISSING_REPOSITORY_OR_REVISION = new Set(["RepoNotFound", "RevisionNotFound"]);
177
+
178
+ /**
179
+ * Read Hugging Face's answer to a request for releases.json: the record,
180
+ * checked as parseReleasesRecordBytes checks it, or null when the repository
181
+ * has no releases.json at that revision. Only a 404 means "no record", and
182
+ * only when Hugging Face's error code (`X-Error-Code`) does not say that the
183
+ * repository or the revision is missing. Any other failure throws, so a
184
+ * reader never falls back to tags or pointer files because of it.
185
+ */
186
+ export async function releasesRecordFromResponse(
187
+ response: Response,
188
+ ): Promise<ReleasesRecord | null> {
189
+ const errorCode = response.headers.get("x-error-code");
190
+ if (response.status === 404 && (errorCode == null || !MISSING_REPOSITORY_OR_REVISION.has(errorCode))) {
191
+ return null;
192
+ }
193
+ if (!response.ok) {
194
+ throw new Error(
195
+ `Hugging Face returned ${response.status}${errorCode ? ` (${errorCode})` : ""} ` +
196
+ `for ${RELEASES_RECORD_PATH}.`,
197
+ );
198
+ }
199
+ return parseReleasesRecordBytes(new Uint8Array(await response.arrayBuffer()));
200
+ }
201
+
202
+ /** A build's folder under `releases/`, which identifies it: `<release>/<build>`. */
203
+ export function buildFolderId(release: string, build: string): string {
204
+ if (!isSafeIdentifier(release) || !isSafeIdentifier(build)) {
205
+ throw new ReleasesRecordError("A release and a build must be safe identifiers.");
206
+ }
207
+ return `${release}/${build}`;
208
+ }
209
+
210
+ export function parseBuildFolderId(
211
+ value: unknown,
212
+ ): { release: string; build: string } | null {
213
+ if (typeof value !== "string") return null;
214
+ const segments = value.split("/");
215
+ if (segments.length !== 2 || !segments.every(isSafeIdentifier)) return null;
216
+ return { release: segments[0], build: segments[1] };
217
+ }
218
+
219
+ export function isBuildFolderId(value: unknown): value is string {
220
+ return parseBuildFolderId(value) != null;
221
+ }
222
+
223
+ /**
224
+ * A folder under `releases/` that release files are read from: a release
225
+ * folder (`<release id>`) or a build folder (`<release>/<build>`). Folders
226
+ * are interpolated into Hugging Face URLs that carry the server's token, so
227
+ * each segment passes the release-id allowlist and none can leave `releases/`.
228
+ */
229
+ export function isReleaseDirectory(value: unknown): value is string {
230
+ return isSafeIdentifier(value) || isBuildFolderId(value);
231
+ }
232
+
233
+ /** Every listed build, ordered by release, then build, as the writer sorts them. */
234
+ export function recordedBuilds(record: ReleasesRecord): RecordedBuild[] {
235
+ const byName = (left: string, right: string) => (left < right ? -1 : left > right ? 1 : 0);
236
+ return Object.keys(record.releases).sort(byName).flatMap((release) => {
237
+ const entry = record.releases[release];
238
+ return Object.keys(entry.builds).sort(byName).map((build) => ({
239
+ release,
240
+ build,
241
+ id: buildFolderId(release, build),
242
+ role: entry.role,
243
+ publishedAt: entry.builds[build].publishedAt,
244
+ withdrawn: entry.builds[build].withdrawn,
245
+ current: entry.current === build,
246
+ inDefaultRelease: record.default === release,
247
+ }));
248
+ });
249
+ }
250
+
251
+ /**
252
+ * The repository's current build: the current build of its default release,
253
+ * or null while the record names no default release or that release has no
254
+ * current build.
255
+ */
256
+ export function currentDefaultBuild(
257
+ record: ReleasesRecord,
258
+ ): { release: string; build: string; id: string } | null {
259
+ const release = record.default;
260
+ const build = release == null ? null : record.releases[release]?.current ?? null;
261
+ if (release == null || build == null) return null;
262
+ return { release, build, id: buildFolderId(release, build) };
263
+ }
264
+
265
+ /**
266
+ * Whether a build folder's release manifest names that release and build:
267
+ * the writer's `check_repository` rule, read from the manifest's top-level
268
+ * `release` and `build`. Manifests written before the build folders keep
269
+ * `build` as an object whose `build_id` is the release, and never match.
270
+ */
271
+ export function releaseManifestNamesBuild(
272
+ manifest: unknown,
273
+ release: string,
274
+ build: string,
275
+ ): boolean {
276
+ return isPlainObject(manifest) && manifest.release === release && manifest.build === build;
277
+ }
@@ -0,0 +1,5 @@
1
+ import { sourceAuthorityLabel } from "./source-labels";
2
+
3
+ export function sourceLabel(source: string): string {
4
+ return sourceAuthorityLabel(source);
5
+ }
@@ -0,0 +1,71 @@
1
+ // Shared presentation names for source authorities. Keys remain stable data
2
+ // identifiers; these labels are display-only and never participate in filters
3
+ // or joins.
4
+ const SOURCE_LABELS: Record<string, string> = {
5
+ bea: "BEA",
6
+ cbo: "CBO",
7
+ census_acs: "Census · American Community Survey",
8
+ census_pep: "Census · Population Estimates Program",
9
+ census_population: "Census population",
10
+ census_population_projections: "Census · Population Projections",
11
+ census_stc: "Census · State Tax Collections",
12
+ cms_aca: "CMS · ACA marketplace",
13
+ cms_medicaid: "CMS · Medicaid / CHIP",
14
+ cms_medicare: "CMS · Medicare",
15
+ cms_nhe: "CMS · National Health Expenditure Accounts",
16
+ ed_fsa: "ED · Federal Student Aid",
17
+ eurostat: "Eurostat",
18
+ federal_reserve: "Federal Reserve",
19
+ hhs_acf_liheap: "HHS · LIHEAP",
20
+ hhs_acf_tanf: "HHS · TANF",
21
+ ici: "Investment Company Institute",
22
+ irs_soi: "IRS Statistics of Income",
23
+ jct: "JCT",
24
+ jrc: "JRC",
25
+ kff: "KFF",
26
+ ssa: "SSA",
27
+ statbel: "Statbel",
28
+ onss: "ONSS",
29
+ sfpd: "SFPD",
30
+ nasa: "NASA",
31
+ state_income_tax: "State income tax",
32
+ usda_snap: "USDA · SNAP",
33
+ };
34
+
35
+ const SOURCE_ACRONYMS = new Set([
36
+ "aca",
37
+ "acf",
38
+ "acs",
39
+ "api",
40
+ "bea",
41
+ "cbo",
42
+ "cms",
43
+ "hhs",
44
+ "ici",
45
+ "irs",
46
+ "jct",
47
+ "jrc",
48
+ "kff",
49
+ "liheap",
50
+ "nhe",
51
+ "pep",
52
+ "soi",
53
+ "ssa",
54
+ "onss",
55
+ "sfpd",
56
+ "stc",
57
+ "tanf",
58
+ "usda",
59
+ ]);
60
+
61
+ export function sourceAuthorityLabel(source: string): string {
62
+ if (Object.hasOwn(SOURCE_LABELS, source)) return SOURCE_LABELS[source];
63
+ return source
64
+ .split("_")
65
+ .map((word) =>
66
+ SOURCE_ACRONYMS.has(word.toLowerCase())
67
+ ? word.toUpperCase()
68
+ : word.charAt(0).toUpperCase() + word.slice(1),
69
+ )
70
+ .join(" ");
71
+ }