@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.
- package/LICENSE +21 -0
- package/README.md +58 -2
- package/package.json +71 -4
- package/src/calibration-build-comparison.ts +77 -0
- package/src/calibration-comparison-blob.ts +367 -0
- package/src/calibration-comparison-bundle.ts +149 -0
- package/src/calibration-explorer.ts +202 -0
- package/src/calibration-release-locator.ts +353 -0
- package/src/calibration-tree-artifact.ts +1974 -0
- package/src/calibration-tree-blob.ts +403 -0
- package/src/calibration-tree-bundle.ts +569 -0
- package/src/calibration-tree-manifest.ts +349 -0
- package/src/calibration-tree-worker.ts +445 -0
- package/src/calibration-tree.ts +1284 -0
- package/src/chronicle-metadata.ts +42 -0
- package/src/countries.ts +355 -0
- package/src/geography-labels.ts +59 -0
- package/src/hierarchy-catalog-reader.ts +297 -0
- package/src/hierarchy-target-reader.ts +243 -0
- package/src/historical-weight-summary.ts +53 -0
- package/src/latest-artifact.ts +3767 -0
- package/src/legacy-target-reader.ts +337 -0
- package/src/program-label.ts +64 -0
- package/src/publish-calibration-tree.ts +1984 -0
- package/src/reforms.ts +224 -0
- package/src/release-cut.ts +32 -0
- package/src/release-record.ts +277 -0
- package/src/source-label.ts +5 -0
- package/src/source-labels.ts +71 -0
- package/src/staging-artifact.ts +1411 -0
- package/src/staging-contract.ts +916 -0
- package/src/structured-dimension-reader.ts +137 -0
- package/src/structured-target-reader.ts +76 -0
- package/src/target-change.ts +421 -0
- package/src/target-loss-attribution-manifest.ts +436 -0
- package/src/target-loss-attribution.ts +1012 -0
- package/src/target-representation.ts +69 -0
- package/src/target-surface-matcher.ts +266 -0
- package/types/next-fetch-cache.d.ts +8 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
type JsonObject = Record<string, unknown>;
|
|
2
|
+
|
|
3
|
+
export interface ParsedLegacyTarget {
|
|
4
|
+
geography: string;
|
|
5
|
+
level: string;
|
|
6
|
+
source: string;
|
|
7
|
+
variable: string;
|
|
8
|
+
breakdown: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface LegacyFilterDecomposition {
|
|
12
|
+
geography: string;
|
|
13
|
+
level: string;
|
|
14
|
+
dimensions: ReadonlyArray<{ value: string }>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const FIPS_TO_ABBR: Record<string, string> = {
|
|
18
|
+
"01": "AL", "02": "AK", "04": "AZ", "05": "AR", "06": "CA", "08": "CO",
|
|
19
|
+
"09": "CT", "10": "DE", "11": "DC", "12": "FL", "13": "GA", "15": "HI",
|
|
20
|
+
"16": "ID", "17": "IL", "18": "IN", "19": "IA", "20": "KS", "21": "KY",
|
|
21
|
+
"22": "LA", "23": "ME", "24": "MD", "25": "MA", "26": "MI", "27": "MN",
|
|
22
|
+
"28": "MS", "29": "MO", "30": "MT", "31": "NE", "32": "NV", "33": "NH",
|
|
23
|
+
"34": "NJ", "35": "NM", "36": "NY", "37": "NC", "38": "ND", "39": "OH",
|
|
24
|
+
"40": "OK", "41": "OR", "42": "PA", "44": "RI", "45": "SC", "46": "SD",
|
|
25
|
+
"47": "TN", "48": "TX", "49": "UT", "50": "VT", "51": "VA", "53": "WA",
|
|
26
|
+
"54": "WV", "55": "WI", "56": "WY",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const STATE_ABBRS = new Set([
|
|
30
|
+
"AL", "AK", "AZ", "AR", "CA", "CO", "CT", "DE", "DC", "FL", "GA", "HI", "ID",
|
|
31
|
+
"IL", "IN", "IA", "KS", "KY", "LA", "ME", "MD", "MA", "MI", "MN", "MS", "MO",
|
|
32
|
+
"MT", "NE", "NV", "NH", "NJ", "NM", "NY", "NC", "ND", "OH", "OK", "OR", "PA",
|
|
33
|
+
"RI", "SC", "SD", "TN", "TX", "UT", "VT", "VA", "WA", "WV", "WI", "WY", "US",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
// Compatibility labels for the country-level GSS identifiers in the current
|
|
37
|
+
// UK schema-6 artifact. Future artifacts should publish geography labels in
|
|
38
|
+
// structured dimension definitions or `ledger_geography_name`.
|
|
39
|
+
const LEGACY_GSS_GEOGRAPHIES: Readonly<Record<string, string>> = {
|
|
40
|
+
K02000001: "United Kingdom",
|
|
41
|
+
K03000001: "Great Britain",
|
|
42
|
+
E92000001: "England",
|
|
43
|
+
S92000003: "Scotland",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const MEASURES = new Set(["total", "count", "mean", "filers", "nonfilers"]);
|
|
47
|
+
const LEGACY_UNDERSCORE_PUBLISHERS = new Set([
|
|
48
|
+
"statbel",
|
|
49
|
+
"onss",
|
|
50
|
+
"jrc",
|
|
51
|
+
"sfpd",
|
|
52
|
+
"nasa",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
function asObject(value: unknown): JsonObject {
|
|
56
|
+
return value != null && typeof value === "object" && !Array.isArray(value)
|
|
57
|
+
? (value as JsonObject)
|
|
58
|
+
: {};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stringValue(value: unknown): string | null {
|
|
62
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readableToken(value: string | null): string | null {
|
|
66
|
+
return value ? value.replace(/_/g, " ") : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function stateFromGeoId(value: string | null): string | null {
|
|
70
|
+
if (!value) return null;
|
|
71
|
+
const match = /US(\d{2})$/.exec(value);
|
|
72
|
+
return match ? FIPS_TO_ABBR[match[1]] ?? null : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function districtFromGeoId(value: string | null): string | null {
|
|
76
|
+
if (!value) return null;
|
|
77
|
+
const match = /US(\d{2})(\d{2})$/.exec(value);
|
|
78
|
+
if (!match) return null;
|
|
79
|
+
const state = FIPS_TO_ABBR[match[1]];
|
|
80
|
+
return state ? `${state}-${match[2]}` : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function geographyFromMetadata(
|
|
84
|
+
metadata: JsonObject,
|
|
85
|
+
nationalGeography: string,
|
|
86
|
+
): Pick<ParsedLegacyTarget, "geography" | "level"> | null {
|
|
87
|
+
const publishedName = stringValue(metadata.ledger_geography_name)?.trim() ?? null;
|
|
88
|
+
const geographyId = stringValue(metadata.ledger_geography_id);
|
|
89
|
+
const geographyLevel = stringValue(metadata.ledger_geography_level);
|
|
90
|
+
const gssGeography = geographyId ? LEGACY_GSS_GEOGRAPHIES[geographyId] : null;
|
|
91
|
+
|
|
92
|
+
if (publishedName) {
|
|
93
|
+
return {
|
|
94
|
+
geography: publishedName,
|
|
95
|
+
level:
|
|
96
|
+
geographyLevel === "country" &&
|
|
97
|
+
publishedName === nationalGeography &&
|
|
98
|
+
!gssGeography
|
|
99
|
+
? "national"
|
|
100
|
+
: geographyLevel ?? "",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (gssGeography) {
|
|
104
|
+
return { geography: gssGeography, level: geographyLevel ?? "country" };
|
|
105
|
+
}
|
|
106
|
+
if (geographyLevel === "congressional_district") {
|
|
107
|
+
const geography = districtFromGeoId(geographyId);
|
|
108
|
+
return geography ? { geography, level: geographyLevel } : null;
|
|
109
|
+
}
|
|
110
|
+
if (geographyLevel === "state") {
|
|
111
|
+
const geography = stateFromGeoId(geographyId) ?? stringValue(metadata.state);
|
|
112
|
+
return geography ? { geography, level: geographyLevel } : null;
|
|
113
|
+
}
|
|
114
|
+
if (geographyLevel === "country") {
|
|
115
|
+
return { geography: nationalGeography, level: "national" };
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function variableFromMeasure(value: string | null): string | null {
|
|
121
|
+
return value
|
|
122
|
+
? value.replace(/_(amount|returns|claims|count|total|collections|projected_amount)$/, "")
|
|
123
|
+
: null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function breakdownFromSourceMeasure(
|
|
127
|
+
variable: string | null,
|
|
128
|
+
measureId: string | null,
|
|
129
|
+
): string | null {
|
|
130
|
+
if (!variable || !measureId) return null;
|
|
131
|
+
const variablePrefix = variable.replace(/\s+/g, "_").toLowerCase();
|
|
132
|
+
const measure = measureId.toLowerCase();
|
|
133
|
+
if (!measure.startsWith(`${variablePrefix}_`)) return null;
|
|
134
|
+
const detail = measure
|
|
135
|
+
.slice(variablePrefix.length + 1)
|
|
136
|
+
.replace(/_(amount|returns|claims|count|total|collections|projected_amount)$/, "");
|
|
137
|
+
if (
|
|
138
|
+
variablePrefix === "eitc" &&
|
|
139
|
+
["amount", "returns", "claims", "count", "total"].includes(detail)
|
|
140
|
+
) {
|
|
141
|
+
return "all children";
|
|
142
|
+
}
|
|
143
|
+
if (
|
|
144
|
+
!detail ||
|
|
145
|
+
MEASURES.has(detail) ||
|
|
146
|
+
["amount", "returns", "claims", "count", "total", "collections", "projected_amount"]
|
|
147
|
+
.includes(detail)
|
|
148
|
+
) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return readableToken(detail);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function qualifyingChildrenFromRecordSet(value: string | null): string | null {
|
|
155
|
+
if (!value) return null;
|
|
156
|
+
const match = /\.eitc_by_agi_children\.([^.]+)$/.exec(value);
|
|
157
|
+
if (!match) return null;
|
|
158
|
+
const childGroup = match[1];
|
|
159
|
+
if (childGroup === "no_qualifying_children") return "no qualifying children";
|
|
160
|
+
if (childGroup === "one_qualifying_child") return "one qualifying child";
|
|
161
|
+
if (childGroup === "two_qualifying_children") return "two qualifying children";
|
|
162
|
+
if (childGroup === "three_or_more_qualifying_children") {
|
|
163
|
+
return "three or more qualifying children";
|
|
164
|
+
}
|
|
165
|
+
return readableToken(childGroup);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function chroniclePublisherFromMetadata(metadata: JsonObject): string | null {
|
|
169
|
+
if (!Array.isArray(metadata.chronicle_record_ids)) return null;
|
|
170
|
+
const first = metadata.chronicle_record_ids.find(
|
|
171
|
+
(value): value is string => typeof value === "string" && Boolean(value.trim()),
|
|
172
|
+
);
|
|
173
|
+
return first?.trim().split(".", 1)[0] || null;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function legacyVariable(
|
|
177
|
+
name: string,
|
|
178
|
+
row: JsonObject,
|
|
179
|
+
decomposition: LegacyFilterDecomposition | null,
|
|
180
|
+
): string | null {
|
|
181
|
+
const metadata = asObject(row.metadata);
|
|
182
|
+
const published =
|
|
183
|
+
stringValue(metadata.variable) ??
|
|
184
|
+
(typeof row.variable === "string" ? stringValue(row.variable) : null);
|
|
185
|
+
if (published) return readableToken(published);
|
|
186
|
+
|
|
187
|
+
if (decomposition && name.includes("_") && !name.includes("/")) {
|
|
188
|
+
let identifier = name;
|
|
189
|
+
const filterSuffix = stringValue(row.filter)?.replace(/^cell_/, "");
|
|
190
|
+
if (filterSuffix && identifier.endsWith(`_${filterSuffix}`)) {
|
|
191
|
+
identifier = identifier.slice(0, -(filterSuffix.length + 1));
|
|
192
|
+
}
|
|
193
|
+
const separator = identifier.indexOf("_");
|
|
194
|
+
if (separator >= 0) identifier = identifier.slice(separator + 1);
|
|
195
|
+
return readableToken(identifier);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const namePublisher = name.split("_", 1)[0];
|
|
199
|
+
if (LEGACY_UNDERSCORE_PUBLISHERS.has(namePublisher)) {
|
|
200
|
+
return readableToken(name.slice(namePublisher.length + 1));
|
|
201
|
+
}
|
|
202
|
+
const publisher = chroniclePublisherFromMetadata(metadata);
|
|
203
|
+
if (publisher && !name.includes(".") && name.startsWith(`${publisher}_`)) {
|
|
204
|
+
return readableToken(name.slice(publisher.length + 1));
|
|
205
|
+
}
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function parseDottedTarget(
|
|
210
|
+
name: string,
|
|
211
|
+
row: JsonObject,
|
|
212
|
+
nationalGeography: string,
|
|
213
|
+
): ParsedLegacyTarget | null {
|
|
214
|
+
if (!name.includes(".")) return null;
|
|
215
|
+
const metadata = asObject(row.metadata);
|
|
216
|
+
const registry = asObject(row.registry);
|
|
217
|
+
const parts = name.split(".");
|
|
218
|
+
const source = stringValue(registry.family) ?? parts[0] ?? "";
|
|
219
|
+
const measureId = stringValue(metadata.source_measure_id) ?? parts.at(-1) ?? "";
|
|
220
|
+
const variable =
|
|
221
|
+
readableToken(stringValue(metadata.variable)) ??
|
|
222
|
+
readableToken(variableFromMeasure(measureId)) ??
|
|
223
|
+
readableToken(parts.at(-2) ?? null) ??
|
|
224
|
+
"";
|
|
225
|
+
const childBreakdown = qualifyingChildrenFromRecordSet(
|
|
226
|
+
stringValue(metadata.ledger_layout_record_set_id),
|
|
227
|
+
);
|
|
228
|
+
const breakdown = [
|
|
229
|
+
readableToken(stringValue(metadata.ledger_layout_groupby_value_id)),
|
|
230
|
+
childBreakdown ?? breakdownFromSourceMeasure(variable, measureId),
|
|
231
|
+
readableToken(stringValue(metadata.filing_status)),
|
|
232
|
+
]
|
|
233
|
+
.filter((value): value is string => Boolean(value && value !== variable))
|
|
234
|
+
.join(" · ");
|
|
235
|
+
|
|
236
|
+
const stateMarkerIndex = parts.findIndex((part) => part.startsWith("state_"));
|
|
237
|
+
const state = stateMarkerIndex < 0
|
|
238
|
+
? null
|
|
239
|
+
: parts
|
|
240
|
+
.slice(stateMarkerIndex + 1, stateMarkerIndex + 3)
|
|
241
|
+
.map((part) => part.toUpperCase())
|
|
242
|
+
.find((part) => STATE_ABBRS.has(part) && part !== "US") ?? null;
|
|
243
|
+
const national = parts.includes("us");
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
geography: state ?? (national ? nationalGeography : ""),
|
|
247
|
+
level: state ? "state" : national ? "national" : "",
|
|
248
|
+
source,
|
|
249
|
+
variable,
|
|
250
|
+
breakdown,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function parseSlashTarget(name: string, nationalGeography: string): ParsedLegacyTarget {
|
|
255
|
+
const parts = name.split("/");
|
|
256
|
+
const p0 = parts[0] ?? "";
|
|
257
|
+
const fips = /^US(\d{2})$/.exec(p0);
|
|
258
|
+
if (fips) {
|
|
259
|
+
return {
|
|
260
|
+
geography: FIPS_TO_ABBR[fips[1]] ?? p0,
|
|
261
|
+
level: "state",
|
|
262
|
+
source: "admin",
|
|
263
|
+
variable: parts[1] ?? "",
|
|
264
|
+
breakdown: parts.slice(2).join(" · "),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (p0 === "state") {
|
|
268
|
+
const second = parts[1] ?? "";
|
|
269
|
+
if (STATE_ABBRS.has(second) && second !== "US") {
|
|
270
|
+
return {
|
|
271
|
+
geography: second,
|
|
272
|
+
level: "state",
|
|
273
|
+
source: "state",
|
|
274
|
+
variable: parts[2] ?? "",
|
|
275
|
+
breakdown: parts.slice(3).join(" · "),
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const last = parts[parts.length - 1];
|
|
279
|
+
if (parts.length >= 4 && STATE_ABBRS.has(last)) {
|
|
280
|
+
return {
|
|
281
|
+
geography: last,
|
|
282
|
+
level: "state",
|
|
283
|
+
source: parts[1] ?? "",
|
|
284
|
+
variable: parts[2] ?? "",
|
|
285
|
+
breakdown: parts.slice(3, -1).join(" · "),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
geography: "state",
|
|
290
|
+
level: "state",
|
|
291
|
+
source: parts[1] ?? "",
|
|
292
|
+
variable: parts[2] ?? "",
|
|
293
|
+
breakdown: parts.slice(3).join(" · "),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
if (p0 === "nation" || p0 === "national" || p0 === "us") {
|
|
297
|
+
return {
|
|
298
|
+
geography: nationalGeography,
|
|
299
|
+
level: "national",
|
|
300
|
+
source: parts[1] ?? "",
|
|
301
|
+
variable: parts[2] ?? "",
|
|
302
|
+
breakdown: parts.slice(3).join(" · "),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
geography: "",
|
|
307
|
+
level: "",
|
|
308
|
+
source: p0,
|
|
309
|
+
variable: parts[1] ?? "",
|
|
310
|
+
breakdown: parts.slice(2).join(" · "),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function readLegacyTarget(
|
|
315
|
+
name: string,
|
|
316
|
+
row: JsonObject,
|
|
317
|
+
nationalGeography: string,
|
|
318
|
+
decomposition: LegacyFilterDecomposition | null,
|
|
319
|
+
): ParsedLegacyTarget {
|
|
320
|
+
const metadata = asObject(row.metadata);
|
|
321
|
+
const parsed =
|
|
322
|
+
parseDottedTarget(name, row, nationalGeography) ??
|
|
323
|
+
parseSlashTarget(name, nationalGeography);
|
|
324
|
+
const publisher = chroniclePublisherFromMetadata(metadata);
|
|
325
|
+
const metadataGeography = geographyFromMetadata(metadata, nationalGeography);
|
|
326
|
+
return {
|
|
327
|
+
...parsed,
|
|
328
|
+
geography:
|
|
329
|
+
decomposition?.geography ?? metadataGeography?.geography ?? parsed.geography,
|
|
330
|
+
level: decomposition?.level ?? metadataGeography?.level ?? parsed.level,
|
|
331
|
+
source: publisher ?? parsed.source,
|
|
332
|
+
variable: legacyVariable(name, row, decomposition) ?? parsed.variable,
|
|
333
|
+
breakdown: decomposition
|
|
334
|
+
? decomposition.dimensions.map((dimension) => dimension.value).join(" · ")
|
|
335
|
+
: parsed.breakdown,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// These are exact, domain-approved tokens—not guesses based on word length.
|
|
2
|
+
const CANONICAL_ACRONYM_LABELS: Readonly<Record<string, string>> = {
|
|
3
|
+
aca: "ACA",
|
|
4
|
+
actc: "ACTC",
|
|
5
|
+
agi: "AGI",
|
|
6
|
+
amt: "AMT",
|
|
7
|
+
cdcc: "CDCC",
|
|
8
|
+
ctc: "CTC",
|
|
9
|
+
eitc: "EITC",
|
|
10
|
+
fica: "FICA",
|
|
11
|
+
fsa: "FSA",
|
|
12
|
+
hsa: "HSA",
|
|
13
|
+
ira: "IRA",
|
|
14
|
+
magi: "MAGI",
|
|
15
|
+
oasdi: "OASDI",
|
|
16
|
+
ptc: "PTC",
|
|
17
|
+
qbi: "QBI",
|
|
18
|
+
salt: "SALT",
|
|
19
|
+
se: "SE",
|
|
20
|
+
snap: "SNAP",
|
|
21
|
+
ssi: "SSI",
|
|
22
|
+
tanf: "TANF",
|
|
23
|
+
ui: "UI",
|
|
24
|
+
w2: "W2",
|
|
25
|
+
z1: "Z1",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function canonicalLabelKey(identifier: string): string {
|
|
29
|
+
return identifier
|
|
30
|
+
.trim()
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[_\s-]+/g, "_");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Resolve a canonical identifier's presentation label without changing its ID.
|
|
37
|
+
* An upstream label wins when release artifacts begin supplying one; otherwise
|
|
38
|
+
* exact domain overrides precede a conservative sentence-case fallback.
|
|
39
|
+
*/
|
|
40
|
+
export function canonicalLabel(
|
|
41
|
+
identifier: string | null | undefined,
|
|
42
|
+
explicitLabel?: string | null,
|
|
43
|
+
): string {
|
|
44
|
+
const suppliedLabel = explicitLabel?.trim();
|
|
45
|
+
if (suppliedLabel) return suppliedLabel;
|
|
46
|
+
|
|
47
|
+
if (!identifier?.trim()) return "";
|
|
48
|
+
const key = canonicalLabelKey(identifier);
|
|
49
|
+
return key
|
|
50
|
+
.split("_")
|
|
51
|
+
.map((token, index) => {
|
|
52
|
+
const acronym = CANONICAL_ACRONYM_LABELS[token];
|
|
53
|
+
if (acronym) return acronym;
|
|
54
|
+
return index === 0 ? token[0].toUpperCase() + token.slice(1) : token;
|
|
55
|
+
})
|
|
56
|
+
.join(" ");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function programLabel(
|
|
60
|
+
identifier: string | null | undefined,
|
|
61
|
+
explicitLabel?: string | null,
|
|
62
|
+
): string {
|
|
63
|
+
return canonicalLabel(identifier, explicitLabel);
|
|
64
|
+
}
|