@openparser/lineage 1.0.2
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/CHANGELOG.md +89 -0
- package/LICENSE +201 -0
- package/README.md +211 -0
- package/dist/activity.d.ts +74 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/approval.d.ts +24 -0
- package/dist/approval.d.ts.map +1 -0
- package/dist/builder.d.ts +48 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/common.d.ts +34 -0
- package/dist/common.d.ts.map +1 -0
- package/dist/confidence.d.ts +61 -0
- package/dist/confidence.d.ts.map +1 -0
- package/dist/derivation.d.ts +163 -0
- package/dist/derivation.d.ts.map +1 -0
- package/dist/document.d.ts +412 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/entity.d.ts +126 -0
- package/dist/entity.d.ts.map +1 -0
- package/dist/fields.d.ts +76 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +870 -0
- package/dist/labels.d.ts +11 -0
- package/dist/labels.d.ts.map +1 -0
- package/dist/profile/grounding.d.ts +34 -0
- package/dist/profile/grounding.d.ts.map +1 -0
- package/dist/profile/index.d.ts +14 -0
- package/dist/profile/index.d.ts.map +1 -0
- package/dist/profile/index.js +1298 -0
- package/dist/profile/labels.d.ts +10 -0
- package/dist/profile/labels.d.ts.map +1 -0
- package/dist/profile/recognition.d.ts +70 -0
- package/dist/profile/recognition.d.ts.map +1 -0
- package/dist/profile/transform-validation.d.ts +250 -0
- package/dist/profile/transform-validation.d.ts.map +1 -0
- package/dist/prov.d.ts +40 -0
- package/dist/prov.d.ts.map +1 -0
- package/dist/relation.d.ts +27 -0
- package/dist/relation.d.ts.map +1 -0
- package/dist/traverse.d.ts +12 -0
- package/dist/traverse.d.ts.map +1 -0
- package/dist/validate.d.ts +14 -0
- package/dist/validate.d.ts.map +1 -0
- package/lineage.schema.json +625 -0
- package/package.json +71 -0
- package/src/activity.ts +65 -0
- package/src/approval.ts +28 -0
- package/src/builder.ts +129 -0
- package/src/common.ts +58 -0
- package/src/confidence.ts +72 -0
- package/src/derivation.ts +81 -0
- package/src/document.ts +82 -0
- package/src/entity.ts +87 -0
- package/src/fields.ts +163 -0
- package/src/index.ts +123 -0
- package/src/labels.ts +13 -0
- package/src/profile/grounding.ts +74 -0
- package/src/profile/index.ts +67 -0
- package/src/profile/labels.ts +28 -0
- package/src/profile/recognition.ts +149 -0
- package/src/profile/transform-validation.ts +1379 -0
- package/src/prov.ts +205 -0
- package/src/relation.ts +32 -0
- package/src/traverse.ts +91 -0
- package/src/validate.ts +221 -0
|
@@ -0,0 +1,1298 @@
|
|
|
1
|
+
// src/profile/labels.ts
|
|
2
|
+
var ACTIVITY_LABELS = {
|
|
3
|
+
ocr: "Parse",
|
|
4
|
+
extract: "Schema-constrained extraction",
|
|
5
|
+
transform: "Transform",
|
|
6
|
+
grounding_resolution: "Grounding resolution and validation",
|
|
7
|
+
"review.confirm": "Human review"
|
|
8
|
+
};
|
|
9
|
+
function activityLabel(activity) {
|
|
10
|
+
return activity.name ?? ACTIVITY_LABELS[activity.type] ?? activity.type;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// src/traverse.ts
|
|
14
|
+
function buildReverseEdges(doc) {
|
|
15
|
+
const edges = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const id of Object.keys(doc.entities)) edges.set(id, []);
|
|
17
|
+
for (const derivation of doc.derivations) {
|
|
18
|
+
if (!(derivation.output in doc.entities)) continue;
|
|
19
|
+
for (const input of derivation.inputs) {
|
|
20
|
+
if (!(input.entity in doc.entities)) continue;
|
|
21
|
+
edges.get(derivation.output).push(input.entity);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return edges;
|
|
25
|
+
}
|
|
26
|
+
function collectReachable(start, edges) {
|
|
27
|
+
if (!edges.has(start)) return [];
|
|
28
|
+
const seen = /* @__PURE__ */ new Set();
|
|
29
|
+
const stack = [...edges.get(start) ?? []];
|
|
30
|
+
while (stack.length > 0) {
|
|
31
|
+
const id = stack.pop();
|
|
32
|
+
if (seen.has(id)) continue;
|
|
33
|
+
seen.add(id);
|
|
34
|
+
for (const next of edges.get(id) ?? []) {
|
|
35
|
+
if (!seen.has(next)) stack.push(next);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [...seen];
|
|
39
|
+
}
|
|
40
|
+
function entityAncestors(doc, entityId) {
|
|
41
|
+
return collectReachable(entityId, buildReverseEdges(doc));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/fields.ts
|
|
45
|
+
function fieldPointer(entity) {
|
|
46
|
+
return entity.path ?? "";
|
|
47
|
+
}
|
|
48
|
+
function fieldConfidence(entity) {
|
|
49
|
+
const assertions = entity.confidence;
|
|
50
|
+
if (!assertions || assertions.length === 0) return void 0;
|
|
51
|
+
return assertions.find((assertion) => assertion.scope === "extraction") ?? assertions[0];
|
|
52
|
+
}
|
|
53
|
+
function dottedPathFromPointer(pointer) {
|
|
54
|
+
if (pointer === "" || pointer === "/") return "";
|
|
55
|
+
return pointer.replace(/^\//, "").split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~")).join(".");
|
|
56
|
+
}
|
|
57
|
+
function toField(id, entity) {
|
|
58
|
+
const pointer = fieldPointer(entity);
|
|
59
|
+
const confidence = fieldConfidence(entity);
|
|
60
|
+
return {
|
|
61
|
+
id,
|
|
62
|
+
pointer,
|
|
63
|
+
dottedPath: dottedPathFromPointer(pointer),
|
|
64
|
+
value: entity.value,
|
|
65
|
+
confidence,
|
|
66
|
+
confidenceOrigin: confidence ? confidence.kind : "not_reported",
|
|
67
|
+
entity
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function fields(doc) {
|
|
71
|
+
const result = [];
|
|
72
|
+
for (const id of doc.outputs) {
|
|
73
|
+
const entity = doc.entities[id];
|
|
74
|
+
if (!entity) continue;
|
|
75
|
+
if (fieldPointer(entity) === "") continue;
|
|
76
|
+
result.push(toField(id, entity));
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
function fieldTrace(doc, pointer) {
|
|
81
|
+
const field = fields(doc).find((candidate) => candidate.pointer === pointer);
|
|
82
|
+
if (!field) return void 0;
|
|
83
|
+
const ancestorIds = entityAncestors(doc, field.id);
|
|
84
|
+
const evidence = ancestorIds.filter((id) => {
|
|
85
|
+
const entity = doc.entities[id];
|
|
86
|
+
return entity?.kind === "collection" || entity?.kind === "evidence";
|
|
87
|
+
}).sort().map((id) => ({ id, entity: doc.entities[id] }));
|
|
88
|
+
const relevant = /* @__PURE__ */ new Set([field.id, ...ancestorIds]);
|
|
89
|
+
const activities = [
|
|
90
|
+
...new Set(doc.derivations.filter((d) => relevant.has(d.output)).map((d) => d.activity))
|
|
91
|
+
].sort();
|
|
92
|
+
return { field, evidence, activities };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/profile/grounding.ts
|
|
96
|
+
var DROPPED_SOURCES_ATTR = "openparser:droppedSourceIds";
|
|
97
|
+
function droppedSourceIds(producer) {
|
|
98
|
+
const dropped = producer?.attributes?.[DROPPED_SOURCES_ATTR];
|
|
99
|
+
return Array.isArray(dropped) ? dropped.filter((id) => typeof id === "string") : [];
|
|
100
|
+
}
|
|
101
|
+
function groundingStatus(options) {
|
|
102
|
+
if (options.evidenceCount === 0) return "ungrounded";
|
|
103
|
+
return options.droppedSourceIds.length > 0 ? "partial" : "grounded";
|
|
104
|
+
}
|
|
105
|
+
function collectDroppedSourceIds(doc, entityIds) {
|
|
106
|
+
const relevant = new Set(entityIds);
|
|
107
|
+
const seen = /* @__PURE__ */ new Set();
|
|
108
|
+
const dropped = [];
|
|
109
|
+
for (const derivation of doc.derivations) {
|
|
110
|
+
if (!relevant.has(derivation.output)) continue;
|
|
111
|
+
for (const id of droppedSourceIds(derivation)) {
|
|
112
|
+
if (seen.has(id)) continue;
|
|
113
|
+
seen.add(id);
|
|
114
|
+
dropped.push(id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return dropped;
|
|
118
|
+
}
|
|
119
|
+
function fieldGrounding(doc, pointer) {
|
|
120
|
+
const trace = fieldTrace(doc, pointer);
|
|
121
|
+
if (!trace) return void 0;
|
|
122
|
+
const ancestorIds = entityAncestors(doc, trace.field.id);
|
|
123
|
+
const dropped = collectDroppedSourceIds(doc, [trace.field.id, ...ancestorIds]);
|
|
124
|
+
return {
|
|
125
|
+
status: groundingStatus({ evidenceCount: trace.evidence.length, droppedSourceIds: dropped }),
|
|
126
|
+
droppedSourceIds: dropped
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/profile/recognition.ts
|
|
131
|
+
var CITED_RECOGNITION_METHOD = "cited_minimum_context_downblend";
|
|
132
|
+
var REGION_RECOGNITION_METHOD = "region_word_minimum";
|
|
133
|
+
var CONTEXT_BLEND_WEIGHT = 0.15;
|
|
134
|
+
function downwardBlendCitedMinimum(citedMin, contextMean) {
|
|
135
|
+
if (contextMean === void 0) return citedMin;
|
|
136
|
+
const blended = citedMin * (1 - CONTEXT_BLEND_WEIGHT) + contextMean * CONTEXT_BLEND_WEIGHT;
|
|
137
|
+
return Math.min(citedMin, blended);
|
|
138
|
+
}
|
|
139
|
+
function aggregateCitedWordConfidences(words) {
|
|
140
|
+
if (words.length === 0) return void 0;
|
|
141
|
+
const cited = words.filter((word) => word.cited !== false);
|
|
142
|
+
const context = words.filter((word) => word.cited === false);
|
|
143
|
+
const active = cited.length > 0 ? cited : words;
|
|
144
|
+
const activeScores = active.map((word) => word.confidence);
|
|
145
|
+
const contextMean = cited.length > 0 && context.length > 0 ? context.reduce((sum, word) => sum + word.confidence, 0) / context.length : void 0;
|
|
146
|
+
const citedMin = Math.min(...activeScores);
|
|
147
|
+
return {
|
|
148
|
+
score: downwardBlendCitedMinimum(citedMin, contextMean),
|
|
149
|
+
minimum: citedMin,
|
|
150
|
+
maximum: Math.max(...activeScores),
|
|
151
|
+
sampleCount: active.length,
|
|
152
|
+
method: cited.length > 0 ? CITED_RECOGNITION_METHOD : REGION_RECOGNITION_METHOD,
|
|
153
|
+
...contextMean !== void 0 ? { contextMean, contextSampleCount: context.length } : {}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function citedSpanInText(regionText, candidates) {
|
|
157
|
+
if (!regionText) return { start: -1, end: -1 };
|
|
158
|
+
for (const candidate of candidates) {
|
|
159
|
+
const quote = candidate?.trim();
|
|
160
|
+
if (!quote) continue;
|
|
161
|
+
let start = regionText.indexOf(quote);
|
|
162
|
+
if (start < 0) start = regionText.toLowerCase().indexOf(quote.toLowerCase());
|
|
163
|
+
if (start >= 0) return { start, end: start + quote.length };
|
|
164
|
+
}
|
|
165
|
+
return { start: -1, end: -1 };
|
|
166
|
+
}
|
|
167
|
+
function wordOverlapsSpan(regionText, word, cursor, span) {
|
|
168
|
+
if (!word.text) return { overlaps: false, nextCursor: cursor };
|
|
169
|
+
const at = regionText.indexOf(word.text, cursor);
|
|
170
|
+
if (at < 0) return { overlaps: false, nextCursor: cursor };
|
|
171
|
+
const wordEnd = at + word.text.length;
|
|
172
|
+
return {
|
|
173
|
+
overlaps: span.start >= 0 && at < span.end && wordEnd > span.start,
|
|
174
|
+
nextCursor: wordEnd
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function annotateWordsWithCitation(regionText, words, options) {
|
|
178
|
+
const span = citedSpanInText(regionText, [options?.extractedText]);
|
|
179
|
+
const hasQuoteSpan = span.start >= 0;
|
|
180
|
+
let cursor = 0;
|
|
181
|
+
return words.map((word) => {
|
|
182
|
+
const { overlaps, nextCursor } = wordOverlapsSpan(regionText, word, cursor, span);
|
|
183
|
+
cursor = nextCursor;
|
|
184
|
+
const inLineage = Boolean(
|
|
185
|
+
word.id && options?.lineageWordIds?.has(word.id) || options?.lineageWordTexts?.has(word.text)
|
|
186
|
+
);
|
|
187
|
+
return { ...word, cited: hasQuoteSpan ? overlaps : inLineage };
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/profile/transform-validation.ts
|
|
192
|
+
import Decimal from "decimal.js";
|
|
193
|
+
import { DateTime } from "luxon";
|
|
194
|
+
import { createHash } from "node:crypto";
|
|
195
|
+
import { z as z2 } from "zod";
|
|
196
|
+
|
|
197
|
+
// src/common.ts
|
|
198
|
+
import { z } from "zod";
|
|
199
|
+
var LineageIdSchema = z.string().trim().min(1).max(512);
|
|
200
|
+
var JsonValueSchema = z.json();
|
|
201
|
+
var AttributesSchema = z.record(z.string().min(1), JsonValueSchema);
|
|
202
|
+
var JsonPointerSchema = z.string().max(2048).regex(/^(?:\/(?:[^~/]|~0|~1)*)*$/, "path must be an RFC 6901 JSON Pointer");
|
|
203
|
+
var LINEAGE_FORMAT = "lineage@1";
|
|
204
|
+
var LineageFormatSchema = z.literal(LINEAGE_FORMAT);
|
|
205
|
+
var TimestampSchema = z.iso.datetime({ offset: true }).max(64);
|
|
206
|
+
|
|
207
|
+
// src/profile/transform-validation.ts
|
|
208
|
+
var TRANSFORM_VERIFICATION_DOCS = "https://docs.openparser.dev/lineage/openparser/transform-verification";
|
|
209
|
+
var TRANSFORM_PROOF_ATTRIBUTE = "openparser:transformProof";
|
|
210
|
+
var MAX_TRANSFORM_PROOF_BYTES = 2048;
|
|
211
|
+
var MAX_TRANSFORM_INTENT_PARAMETER_BYTES = 1024;
|
|
212
|
+
var TRANSFORM_DOCS_ANCHORS = [
|
|
213
|
+
"date-time-format",
|
|
214
|
+
"numeric-format",
|
|
215
|
+
"quantity-magnitude",
|
|
216
|
+
"unit-conversion",
|
|
217
|
+
"currency-code",
|
|
218
|
+
"boolean-alias",
|
|
219
|
+
"text-normalization",
|
|
220
|
+
"enum-alias"
|
|
221
|
+
];
|
|
222
|
+
var TransformProofClassSchema = z2.enum([
|
|
223
|
+
"safe_equivalence",
|
|
224
|
+
"exact_conversion",
|
|
225
|
+
"policy_application"
|
|
226
|
+
]);
|
|
227
|
+
var FORMAT_TOKENS = [
|
|
228
|
+
["yyyyy", "year", "full"],
|
|
229
|
+
["yyyy", "year", "full"],
|
|
230
|
+
["yyy", "year", "short"],
|
|
231
|
+
["yy", "year", "short"],
|
|
232
|
+
["y", "year", "short"],
|
|
233
|
+
["uuuuu", "year", "full"],
|
|
234
|
+
["uuuu", "year", "full"],
|
|
235
|
+
["uuu", "year", "short"],
|
|
236
|
+
["uu", "year", "short"],
|
|
237
|
+
["u", "year", "short"],
|
|
238
|
+
["MMMMM", "month"],
|
|
239
|
+
["MMMM", "month"],
|
|
240
|
+
["MMM", "month"],
|
|
241
|
+
["MM", "month"],
|
|
242
|
+
["M", "month"],
|
|
243
|
+
["LLLLL", "month"],
|
|
244
|
+
["LLLL", "month"],
|
|
245
|
+
["LLL", "month"],
|
|
246
|
+
["LL", "month"],
|
|
247
|
+
["L", "month"],
|
|
248
|
+
["dd", "day"],
|
|
249
|
+
["d", "day"],
|
|
250
|
+
["HH", "hour"],
|
|
251
|
+
["H", "hour"],
|
|
252
|
+
["hh", "hour"],
|
|
253
|
+
["h", "hour"],
|
|
254
|
+
["kk", "hour"],
|
|
255
|
+
["k", "hour"],
|
|
256
|
+
["mm", "minute"],
|
|
257
|
+
["m", "minute"],
|
|
258
|
+
["ss", "second"],
|
|
259
|
+
["s", "second"],
|
|
260
|
+
["SSS", "millisecond"],
|
|
261
|
+
["SS", "millisecond"],
|
|
262
|
+
["S", "millisecond"],
|
|
263
|
+
["ZZZZZ", "offset"],
|
|
264
|
+
["ZZZZ", "offset"],
|
|
265
|
+
["ZZZ", "offset"],
|
|
266
|
+
["ZZ", "offset"],
|
|
267
|
+
["Z", "offset"],
|
|
268
|
+
["zzz", "zone"],
|
|
269
|
+
["zz", "zone"],
|
|
270
|
+
["z", "zone"]
|
|
271
|
+
].toSorted((left, right) => right[0].length - left[0].length);
|
|
272
|
+
function unquotedFormatChunks(format) {
|
|
273
|
+
const chunks = [];
|
|
274
|
+
let i = 0;
|
|
275
|
+
let current = "";
|
|
276
|
+
while (i < format.length) {
|
|
277
|
+
if (format[i] === "'") {
|
|
278
|
+
if (current) chunks.push(current);
|
|
279
|
+
current = "";
|
|
280
|
+
i += 1;
|
|
281
|
+
while (i < format.length) {
|
|
282
|
+
if (format[i] === "'" && format[i + 1] === "'") {
|
|
283
|
+
i += 2;
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if (format[i] === "'") {
|
|
287
|
+
i += 1;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
i += 1;
|
|
291
|
+
}
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
current += format[i];
|
|
295
|
+
i += 1;
|
|
296
|
+
}
|
|
297
|
+
if (current) chunks.push(current);
|
|
298
|
+
return chunks;
|
|
299
|
+
}
|
|
300
|
+
function inspectFormat(format) {
|
|
301
|
+
const components = {
|
|
302
|
+
yearQuality: "none",
|
|
303
|
+
month: false,
|
|
304
|
+
day: false,
|
|
305
|
+
hour: false,
|
|
306
|
+
minute: false,
|
|
307
|
+
second: false,
|
|
308
|
+
millisecond: false,
|
|
309
|
+
offset: false,
|
|
310
|
+
zone: false
|
|
311
|
+
};
|
|
312
|
+
for (const chunk of unquotedFormatChunks(format)) {
|
|
313
|
+
let i = 0;
|
|
314
|
+
while (i < chunk.length) {
|
|
315
|
+
const match = FORMAT_TOKENS.find(([token]) => chunk.startsWith(token, i));
|
|
316
|
+
if (!match) {
|
|
317
|
+
i += 1;
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
const [, field, yearQuality] = match;
|
|
321
|
+
if (field === "year") {
|
|
322
|
+
if (yearQuality === "short" || components.yearQuality === "short") {
|
|
323
|
+
components.yearQuality = "short";
|
|
324
|
+
} else {
|
|
325
|
+
components.yearQuality = "full";
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
components[field] = true;
|
|
329
|
+
}
|
|
330
|
+
i += match[0].length;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return components;
|
|
334
|
+
}
|
|
335
|
+
function formatHasRequiredKindTokens(kind, components) {
|
|
336
|
+
if (components.yearQuality === "short") return false;
|
|
337
|
+
if (kind === "date") {
|
|
338
|
+
return components.yearQuality === "full" && components.month && components.day && !components.hour && !components.minute && !components.second && !components.millisecond && !components.offset && !components.zone;
|
|
339
|
+
}
|
|
340
|
+
if (kind === "time") {
|
|
341
|
+
return components.hour && components.minute && components.second && components.yearQuality === "none" && !components.month && !components.day;
|
|
342
|
+
}
|
|
343
|
+
return components.yearQuality === "full" && components.month && components.day && components.hour && components.minute && components.second;
|
|
344
|
+
}
|
|
345
|
+
function optionalTimeComponentsAlign(source, target) {
|
|
346
|
+
return source.millisecond === target.millisecond && source.offset === target.offset && source.zone === target.zone;
|
|
347
|
+
}
|
|
348
|
+
function dateTimeFormatIssue(kind, sourceFormats, targetFormat) {
|
|
349
|
+
const target = inspectFormat(targetFormat);
|
|
350
|
+
if (!formatHasRequiredKindTokens(kind, target)) {
|
|
351
|
+
return "target format must preserve every required component and must not use a two-digit year";
|
|
352
|
+
}
|
|
353
|
+
for (const format of sourceFormats) {
|
|
354
|
+
const source = inspectFormat(format);
|
|
355
|
+
if (!formatHasRequiredKindTokens(kind, source)) {
|
|
356
|
+
return "source format must preserve every required component and must not use a two-digit year";
|
|
357
|
+
}
|
|
358
|
+
if (!optionalTimeComponentsAlign(source, target)) {
|
|
359
|
+
return "source and target formats must not add or drop milliseconds, offset, or zone";
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return void 0;
|
|
363
|
+
}
|
|
364
|
+
var DATE_SOURCE_CATALOG_ID = "openparser.transform.date-source-catalog";
|
|
365
|
+
var DATE_SOURCE_CATALOG_VERSION = 1;
|
|
366
|
+
var FORMAT_ONLY_DATE_TARGETS = {
|
|
367
|
+
date: "yyyy-MM-dd",
|
|
368
|
+
"date-time": "yyyy-MM-dd'T'HH:mm:ssZZ",
|
|
369
|
+
time: "HH:mm:ssZZ"
|
|
370
|
+
};
|
|
371
|
+
var DATE_SOURCE_CATALOG_V1 = {
|
|
372
|
+
date: [
|
|
373
|
+
"yyyy-MM-dd",
|
|
374
|
+
"yyyy/MM/dd",
|
|
375
|
+
"yyyy.MM.dd",
|
|
376
|
+
"d MMMM yyyy",
|
|
377
|
+
"dd MMMM yyyy",
|
|
378
|
+
"MMMM d, yyyy",
|
|
379
|
+
"MMMM dd, yyyy",
|
|
380
|
+
"d MMM yyyy",
|
|
381
|
+
"dd MMM yyyy",
|
|
382
|
+
"MMM d, yyyy",
|
|
383
|
+
"MMM dd, yyyy",
|
|
384
|
+
"d 'day of' MMMM yyyy",
|
|
385
|
+
"dd 'day of' MMMM yyyy"
|
|
386
|
+
],
|
|
387
|
+
"date-time": ["yyyy-MM-dd'T'HH:mm:ssZZ", "yyyy-MM-dd'T'HH:mm:ssZ"],
|
|
388
|
+
time: ["HH:mm:ssZZ", "HH:mm:ssZ"]
|
|
389
|
+
};
|
|
390
|
+
function trustedDateSourceFormats(kind, targetFormat = FORMAT_ONLY_DATE_TARGETS[kind]) {
|
|
391
|
+
return DATE_SOURCE_CATALOG_V1[kind].filter(
|
|
392
|
+
(format) => dateTimeFormatIssue(kind, [format], targetFormat) === void 0
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
function formatOnlyDateTimeParameters(kind) {
|
|
396
|
+
return {
|
|
397
|
+
kind,
|
|
398
|
+
sourceFormats: trustedDateSourceFormats(kind, FORMAT_ONLY_DATE_TARGETS[kind]),
|
|
399
|
+
targetFormat: FORMAT_ONLY_DATE_TARGETS[kind],
|
|
400
|
+
locale: "en-US",
|
|
401
|
+
zonePolicy: kind === "date" ? "civil" : "preserve",
|
|
402
|
+
sourceCatalog: {
|
|
403
|
+
id: DATE_SOURCE_CATALOG_ID,
|
|
404
|
+
version: DATE_SOURCE_CATALOG_VERSION
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
var DateSourceCatalogRefSchema = z2.object({
|
|
409
|
+
id: z2.literal(DATE_SOURCE_CATALOG_ID),
|
|
410
|
+
version: z2.literal(DATE_SOURCE_CATALOG_VERSION)
|
|
411
|
+
}).strict();
|
|
412
|
+
var DateTimeParametersSchema = z2.object({
|
|
413
|
+
kind: z2.enum(["date", "date-time", "time"]),
|
|
414
|
+
sourceFormats: z2.array(z2.string().min(1).max(128)).max(16).default([]),
|
|
415
|
+
targetFormat: z2.string().min(1).max(128),
|
|
416
|
+
locale: z2.string().min(2).max(64).default("en-US"),
|
|
417
|
+
zonePolicy: z2.enum(["civil", "preserve", "utc", "named"]).default("civil"),
|
|
418
|
+
zone: z2.string().min(1).max(128).optional(),
|
|
419
|
+
sourceCatalog: DateSourceCatalogRefSchema.optional()
|
|
420
|
+
}).strict().superRefine((value, ctx) => {
|
|
421
|
+
if (value.zonePolicy === "named" && !value.zone) {
|
|
422
|
+
ctx.addIssue({ code: "custom", path: ["zone"], message: "named zone policy requires zone" });
|
|
423
|
+
}
|
|
424
|
+
if (value.kind === "date" && value.zonePolicy !== "civil") {
|
|
425
|
+
ctx.addIssue({
|
|
426
|
+
code: "custom",
|
|
427
|
+
path: ["zonePolicy"],
|
|
428
|
+
message: "date-only transforms must remain civil"
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (/[Zz]/.test(value.targetFormat) && value.zonePolicy === "civil") {
|
|
432
|
+
ctx.addIssue({
|
|
433
|
+
code: "custom",
|
|
434
|
+
path: ["zonePolicy"],
|
|
435
|
+
message: "target formats with an offset require preserve, utc, or named zone policy"
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
var NumericParametersSchema = z2.object({
|
|
440
|
+
decimalSeparator: z2.enum([".", ","]).default("."),
|
|
441
|
+
groupingSeparator: z2.enum([",", ".", " ", "\xA0"]).optional(),
|
|
442
|
+
prefixes: z2.array(z2.string().max(16)).max(16).default([]),
|
|
443
|
+
suffixes: z2.array(z2.string().max(16)).max(16).default([]),
|
|
444
|
+
accountingParentheses: z2.boolean().default(false)
|
|
445
|
+
}).strict().superRefine((value, ctx) => {
|
|
446
|
+
if (value.groupingSeparator === value.decimalSeparator) {
|
|
447
|
+
ctx.addIssue({
|
|
448
|
+
code: "custom",
|
|
449
|
+
path: ["groupingSeparator"],
|
|
450
|
+
message: "grouping and decimal separators must differ"
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
function policyMap(keyMax, maxKeys, schema) {
|
|
455
|
+
return z2.record(z2.string().min(1).max(keyMax), schema).refine((value) => Object.keys(value).length <= maxKeys, `at most ${maxKeys} keys`);
|
|
456
|
+
}
|
|
457
|
+
var QuantityParametersSchema = z2.object({
|
|
458
|
+
allowedUnits: policyMap(32, 16, z2.string().min(1).max(32)),
|
|
459
|
+
targetUnit: z2.string().min(1).max(32)
|
|
460
|
+
}).strict();
|
|
461
|
+
var UnitConversionParametersSchema = z2.object({
|
|
462
|
+
sourceUnits: z2.array(z2.string().min(1).max(32)).min(1).max(32),
|
|
463
|
+
targetUnit: z2.string().min(1).max(32)
|
|
464
|
+
}).strict();
|
|
465
|
+
var CurrencyParametersSchema = z2.object({
|
|
466
|
+
acceptedCodes: z2.array(z2.string().regex(/^[A-Z]{3}$/)).min(1).max(64),
|
|
467
|
+
aliases: policyMap(64, 16, z2.string().regex(/^[A-Z]{3}$/)).default({}),
|
|
468
|
+
symbolMap: policyMap(8, 16, z2.string().regex(/^[A-Z]{3}$/)).default({})
|
|
469
|
+
}).strict();
|
|
470
|
+
var BooleanParametersSchema = z2.object({
|
|
471
|
+
aliases: policyMap(64, 16, z2.boolean()),
|
|
472
|
+
caseSensitive: z2.boolean().default(false)
|
|
473
|
+
}).strict();
|
|
474
|
+
var TextNormalizationParametersSchema = z2.object({
|
|
475
|
+
operations: z2.array(z2.enum(["trim", "collapse_whitespace", "lowercase", "uppercase", "nfkc"])).min(1).max(8),
|
|
476
|
+
locale: z2.string().min(2).max(64).default("en-US")
|
|
477
|
+
}).strict();
|
|
478
|
+
var EnumAliasParametersSchema = z2.object({
|
|
479
|
+
aliases: policyMap(256, 16, z2.union([z2.string().max(256), z2.number()])),
|
|
480
|
+
caseSensitive: z2.boolean().default(true)
|
|
481
|
+
}).strict();
|
|
482
|
+
var TransformIntentSchema = z2.discriminatedUnion("operation", [
|
|
483
|
+
z2.object({ operation: z2.literal("date_time_format"), parameters: DateTimeParametersSchema }).strict(),
|
|
484
|
+
z2.object({ operation: z2.literal("numeric_format"), parameters: NumericParametersSchema }).strict(),
|
|
485
|
+
z2.object({ operation: z2.literal("quantity_magnitude"), parameters: QuantityParametersSchema }).strict(),
|
|
486
|
+
z2.object({ operation: z2.literal("unit_conversion"), parameters: UnitConversionParametersSchema }).strict(),
|
|
487
|
+
z2.object({ operation: z2.literal("currency_code"), parameters: CurrencyParametersSchema }).strict(),
|
|
488
|
+
z2.object({ operation: z2.literal("boolean_alias"), parameters: BooleanParametersSchema }).strict(),
|
|
489
|
+
z2.object({
|
|
490
|
+
operation: z2.literal("text_normalization"),
|
|
491
|
+
parameters: TextNormalizationParametersSchema
|
|
492
|
+
}).strict(),
|
|
493
|
+
z2.object({ operation: z2.literal("enum_alias"), parameters: EnumAliasParametersSchema }).strict()
|
|
494
|
+
]);
|
|
495
|
+
var TRANSFORM_OPERATIONS = [
|
|
496
|
+
"date_time_format",
|
|
497
|
+
"numeric_format",
|
|
498
|
+
"quantity_magnitude",
|
|
499
|
+
"unit_conversion",
|
|
500
|
+
"currency_code",
|
|
501
|
+
"boolean_alias",
|
|
502
|
+
"text_normalization",
|
|
503
|
+
"enum_alias"
|
|
504
|
+
];
|
|
505
|
+
var TransformClaimSchema = z2.object({
|
|
506
|
+
operation: z2.enum(["", ...TRANSFORM_OPERATIONS]),
|
|
507
|
+
parameters: z2.record(z2.string().max(64), z2.union([z2.string().max(128), z2.number(), z2.boolean()])).refine((value) => Object.keys(value).length <= 16, "at most 16 claim parameters").default({}),
|
|
508
|
+
reason: z2.string().max(2e3),
|
|
509
|
+
confidence: z2.enum(["", "low", "medium", "high"])
|
|
510
|
+
}).strict();
|
|
511
|
+
var TRANSFORM_PROOF_OPERAND_KEYS = [
|
|
512
|
+
"bounded",
|
|
513
|
+
"canonical",
|
|
514
|
+
"magnitude",
|
|
515
|
+
"mappedValue",
|
|
516
|
+
"matchedSourceFormat",
|
|
517
|
+
"output",
|
|
518
|
+
"outputCode",
|
|
519
|
+
"outputDecimal",
|
|
520
|
+
"outputHash",
|
|
521
|
+
"policyHash",
|
|
522
|
+
"resolvedCode",
|
|
523
|
+
"sourceDecimal",
|
|
524
|
+
"sourceHash",
|
|
525
|
+
"sourceMagnitude",
|
|
526
|
+
"sourceUnit",
|
|
527
|
+
"targetMagnitude",
|
|
528
|
+
"targetUnit"
|
|
529
|
+
];
|
|
530
|
+
function sanitizeProofOperands(value) {
|
|
531
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
532
|
+
const next = Object.fromEntries(
|
|
533
|
+
Object.entries(value).filter(
|
|
534
|
+
([key]) => TRANSFORM_PROOF_OPERAND_KEYS.includes(key)
|
|
535
|
+
)
|
|
536
|
+
);
|
|
537
|
+
return Object.keys(next).length ? next : void 0;
|
|
538
|
+
}
|
|
539
|
+
var TransformProofHashSchema = z2.object({
|
|
540
|
+
algorithm: z2.literal("sha256"),
|
|
541
|
+
digest: z2.string().regex(/^[a-f0-9]{64}$/)
|
|
542
|
+
}).strict();
|
|
543
|
+
var TransformProofOperandValueSchema = z2.union([
|
|
544
|
+
z2.string().max(128),
|
|
545
|
+
z2.number().finite(),
|
|
546
|
+
z2.boolean(),
|
|
547
|
+
TransformProofHashSchema
|
|
548
|
+
]);
|
|
549
|
+
function recordSize(maxKeys, schema) {
|
|
550
|
+
return z2.record(z2.string().min(1).max(64), schema).refine((value) => Object.keys(value).length <= maxKeys, `at most ${maxKeys} keys`);
|
|
551
|
+
}
|
|
552
|
+
function isTrustedTransformDocsHref(href) {
|
|
553
|
+
if (href === TRANSFORM_VERIFICATION_DOCS) return true;
|
|
554
|
+
const prefix = `${TRANSFORM_VERIFICATION_DOCS}#`;
|
|
555
|
+
if (!href.startsWith(prefix)) return false;
|
|
556
|
+
const anchor = href.slice(prefix.length);
|
|
557
|
+
return TRANSFORM_DOCS_ANCHORS.includes(anchor);
|
|
558
|
+
}
|
|
559
|
+
function trustedTransformDocsHref(methodId) {
|
|
560
|
+
if (!methodId) return TRANSFORM_VERIFICATION_DOCS;
|
|
561
|
+
const descriptor = TRANSFORM_VALIDATORS.find((entry) => entry.methodId === methodId);
|
|
562
|
+
return descriptor ? `${TRANSFORM_VERIFICATION_DOCS}#${descriptor.docsAnchor}` : TRANSFORM_VERIFICATION_DOCS;
|
|
563
|
+
}
|
|
564
|
+
var proofByteLength = (input) => new TextEncoder().encode(JSON.stringify(input)).byteLength;
|
|
565
|
+
function trustedIntentParametersFitProof(parameters) {
|
|
566
|
+
return proofByteLength(parameters) <= MAX_TRANSFORM_INTENT_PARAMETER_BYTES;
|
|
567
|
+
}
|
|
568
|
+
var TransformProofSchema = z2.object({
|
|
569
|
+
methodId: z2.string().min(1).max(128),
|
|
570
|
+
methodVersion: z2.number().int().positive(),
|
|
571
|
+
result: z2.enum(["verified", "contradicted", "not_applicable"]),
|
|
572
|
+
proofClass: TransformProofClassSchema,
|
|
573
|
+
scope: z2.string().min(1).max(256),
|
|
574
|
+
trustedParameters: recordSize(32, JsonValueSchema),
|
|
575
|
+
operands: recordSize(16, TransformProofOperandValueSchema).refine(
|
|
576
|
+
(value) => Object.keys(value).every(
|
|
577
|
+
(key) => TRANSFORM_PROOF_OPERAND_KEYS.includes(key)
|
|
578
|
+
),
|
|
579
|
+
"unsupported proof operand key"
|
|
580
|
+
).optional(),
|
|
581
|
+
checks: recordSize(32, z2.boolean()),
|
|
582
|
+
implementation: z2.object({
|
|
583
|
+
library: z2.string().min(1).max(128),
|
|
584
|
+
version: z2.string().min(1).max(64),
|
|
585
|
+
runtime: z2.string().min(1).max(128).optional(),
|
|
586
|
+
locale: z2.string().min(1).max(64).optional(),
|
|
587
|
+
zone: z2.string().min(1).max(128).optional()
|
|
588
|
+
}).strict(),
|
|
589
|
+
limitations: z2.string().min(1).max(512),
|
|
590
|
+
docsHref: z2.string().max(256).refine(isTrustedTransformDocsHref, "untrusted transform docs href")
|
|
591
|
+
}).strict().superRefine((value, ctx) => {
|
|
592
|
+
if (proofByteLength(value) > MAX_TRANSFORM_PROOF_BYTES) {
|
|
593
|
+
ctx.addIssue({
|
|
594
|
+
code: "custom",
|
|
595
|
+
message: `transform proof exceeds ${MAX_TRANSFORM_PROOF_BYTES} bytes`
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
function parseTransformProof(value) {
|
|
600
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
601
|
+
const record = value;
|
|
602
|
+
const methodId = typeof record.methodId === "string" ? record.methodId : void 0;
|
|
603
|
+
const candidate = {
|
|
604
|
+
methodId: record.methodId,
|
|
605
|
+
methodVersion: record.methodVersion,
|
|
606
|
+
result: record.result,
|
|
607
|
+
proofClass: record.proofClass,
|
|
608
|
+
scope: record.scope,
|
|
609
|
+
trustedParameters: record.trustedParameters,
|
|
610
|
+
...sanitizeProofOperands(record.operands) ? { operands: sanitizeProofOperands(record.operands) } : {},
|
|
611
|
+
checks: record.checks,
|
|
612
|
+
implementation: record.implementation,
|
|
613
|
+
limitations: record.limitations,
|
|
614
|
+
docsHref: trustedTransformDocsHref(methodId)
|
|
615
|
+
};
|
|
616
|
+
const parsed = TransformProofSchema.safeParse(candidate);
|
|
617
|
+
return parsed.success ? parsed.data : void 0;
|
|
618
|
+
}
|
|
619
|
+
var DECIMAL_VERSION = "10.6.0";
|
|
620
|
+
var LUXON_VERSION = "3.7.2";
|
|
621
|
+
var ISO_4217_CODES = new Set(
|
|
622
|
+
"AED AFN ALL AMD ANG AOA ARS AUD AWG AZN BAM BBD BDT BGN BHD BIF BMD BND BOB BOV BRL BSD BTN BWP BYN BZD CAD CDF CHE CHF CHW CLF CLP CNY COP COU CRC CUC CUP CVE CZK DJF DKK DOP DZD EGP ERN ETB EUR FJD FKP GBP GEL GHS GIP GMD GNF GTQ GYD HKD HNL HRK HTG HUF IDR ILS INR IQD IRR ISK JMD JOD JPY KES KGS KHR KMF KPW KRW KWD KYD KZT LAK LBP LKR LRD LSL LYD MAD MDL MGA MKD MMK MNT MOP MRU MUR MVR MWK MXN MXV MYR MZN NAD NGN NIO NOK NPR NZD OMR PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RUB RWF SAR SBD SCR SDG SEK SGD SHP SLE SLL SOS SRD SSP STN SVC SYP SZL THB TJS TMT TND TOP TRY TTD TWD TZS UAH UGX USD USN UYI UYU UYW UZS VED VES VND VUV WST XAF XAG XAU XBA XBB XBC XBD XCD XDR XOF XPD XPF XPT XSU XTS XUA XXX YER ZAR ZMW ZWG ZWL".split(
|
|
623
|
+
" "
|
|
624
|
+
)
|
|
625
|
+
);
|
|
626
|
+
function descriptorBase(input) {
|
|
627
|
+
return {
|
|
628
|
+
...input,
|
|
629
|
+
parameterJsonSchema: input.parameterJsonSchema ?? z2.toJSONSchema(input.parameterSchema)
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
function sha256Operand(value) {
|
|
633
|
+
return {
|
|
634
|
+
algorithm: "sha256",
|
|
635
|
+
digest: createHash("sha256").update(value, "utf8").digest("hex")
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
function boundedOperand(value) {
|
|
639
|
+
return value.length <= 128 ? value : sha256Operand(value);
|
|
640
|
+
}
|
|
641
|
+
function proof(descriptor, result, parameters, extras) {
|
|
642
|
+
const jsonParameters = JSON.parse(JSON.stringify(parameters));
|
|
643
|
+
const jsonOperands = extras.operands ? JSON.parse(JSON.stringify(extras.operands)) : void 0;
|
|
644
|
+
const value = {
|
|
645
|
+
methodId: descriptor.methodId,
|
|
646
|
+
methodVersion: descriptor.version,
|
|
647
|
+
result,
|
|
648
|
+
proofClass: extras.proofClass ?? descriptor.proofClass,
|
|
649
|
+
scope: descriptor.summary,
|
|
650
|
+
trustedParameters: jsonParameters,
|
|
651
|
+
...jsonOperands ? { operands: jsonOperands } : {},
|
|
652
|
+
checks: extras.checks,
|
|
653
|
+
implementation: {
|
|
654
|
+
library: descriptor.library.name,
|
|
655
|
+
version: descriptor.library.version,
|
|
656
|
+
...extras.runtime ? { runtime: extras.runtime.slice(0, 128) } : {},
|
|
657
|
+
...extras.locale ? { locale: extras.locale } : {},
|
|
658
|
+
...extras.zone ? { zone: extras.zone } : {}
|
|
659
|
+
},
|
|
660
|
+
limitations: descriptor.limitations,
|
|
661
|
+
docsHref: `${TRANSFORM_VERIFICATION_DOCS}#${descriptor.docsAnchor}`
|
|
662
|
+
};
|
|
663
|
+
const next = boundedProof(value);
|
|
664
|
+
return { result: next.result, proof: next };
|
|
665
|
+
}
|
|
666
|
+
function isIncompleteTrustedPolicy(parameters) {
|
|
667
|
+
return typeof parameters === "object" && parameters !== null && !Array.isArray(parameters) && Object.keys(parameters).length === 1 && parameters.bounded === true;
|
|
668
|
+
}
|
|
669
|
+
function downgradeProofTooLarge(value) {
|
|
670
|
+
const digest = sha256Operand(JSON.stringify(value.trustedParameters ?? {}));
|
|
671
|
+
const parsed = TransformProofSchema.safeParse({
|
|
672
|
+
methodId: value.methodId,
|
|
673
|
+
methodVersion: value.methodVersion,
|
|
674
|
+
result: "not_applicable",
|
|
675
|
+
proofClass: value.proofClass,
|
|
676
|
+
scope: value.scope,
|
|
677
|
+
trustedParameters: {
|
|
678
|
+
reason: "proof_too_large",
|
|
679
|
+
policyHash: digest
|
|
680
|
+
},
|
|
681
|
+
operands: { policyHash: digest },
|
|
682
|
+
checks: { proofFitsBound: false },
|
|
683
|
+
implementation: value.implementation,
|
|
684
|
+
limitations: value.limitations,
|
|
685
|
+
docsHref: value.docsHref
|
|
686
|
+
});
|
|
687
|
+
if (!parsed.success) {
|
|
688
|
+
throw new Error("transform proof descriptor exceeds bounded metadata limit");
|
|
689
|
+
}
|
|
690
|
+
return parsed.data;
|
|
691
|
+
}
|
|
692
|
+
function boundedProof(value) {
|
|
693
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
694
|
+
throw new Error("transform proof descriptor exceeds bounded metadata limit");
|
|
695
|
+
}
|
|
696
|
+
const record = value;
|
|
697
|
+
const parsed = TransformProofSchema.safeParse(value);
|
|
698
|
+
if (parsed.success) {
|
|
699
|
+
if (parsed.data.result === "verified" && isIncompleteTrustedPolicy(parsed.data.trustedParameters)) {
|
|
700
|
+
return downgradeProofTooLarge(record);
|
|
701
|
+
}
|
|
702
|
+
return parsed.data;
|
|
703
|
+
}
|
|
704
|
+
return downgradeProofTooLarge(record);
|
|
705
|
+
}
|
|
706
|
+
function escaped(value) {
|
|
707
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
708
|
+
}
|
|
709
|
+
function outputDecimal(value) {
|
|
710
|
+
if (typeof value === "number" && Number.isFinite(value)) return new Decimal(value.toString());
|
|
711
|
+
if (typeof value === "string" && /^[+-]?\d+(?:\.\d+)?$/.test(value)) return new Decimal(value);
|
|
712
|
+
return void 0;
|
|
713
|
+
}
|
|
714
|
+
function parseTrustedDecimal(quote, parameters) {
|
|
715
|
+
let text = quote.normalize("NFKC").trim();
|
|
716
|
+
let negative = false;
|
|
717
|
+
if (parameters.accountingParentheses && text.startsWith("(") && text.endsWith(")")) {
|
|
718
|
+
negative = true;
|
|
719
|
+
text = text.slice(1, -1).trim();
|
|
720
|
+
}
|
|
721
|
+
const stripEdge = (values, edge) => {
|
|
722
|
+
const match = [...values].sort((a, b) => b.length - a.length).find((value) => edge === "prefix" ? text.startsWith(value) : text.endsWith(value));
|
|
723
|
+
if (!match) return;
|
|
724
|
+
text = edge === "prefix" ? text.slice(match.length).trim() : text.slice(0, text.length - match.length).trim();
|
|
725
|
+
};
|
|
726
|
+
stripEdge(parameters.prefixes, "prefix");
|
|
727
|
+
stripEdge(parameters.suffixes, "suffix");
|
|
728
|
+
const decimal = escaped(parameters.decimalSeparator);
|
|
729
|
+
const grouping = parameters.groupingSeparator ? escaped(parameters.groupingSeparator) : void 0;
|
|
730
|
+
const integer = grouping ? `(?:\\d{1,3}(?:${grouping}\\d{3})+|\\d+)` : "\\d+";
|
|
731
|
+
if (!new RegExp(`^[+-]?${integer}(?:${decimal}\\d+)?$`).test(text)) return void 0;
|
|
732
|
+
const canonical = text.replaceAll(parameters.groupingSeparator ?? "\0", "").replace(parameters.decimalSeparator, ".");
|
|
733
|
+
try {
|
|
734
|
+
const parsed = new Decimal(canonical);
|
|
735
|
+
return negative ? parsed.negated() : parsed;
|
|
736
|
+
} catch {
|
|
737
|
+
return void 0;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function dateTimeRuntime() {
|
|
741
|
+
if (typeof process === "undefined") return void 0;
|
|
742
|
+
return `node ${process.versions.node}; ICU ${process.versions.icu ?? "unknown"}`;
|
|
743
|
+
}
|
|
744
|
+
function canonicalDateTime(kind, value, components) {
|
|
745
|
+
if (kind === "date") return value.toFormat("yyyy-MM-dd");
|
|
746
|
+
const fraction = components.millisecond ? ".SSS" : "";
|
|
747
|
+
if (kind === "time") {
|
|
748
|
+
const offset = components.offset ? "ZZ" : "";
|
|
749
|
+
const zone = components.zone ? " zzz" : "";
|
|
750
|
+
return value.toFormat(`HH:mm:ss${fraction}${offset}${zone}`).trim();
|
|
751
|
+
}
|
|
752
|
+
if (components.offset || components.zone) return value.toUTC().toISO() ?? "";
|
|
753
|
+
return value.toFormat(`yyyy-MM-dd'T'HH:mm:ss${fraction}`);
|
|
754
|
+
}
|
|
755
|
+
var dateTimeBase = descriptorBase({
|
|
756
|
+
operation: "date_time_format",
|
|
757
|
+
methodId: "openparser.transform.date-time-format.v1",
|
|
758
|
+
version: 1,
|
|
759
|
+
label: "Date/time formatting",
|
|
760
|
+
summary: "Exact reformatting of one civil date/time value or instant",
|
|
761
|
+
docsAnchor: "date-time-format",
|
|
762
|
+
acceptedSourceTypes: ["string"],
|
|
763
|
+
acceptedOutputTypes: ["string"],
|
|
764
|
+
proofClass: "safe_equivalence",
|
|
765
|
+
parameterSchema: DateTimeParametersSchema,
|
|
766
|
+
library: { name: "luxon", version: LUXON_VERSION },
|
|
767
|
+
limitations: "Only explicit Luxon formats with four-digit years; source and target must preserve the same required components. Two-digit years, defaulted fields, and silent millisecond/offset/zone changes are not proven. Format-only JSON Schema date intent uses the versioned built-in source catalog; a claim source_format cannot add or narrow that catalog."
|
|
768
|
+
});
|
|
769
|
+
function resolveTrustedDateTimeParameters(parameters) {
|
|
770
|
+
if (parameters.sourceFormats.length === 0 || parameters.sourceCatalog) {
|
|
771
|
+
return {
|
|
772
|
+
...parameters,
|
|
773
|
+
sourceFormats: trustedDateSourceFormats(parameters.kind, parameters.targetFormat),
|
|
774
|
+
sourceCatalog: {
|
|
775
|
+
id: DATE_SOURCE_CATALOG_ID,
|
|
776
|
+
version: DATE_SOURCE_CATALOG_VERSION
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
return parameters;
|
|
781
|
+
}
|
|
782
|
+
function prioritizeSourceFormats(formats, preferred) {
|
|
783
|
+
if (!preferred || !formats.includes(preferred)) return formats;
|
|
784
|
+
return [preferred, ...formats.filter((format) => format !== preferred)];
|
|
785
|
+
}
|
|
786
|
+
var dateTimeValidator = {
|
|
787
|
+
...dateTimeBase,
|
|
788
|
+
verify({ quote, output, parameters, preferredSourceFormat }) {
|
|
789
|
+
const parsed = DateTimeParametersSchema.safeParse(parameters);
|
|
790
|
+
if (!parsed.success || typeof output !== "string") {
|
|
791
|
+
return proof(dateTimeBase, "not_applicable", {}, { checks: { parametersValid: false } });
|
|
792
|
+
}
|
|
793
|
+
const p = resolveTrustedDateTimeParameters(parsed.data);
|
|
794
|
+
if (p.sourceFormats.length === 0) {
|
|
795
|
+
return proof(dateTimeBase, "not_applicable", p, {
|
|
796
|
+
checks: {
|
|
797
|
+
sourceTokensValid: false,
|
|
798
|
+
targetTokensValid: formatHasRequiredKindTokens(p.kind, inspectFormat(p.targetFormat)),
|
|
799
|
+
fullSourceConsumed: false,
|
|
800
|
+
sourceRoundTrip: false
|
|
801
|
+
},
|
|
802
|
+
locale: p.locale
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
const zone = p.zonePolicy === "utc" ? "UTC" : p.zonePolicy === "named" ? p.zone : void 0;
|
|
806
|
+
const targetComponents = inspectFormat(p.targetFormat);
|
|
807
|
+
const preferred = preferredSourceFormat && p.sourceFormats.includes(preferredSourceFormat) ? preferredSourceFormat : void 0;
|
|
808
|
+
const candidates = prioritizeSourceFormats(p.sourceFormats, preferred).flatMap((format) => {
|
|
809
|
+
const sourceComponents = inspectFormat(format);
|
|
810
|
+
if (!formatHasRequiredKindTokens(p.kind, sourceComponents) || !formatHasRequiredKindTokens(p.kind, targetComponents) || !optionalTimeComponentsAlign(sourceComponents, targetComponents)) {
|
|
811
|
+
return [];
|
|
812
|
+
}
|
|
813
|
+
const value = DateTime.fromFormat(quote, format, {
|
|
814
|
+
locale: p.locale,
|
|
815
|
+
zone,
|
|
816
|
+
setZone: p.zonePolicy !== "civil"
|
|
817
|
+
});
|
|
818
|
+
if (!value.isValid || value.toFormat(format) !== quote) return [];
|
|
819
|
+
if (p.zonePolicy === "named" && value.getPossibleOffsets().length !== 1) return [];
|
|
820
|
+
return [{ value, format, components: sourceComponents }];
|
|
821
|
+
});
|
|
822
|
+
if (!candidates.length) {
|
|
823
|
+
return proof(dateTimeBase, "not_applicable", p, {
|
|
824
|
+
checks: {
|
|
825
|
+
sourceTokensValid: true,
|
|
826
|
+
targetTokensValid: formatHasRequiredKindTokens(p.kind, targetComponents),
|
|
827
|
+
fullSourceConsumed: false,
|
|
828
|
+
sourceRoundTrip: false,
|
|
829
|
+
unambiguous: false
|
|
830
|
+
},
|
|
831
|
+
locale: p.locale,
|
|
832
|
+
zone
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
const canonical = candidates.map(
|
|
836
|
+
({ value, components }) => canonicalDateTime(p.kind, value, components)
|
|
837
|
+
);
|
|
838
|
+
const converged = new Set(canonical).size === 1;
|
|
839
|
+
const rendered = candidates[0].value.toFormat(p.targetFormat);
|
|
840
|
+
const target = DateTime.fromFormat(rendered, p.targetFormat, {
|
|
841
|
+
locale: p.locale,
|
|
842
|
+
zone,
|
|
843
|
+
setZone: p.zonePolicy !== "civil"
|
|
844
|
+
});
|
|
845
|
+
const targetRoundTrip = target.isValid && target.toFormat(p.targetFormat) === rendered;
|
|
846
|
+
const exactOutput = output === rendered;
|
|
847
|
+
const result = converged && targetRoundTrip && exactOutput ? "verified" : "contradicted";
|
|
848
|
+
const matchedSourceFormat = preferred && candidates.some((candidate) => candidate.format === preferred) ? preferred : candidates[0].format;
|
|
849
|
+
return proof(dateTimeBase, result, p, {
|
|
850
|
+
operands: {
|
|
851
|
+
sourceHash: sha256Operand(quote),
|
|
852
|
+
matchedSourceFormat: boundedOperand(matchedSourceFormat),
|
|
853
|
+
canonical: boundedOperand(canonical[0]),
|
|
854
|
+
output: boundedOperand(output)
|
|
855
|
+
},
|
|
856
|
+
checks: {
|
|
857
|
+
sourceTokensValid: true,
|
|
858
|
+
targetTokensValid: true,
|
|
859
|
+
componentsPreserved: true,
|
|
860
|
+
fullSourceConsumed: true,
|
|
861
|
+
sourceRoundTrip: true,
|
|
862
|
+
formatsConverged: converged,
|
|
863
|
+
targetRoundTrip,
|
|
864
|
+
exactOutput
|
|
865
|
+
},
|
|
866
|
+
runtime: dateTimeRuntime(),
|
|
867
|
+
locale: p.locale,
|
|
868
|
+
zone: zone ?? p.zonePolicy
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
var numericBase = descriptorBase({
|
|
873
|
+
operation: "numeric_format",
|
|
874
|
+
methodId: "openparser.transform.numeric-format.v1",
|
|
875
|
+
version: 1,
|
|
876
|
+
label: "Numeric formatting",
|
|
877
|
+
summary: "Exact decimal equivalence after a declared representation change",
|
|
878
|
+
docsAnchor: "numeric-format",
|
|
879
|
+
acceptedSourceTypes: ["string"],
|
|
880
|
+
acceptedOutputTypes: ["number", "integer", "string"],
|
|
881
|
+
proofClass: "safe_equivalence",
|
|
882
|
+
parameterSchema: NumericParametersSchema,
|
|
883
|
+
library: { name: "decimal.js", version: DECIMAL_VERSION },
|
|
884
|
+
limitations: "Only declared separators and edge affixes are accepted."
|
|
885
|
+
});
|
|
886
|
+
var numericValidator = {
|
|
887
|
+
...numericBase,
|
|
888
|
+
verify({ quote, output, parameters }) {
|
|
889
|
+
const parsed = NumericParametersSchema.safeParse(parameters);
|
|
890
|
+
const right = outputDecimal(output);
|
|
891
|
+
if (!parsed.success || !right) {
|
|
892
|
+
return proof(numericBase, "not_applicable", {}, { checks: { parametersValid: false } });
|
|
893
|
+
}
|
|
894
|
+
const left = parseTrustedDecimal(quote, parsed.data);
|
|
895
|
+
if (!left) {
|
|
896
|
+
return proof(numericBase, "not_applicable", parsed.data, {
|
|
897
|
+
checks: { wholeQuoteMatched: false }
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
const exact = left.eq(right);
|
|
901
|
+
return proof(numericBase, exact ? "verified" : "contradicted", parsed.data, {
|
|
902
|
+
operands: { sourceDecimal: left.toString(), outputDecimal: right.toString() },
|
|
903
|
+
checks: { wholeQuoteMatched: true, exactDecimalEquality: exact }
|
|
904
|
+
});
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
var quantityBase = descriptorBase({
|
|
908
|
+
operation: "quantity_magnitude",
|
|
909
|
+
methodId: "openparser.transform.quantity-magnitude.v1",
|
|
910
|
+
version: 1,
|
|
911
|
+
label: "Quantity magnitude",
|
|
912
|
+
summary: "Removal of an explicitly accepted unit while preserving magnitude",
|
|
913
|
+
docsAnchor: "quantity-magnitude",
|
|
914
|
+
acceptedSourceTypes: ["string"],
|
|
915
|
+
acceptedOutputTypes: ["number", "integer", "string"],
|
|
916
|
+
proofClass: "safe_equivalence",
|
|
917
|
+
parameterSchema: QuantityParametersSchema,
|
|
918
|
+
library: { name: "decimal.js", version: DECIMAL_VERSION },
|
|
919
|
+
limitations: "The complete quote must be one quantity and the unit must be allowlisted."
|
|
920
|
+
});
|
|
921
|
+
var quantityValidator = {
|
|
922
|
+
...quantityBase,
|
|
923
|
+
verify({ quote, output, parameters }) {
|
|
924
|
+
const parsed = QuantityParametersSchema.safeParse(parameters);
|
|
925
|
+
const right = outputDecimal(output);
|
|
926
|
+
if (!parsed.success || !right) {
|
|
927
|
+
return proof(quantityBase, "not_applicable", {}, { checks: { parametersValid: false } });
|
|
928
|
+
}
|
|
929
|
+
const aliases = Object.keys(parsed.data.allowedUnits).sort((a, b) => b.length - a.length);
|
|
930
|
+
const match = quote.normalize("NFKC").trim().match(new RegExp(`^([+-]?\\d+(?:\\.\\d+)?)\\s*(${aliases.map(escaped).join("|")})$`, "i"));
|
|
931
|
+
if (!match) {
|
|
932
|
+
return proof(quantityBase, "not_applicable", parsed.data, {
|
|
933
|
+
checks: { wholeQuoteMatched: false }
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
const canonicalUnit = Object.entries(parsed.data.allowedUnits).find(
|
|
937
|
+
([alias]) => alias.toLocaleLowerCase("en-US") === match[2].toLocaleLowerCase("en-US")
|
|
938
|
+
)?.[1];
|
|
939
|
+
const exact = canonicalUnit === parsed.data.targetUnit && new Decimal(match[1]).eq(right);
|
|
940
|
+
return proof(quantityBase, exact ? "verified" : "contradicted", parsed.data, {
|
|
941
|
+
operands: {
|
|
942
|
+
magnitude: match[1],
|
|
943
|
+
sourceUnit: canonicalUnit,
|
|
944
|
+
targetUnit: parsed.data.targetUnit
|
|
945
|
+
},
|
|
946
|
+
checks: {
|
|
947
|
+
wholeQuoteMatched: true,
|
|
948
|
+
acceptedUnit: Boolean(canonicalUnit),
|
|
949
|
+
exactMagnitude: exact
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
var UNIT_RATIOS = {
|
|
955
|
+
mm: { dimension: "length", baseRatio: "0.001" },
|
|
956
|
+
cm: { dimension: "length", baseRatio: "0.01" },
|
|
957
|
+
m: { dimension: "length", baseRatio: "1" },
|
|
958
|
+
km: { dimension: "length", baseRatio: "1000" },
|
|
959
|
+
in: { dimension: "length", baseRatio: "0.0254" },
|
|
960
|
+
ft: { dimension: "length", baseRatio: "0.3048" },
|
|
961
|
+
s: { dimension: "duration", baseRatio: "1" },
|
|
962
|
+
min: { dimension: "duration", baseRatio: "60" },
|
|
963
|
+
h: { dimension: "duration", baseRatio: "3600" },
|
|
964
|
+
day: { dimension: "duration", baseRatio: "86400" },
|
|
965
|
+
month: { dimension: "calendar_duration", baseRatio: "1" },
|
|
966
|
+
year: { dimension: "calendar_duration", baseRatio: "12" },
|
|
967
|
+
g: { dimension: "mass", baseRatio: "1" },
|
|
968
|
+
kg: { dimension: "mass", baseRatio: "1000" },
|
|
969
|
+
lb: { dimension: "mass", baseRatio: "453.59237" }
|
|
970
|
+
};
|
|
971
|
+
var unitBase = descriptorBase({
|
|
972
|
+
operation: "unit_conversion",
|
|
973
|
+
methodId: "openparser.transform.unit-conversion.v1",
|
|
974
|
+
version: 1,
|
|
975
|
+
label: "Unit conversion",
|
|
976
|
+
summary: "Exact conversion between allowlisted units in one dimension",
|
|
977
|
+
docsAnchor: "unit-conversion",
|
|
978
|
+
acceptedSourceTypes: ["string"],
|
|
979
|
+
acceptedOutputTypes: ["number", "integer", "string"],
|
|
980
|
+
proofClass: "exact_conversion",
|
|
981
|
+
parameterSchema: UnitConversionParametersSchema,
|
|
982
|
+
library: { name: "decimal.js + OpenParser unit table", version: `${DECIMAL_VERSION}/1` },
|
|
983
|
+
limitations: "Only the versioned unit table is supported; no contextual or ambiguous units."
|
|
984
|
+
});
|
|
985
|
+
var unitValidator = {
|
|
986
|
+
...unitBase,
|
|
987
|
+
verify({ quote, output, parameters }) {
|
|
988
|
+
const parsed = UnitConversionParametersSchema.safeParse(parameters);
|
|
989
|
+
const right = outputDecimal(output);
|
|
990
|
+
if (!parsed.success || !right) {
|
|
991
|
+
return proof(unitBase, "not_applicable", {}, { checks: { parametersValid: false } });
|
|
992
|
+
}
|
|
993
|
+
const match = quote.normalize("NFKC").trim().match(/^([+-]?\d+(?:\.\d+)?)\s*([A-Za-z]+)$/);
|
|
994
|
+
const sourceUnit = match?.[2];
|
|
995
|
+
const source = sourceUnit ? UNIT_RATIOS[sourceUnit] : void 0;
|
|
996
|
+
const target = UNIT_RATIOS[parsed.data.targetUnit];
|
|
997
|
+
if (!match || !sourceUnit || !parsed.data.sourceUnits.includes(sourceUnit) || !source || !target) {
|
|
998
|
+
return proof(unitBase, "not_applicable", parsed.data, {
|
|
999
|
+
checks: { wholeQuoteMatched: Boolean(match), supportedUnits: false }
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
if (source.dimension !== target.dimension) {
|
|
1003
|
+
return proof(unitBase, "contradicted", parsed.data, {
|
|
1004
|
+
operands: { sourceUnit, targetUnit: parsed.data.targetUnit },
|
|
1005
|
+
checks: { sameDimension: false }
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
const converted = new Decimal(match[1]).mul(source.baseRatio).div(target.baseRatio);
|
|
1009
|
+
const exact = converted.eq(right);
|
|
1010
|
+
return proof(unitBase, exact ? "verified" : "contradicted", parsed.data, {
|
|
1011
|
+
operands: {
|
|
1012
|
+
sourceMagnitude: match[1],
|
|
1013
|
+
sourceUnit,
|
|
1014
|
+
targetMagnitude: converted.toString(),
|
|
1015
|
+
targetUnit: parsed.data.targetUnit
|
|
1016
|
+
},
|
|
1017
|
+
checks: { wholeQuoteMatched: true, sameDimension: true, exactDecimalEquality: exact }
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
function findBoundedTokens(text, token) {
|
|
1022
|
+
const indices = [];
|
|
1023
|
+
const pattern = new RegExp(`(?<![A-Za-z0-9])${escaped(token)}(?![A-Za-z0-9])`, "gi");
|
|
1024
|
+
for (const match of text.matchAll(pattern)) {
|
|
1025
|
+
if (match.index !== void 0) indices.push(match.index);
|
|
1026
|
+
}
|
|
1027
|
+
return indices;
|
|
1028
|
+
}
|
|
1029
|
+
function collectCurrencySignals(text, parameters) {
|
|
1030
|
+
const signals = [];
|
|
1031
|
+
const isoCodes = /* @__PURE__ */ new Set([...ISO_4217_CODES, ...parameters.acceptedCodes]);
|
|
1032
|
+
for (const code of isoCodes) {
|
|
1033
|
+
for (const index of findBoundedTokens(text, code)) {
|
|
1034
|
+
signals.push({ code: code.toUpperCase(), index, kind: "iso" });
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
for (const [alias, code] of Object.entries(parameters.aliases)) {
|
|
1038
|
+
for (const index of findBoundedTokens(text, alias)) {
|
|
1039
|
+
signals.push({ code, index, kind: "alias" });
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
const covered = new Array(text.length).fill(false);
|
|
1043
|
+
const symbols = Object.entries(parameters.symbolMap).toSorted(
|
|
1044
|
+
(left, right) => right[0].length - left[0].length
|
|
1045
|
+
);
|
|
1046
|
+
for (const [symbol, code] of symbols) {
|
|
1047
|
+
let from = 0;
|
|
1048
|
+
while (from <= text.length - symbol.length) {
|
|
1049
|
+
const index = text.indexOf(symbol, from);
|
|
1050
|
+
if (index < 0) break;
|
|
1051
|
+
from = index + 1;
|
|
1052
|
+
const before = index === 0 ? "" : text[index - 1];
|
|
1053
|
+
if (/\p{L}/u.test(before)) continue;
|
|
1054
|
+
if (covered.slice(index, index + symbol.length).some(Boolean)) continue;
|
|
1055
|
+
for (let i = index; i < index + symbol.length; i += 1) covered[i] = true;
|
|
1056
|
+
signals.push({ code, index, kind: "symbol" });
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
return signals;
|
|
1060
|
+
}
|
|
1061
|
+
var currencyBase = descriptorBase({
|
|
1062
|
+
operation: "currency_code",
|
|
1063
|
+
methodId: "openparser.transform.currency-code.v1",
|
|
1064
|
+
version: 1,
|
|
1065
|
+
label: "Currency code derivation",
|
|
1066
|
+
summary: "Derivation of an ISO code from an explicit token or declared symbol policy",
|
|
1067
|
+
docsAnchor: "currency-code",
|
|
1068
|
+
acceptedSourceTypes: ["string"],
|
|
1069
|
+
acceptedOutputTypes: ["string"],
|
|
1070
|
+
proofClass: "safe_equivalence",
|
|
1071
|
+
parameterSchema: CurrencyParametersSchema,
|
|
1072
|
+
library: { name: "OpenParser ISO currency token table", version: "1" },
|
|
1073
|
+
limitations: "A bare $ proves nothing unless trusted intent maps that standalone token. $ inside C$, A$, NZ$, HK$, or S$ is not a USD signal. Conflicting ISO codes or currency signals are not applied."
|
|
1074
|
+
});
|
|
1075
|
+
var currencyValidator = {
|
|
1076
|
+
...currencyBase,
|
|
1077
|
+
verify({ quote, output, parameters }) {
|
|
1078
|
+
const parsed = CurrencyParametersSchema.safeParse(parameters);
|
|
1079
|
+
if (!parsed.success || typeof output !== "string") {
|
|
1080
|
+
return proof(currencyBase, "not_applicable", {}, { checks: { parametersValid: false } });
|
|
1081
|
+
}
|
|
1082
|
+
const text = quote.normalize("NFKC").trim();
|
|
1083
|
+
const accepted = new Set(parsed.data.acceptedCodes);
|
|
1084
|
+
const signals = collectCurrencySignals(text, parsed.data);
|
|
1085
|
+
const codes = [...new Set(signals.map((signal) => signal.code))];
|
|
1086
|
+
if (codes.length > 1) {
|
|
1087
|
+
return proof(currencyBase, "not_applicable", parsed.data, {
|
|
1088
|
+
checks: { trustedTokenFound: true, unambiguous: false }
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
const code = codes[0];
|
|
1092
|
+
if (!code || !accepted.has(code)) {
|
|
1093
|
+
return proof(currencyBase, "not_applicable", parsed.data, {
|
|
1094
|
+
checks: { trustedTokenFound: false, unambiguous: true }
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
const policy = signals.every((signal) => signal.kind === "symbol");
|
|
1098
|
+
const exact = output === code;
|
|
1099
|
+
return proof(currencyBase, exact ? "verified" : "contradicted", parsed.data, {
|
|
1100
|
+
proofClass: policy ? "policy_application" : "safe_equivalence",
|
|
1101
|
+
operands: { resolvedCode: code, outputCode: output },
|
|
1102
|
+
checks: { trustedTokenFound: true, unambiguous: true, exactOutput: exact }
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
var booleanBase = descriptorBase({
|
|
1107
|
+
operation: "boolean_alias",
|
|
1108
|
+
methodId: "openparser.transform.boolean-alias.v1",
|
|
1109
|
+
version: 1,
|
|
1110
|
+
label: "Boolean alias",
|
|
1111
|
+
summary: "Application of a trusted exact alias map to a whole quote",
|
|
1112
|
+
docsAnchor: "boolean-alias",
|
|
1113
|
+
acceptedSourceTypes: ["string"],
|
|
1114
|
+
acceptedOutputTypes: ["boolean"],
|
|
1115
|
+
proofClass: "policy_application",
|
|
1116
|
+
parameterSchema: BooleanParametersSchema,
|
|
1117
|
+
library: { name: "OpenParser alias matcher", version: "1" },
|
|
1118
|
+
limitations: "No sentiment, negation, or natural-language interpretation."
|
|
1119
|
+
});
|
|
1120
|
+
var booleanValidator = {
|
|
1121
|
+
...booleanBase,
|
|
1122
|
+
verify({ quote, output, parameters }) {
|
|
1123
|
+
const parsed = BooleanParametersSchema.safeParse(parameters);
|
|
1124
|
+
if (!parsed.success || typeof output !== "boolean") {
|
|
1125
|
+
return proof(booleanBase, "not_applicable", {}, { checks: { parametersValid: false } });
|
|
1126
|
+
}
|
|
1127
|
+
const normalize = (value) => parsed.data.caseSensitive ? value : value.toLocaleLowerCase("en-US");
|
|
1128
|
+
const match = Object.entries(parsed.data.aliases).find(
|
|
1129
|
+
([alias]) => normalize(alias) === normalize(quote.normalize("NFKC").trim())
|
|
1130
|
+
);
|
|
1131
|
+
if (!match) {
|
|
1132
|
+
return proof(booleanBase, "not_applicable", parsed.data, {
|
|
1133
|
+
checks: { wholeQuoteMatched: false }
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
const exact = match[1] === output;
|
|
1137
|
+
return proof(booleanBase, exact ? "verified" : "contradicted", parsed.data, {
|
|
1138
|
+
operands: { mappedValue: match[1], output },
|
|
1139
|
+
checks: { wholeQuoteMatched: true, exactOutput: exact }
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
function applyTextOperations(value, parameters) {
|
|
1144
|
+
return parameters.operations.reduce((text, operation) => {
|
|
1145
|
+
switch (operation) {
|
|
1146
|
+
case "trim":
|
|
1147
|
+
return text.trim();
|
|
1148
|
+
case "collapse_whitespace":
|
|
1149
|
+
return text.replace(/\s+/g, " ");
|
|
1150
|
+
case "lowercase":
|
|
1151
|
+
return text.toLocaleLowerCase(parameters.locale);
|
|
1152
|
+
case "uppercase":
|
|
1153
|
+
return text.toLocaleUpperCase(parameters.locale);
|
|
1154
|
+
case "nfkc":
|
|
1155
|
+
return text.normalize("NFKC");
|
|
1156
|
+
}
|
|
1157
|
+
}, value);
|
|
1158
|
+
}
|
|
1159
|
+
var textBase = descriptorBase({
|
|
1160
|
+
operation: "text_normalization",
|
|
1161
|
+
methodId: "openparser.transform.text-normalization.v1",
|
|
1162
|
+
version: 1,
|
|
1163
|
+
label: "Controlled text normalization",
|
|
1164
|
+
summary: "Application of an ordered, enumerated text normalization plan",
|
|
1165
|
+
docsAnchor: "text-normalization",
|
|
1166
|
+
acceptedSourceTypes: ["string"],
|
|
1167
|
+
acceptedOutputTypes: ["string"],
|
|
1168
|
+
proofClass: "policy_application",
|
|
1169
|
+
parameterSchema: TextNormalizationParametersSchema,
|
|
1170
|
+
library: { name: "ECMAScript String", version: "ES2024" },
|
|
1171
|
+
limitations: "Only the recorded operations run; semantic paraphrasing is never verified."
|
|
1172
|
+
});
|
|
1173
|
+
var textValidator = {
|
|
1174
|
+
...textBase,
|
|
1175
|
+
verify({ quote, output, parameters }) {
|
|
1176
|
+
const parsed = TextNormalizationParametersSchema.safeParse(parameters);
|
|
1177
|
+
if (!parsed.success || typeof output !== "string") {
|
|
1178
|
+
return proof(textBase, "not_applicable", {}, { checks: { parametersValid: false } });
|
|
1179
|
+
}
|
|
1180
|
+
const normalized = applyTextOperations(quote, parsed.data);
|
|
1181
|
+
const exact = output === normalized;
|
|
1182
|
+
return proof(textBase, exact ? "verified" : "contradicted", parsed.data, {
|
|
1183
|
+
operands: { sourceHash: sha256Operand(quote), outputHash: sha256Operand(output) },
|
|
1184
|
+
checks: { operationsApplied: true, exactOutput: exact },
|
|
1185
|
+
locale: parsed.data.locale
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
var enumBase = descriptorBase({
|
|
1190
|
+
operation: "enum_alias",
|
|
1191
|
+
methodId: "openparser.transform.enum-alias.v1",
|
|
1192
|
+
version: 1,
|
|
1193
|
+
label: "Enum alias",
|
|
1194
|
+
summary: "Application of a trusted exact alias table",
|
|
1195
|
+
docsAnchor: "enum-alias",
|
|
1196
|
+
acceptedSourceTypes: ["string"],
|
|
1197
|
+
acceptedOutputTypes: ["string", "number", "integer"],
|
|
1198
|
+
proofClass: "policy_application",
|
|
1199
|
+
parameterSchema: EnumAliasParametersSchema,
|
|
1200
|
+
library: { name: "OpenParser alias matcher", version: "1" },
|
|
1201
|
+
limitations: "Only exact whole-quote aliases are accepted."
|
|
1202
|
+
});
|
|
1203
|
+
var enumValidator = {
|
|
1204
|
+
...enumBase,
|
|
1205
|
+
verify({ quote, output, parameters }) {
|
|
1206
|
+
const parsed = EnumAliasParametersSchema.safeParse(parameters);
|
|
1207
|
+
if (!parsed.success || typeof output !== "string" && typeof output !== "number") {
|
|
1208
|
+
return proof(enumBase, "not_applicable", {}, { checks: { parametersValid: false } });
|
|
1209
|
+
}
|
|
1210
|
+
const normalize = (value) => parsed.data.caseSensitive ? value : value.toLocaleLowerCase("en-US");
|
|
1211
|
+
const match = Object.entries(parsed.data.aliases).find(
|
|
1212
|
+
([alias]) => normalize(alias) === normalize(quote.normalize("NFKC").trim())
|
|
1213
|
+
);
|
|
1214
|
+
if (!match) {
|
|
1215
|
+
return proof(enumBase, "not_applicable", parsed.data, {
|
|
1216
|
+
checks: { wholeQuoteMatched: false }
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
const exact = match[1] === output;
|
|
1220
|
+
return proof(enumBase, exact ? "verified" : "contradicted", parsed.data, {
|
|
1221
|
+
operands: {
|
|
1222
|
+
mappedValue: typeof match[1] === "string" ? boundedOperand(match[1]) : match[1],
|
|
1223
|
+
output: typeof output === "string" ? boundedOperand(output) : output
|
|
1224
|
+
},
|
|
1225
|
+
checks: { wholeQuoteMatched: true, exactOutput: exact }
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
};
|
|
1229
|
+
var TRANSFORM_VALIDATORS = [
|
|
1230
|
+
dateTimeValidator,
|
|
1231
|
+
numericValidator,
|
|
1232
|
+
quantityValidator,
|
|
1233
|
+
unitValidator,
|
|
1234
|
+
currencyValidator,
|
|
1235
|
+
booleanValidator,
|
|
1236
|
+
textValidator,
|
|
1237
|
+
enumValidator
|
|
1238
|
+
];
|
|
1239
|
+
var VALIDATOR_BY_OPERATION = new Map(
|
|
1240
|
+
TRANSFORM_VALIDATORS.map((descriptor) => [descriptor.operation, descriptor])
|
|
1241
|
+
);
|
|
1242
|
+
function transformValidatorDescriptor(operation) {
|
|
1243
|
+
return VALIDATOR_BY_OPERATION.get(operation);
|
|
1244
|
+
}
|
|
1245
|
+
function claimedDateSourceFormat(claim) {
|
|
1246
|
+
if (!claim || claim.operation !== "date_time_format") return void 0;
|
|
1247
|
+
const value = claim.parameters.source_format;
|
|
1248
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1249
|
+
}
|
|
1250
|
+
function verifyTransform(input) {
|
|
1251
|
+
const intent = TransformIntentSchema.parse(input.intent);
|
|
1252
|
+
const descriptor = transformValidatorDescriptor(intent.operation);
|
|
1253
|
+
const hinted = claimedDateSourceFormat(input.claim);
|
|
1254
|
+
return descriptor.verify({
|
|
1255
|
+
quote: input.quote,
|
|
1256
|
+
output: input.output,
|
|
1257
|
+
parameters: intent.parameters,
|
|
1258
|
+
...intent.operation === "date_time_format" && hinted ? { preferredSourceFormat: hinted } : {}
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
export {
|
|
1262
|
+
CITED_RECOGNITION_METHOD,
|
|
1263
|
+
CONTEXT_BLEND_WEIGHT,
|
|
1264
|
+
DATE_SOURCE_CATALOG_ID,
|
|
1265
|
+
DATE_SOURCE_CATALOG_V1,
|
|
1266
|
+
DATE_SOURCE_CATALOG_VERSION,
|
|
1267
|
+
FORMAT_ONLY_DATE_TARGETS,
|
|
1268
|
+
MAX_TRANSFORM_INTENT_PARAMETER_BYTES,
|
|
1269
|
+
MAX_TRANSFORM_PROOF_BYTES,
|
|
1270
|
+
REGION_RECOGNITION_METHOD,
|
|
1271
|
+
TRANSFORM_DOCS_ANCHORS,
|
|
1272
|
+
TRANSFORM_OPERATIONS,
|
|
1273
|
+
TRANSFORM_PROOF_ATTRIBUTE,
|
|
1274
|
+
TRANSFORM_PROOF_OPERAND_KEYS,
|
|
1275
|
+
TRANSFORM_VALIDATORS,
|
|
1276
|
+
TRANSFORM_VERIFICATION_DOCS,
|
|
1277
|
+
TransformClaimSchema,
|
|
1278
|
+
TransformIntentSchema,
|
|
1279
|
+
TransformProofClassSchema,
|
|
1280
|
+
TransformProofSchema,
|
|
1281
|
+
activityLabel,
|
|
1282
|
+
aggregateCitedWordConfidences,
|
|
1283
|
+
annotateWordsWithCitation,
|
|
1284
|
+
citedSpanInText,
|
|
1285
|
+
dateTimeFormatIssue,
|
|
1286
|
+
downwardBlendCitedMinimum,
|
|
1287
|
+
droppedSourceIds,
|
|
1288
|
+
fieldGrounding,
|
|
1289
|
+
formatOnlyDateTimeParameters,
|
|
1290
|
+
groundingStatus,
|
|
1291
|
+
isTrustedTransformDocsHref,
|
|
1292
|
+
parseTransformProof,
|
|
1293
|
+
transformValidatorDescriptor,
|
|
1294
|
+
trustedDateSourceFormats,
|
|
1295
|
+
trustedIntentParametersFitProof,
|
|
1296
|
+
trustedTransformDocsHref,
|
|
1297
|
+
verifyTransform
|
|
1298
|
+
};
|