@tiangong-lca/cli 0.0.7 → 0.0.8

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 (52) hide show
  1. package/README.md +83 -2
  2. package/dist/src/cli.js +1327 -40
  3. package/dist/src/cli.js.map +1 -1
  4. package/dist/src/lib/dataset-author.js +100 -0
  5. package/dist/src/lib/dataset-author.js.map +1 -0
  6. package/dist/src/lib/dataset-bilingual.js +545 -0
  7. package/dist/src/lib/dataset-bilingual.js.map +1 -0
  8. package/dist/src/lib/dataset-contract.js +350 -0
  9. package/dist/src/lib/dataset-contract.js.map +1 -0
  10. package/dist/src/lib/dataset-evidence-search.js +636 -0
  11. package/dist/src/lib/dataset-evidence-search.js.map +1 -0
  12. package/dist/src/lib/dataset-import-lca.js +171 -0
  13. package/dist/src/lib/dataset-import-lca.js.map +1 -0
  14. package/dist/src/lib/dataset-remote-refresh.js +166 -0
  15. package/dist/src/lib/dataset-remote-refresh.js.map +1 -0
  16. package/dist/src/lib/dataset-remote-verify.js +543 -0
  17. package/dist/src/lib/dataset-remote-verify.js.map +1 -0
  18. package/dist/src/lib/dataset-validate.js +63 -7
  19. package/dist/src/lib/dataset-validate.js.map +1 -1
  20. package/dist/src/lib/flow-payload-validation.js +51 -0
  21. package/dist/src/lib/flow-payload-validation.js.map +1 -0
  22. package/dist/src/lib/flow-publish-reviewed-data.js +16 -0
  23. package/dist/src/lib/flow-publish-reviewed-data.js.map +1 -1
  24. package/dist/src/lib/flow-publish-version.js +182 -12
  25. package/dist/src/lib/flow-publish-version.js.map +1 -1
  26. package/dist/src/lib/identity-preflight.js +1021 -0
  27. package/dist/src/lib/identity-preflight.js.map +1 -0
  28. package/dist/src/lib/process-auto-build.js +147 -0
  29. package/dist/src/lib/process-auto-build.js.map +1 -1
  30. package/dist/src/lib/process-dedup-review.js +51 -0
  31. package/dist/src/lib/process-dedup-review.js.map +1 -1
  32. package/dist/src/lib/process-flow-build-plan.js +1071 -0
  33. package/dist/src/lib/process-flow-build-plan.js.map +1 -0
  34. package/dist/src/lib/process-payload-validation.js +14 -7
  35. package/dist/src/lib/process-payload-validation.js.map +1 -1
  36. package/dist/src/lib/process-publish-build.js +122 -4
  37. package/dist/src/lib/process-publish-build.js.map +1 -1
  38. package/dist/src/lib/process-refresh-references.js +19 -10
  39. package/dist/src/lib/process-refresh-references.js.map +1 -1
  40. package/dist/src/lib/process-required-fields.js +810 -0
  41. package/dist/src/lib/process-required-fields.js.map +1 -0
  42. package/dist/src/lib/process-save-draft-run.js +4 -1
  43. package/dist/src/lib/process-save-draft-run.js.map +1 -1
  44. package/dist/src/lib/publish.js +100 -0
  45. package/dist/src/lib/publish.js.map +1 -1
  46. package/dist/src/lib/review-flow.js +58 -0
  47. package/dist/src/lib/review-flow.js.map +1 -1
  48. package/dist/src/lib/review-process.js +150 -2
  49. package/dist/src/lib/review-process.js.map +1 -1
  50. package/dist/src/lib/runtime-rulesets.js +283 -0
  51. package/dist/src/lib/runtime-rulesets.js.map +1 -0
  52. package/package.json +2 -2
@@ -0,0 +1,810 @@
1
+ import path from 'node:path';
2
+ import { writeJsonArtifact, writeJsonLinesArtifact } from './artifacts.js';
3
+ import { CliError } from './errors.js';
4
+ import { cloneJson, datasetRoot, firstNonEmpty, isRecord, materializeDatasetRows, } from './dataset-local.js';
5
+ const ANNUAL_SUPPLY_FIELD = 'processDataSet.modellingAndValidation.dataSourcesTreatmentAndRepresentativeness.annualSupplyOrProductionVolume';
6
+ const ANNUAL_SUPPLY_ROOT_FIELD = 'modellingAndValidation.dataSourcesTreatmentAndRepresentativeness.annualSupplyOrProductionVolume';
7
+ const NUMERIC_TEXT_WITH_SUFFIX_PATTERN = /^[+-]?(\d+(\.\d*)?|\.\d+)([Ee][+-]?\d+)?\s+\S.*$/u;
8
+ const ANNUAL_PERIOD_PATTERN = /(?:\/\s*(?:year|yr|a)\b|\bper\s+(?:year|annum)\b|\/\s*年|每年|年度|年供应|年产)/iu;
9
+ const CJK_TEXT_PATTERN = /[\p{Script=Han}]/u;
10
+ const NET_CALORIFIC_VALUE_ID = '93a60a56-a3c8-11da-a746-0800200c9a66';
11
+ const DEFAULT_COMPLIANCE_SYSTEM = {
12
+ '@refObjectId': 'c84c4185-d1b0-44fc-823e-d2ec630c7906',
13
+ '@type': 'source data set',
14
+ '@uri': 'https://tiangong.earth/datasets/c84c4185-d1b0-44fc-823e-d2ec630c7906',
15
+ '@version': '00.00.001',
16
+ 'common:shortDescription': {
17
+ '@xml:lang': 'en',
18
+ '#text': 'Environmental Footprint (EF) 3.1',
19
+ },
20
+ };
21
+ const DEFAULT_COMPLIANCE_DECLARATION = {
22
+ 'common:referenceToComplianceSystem': DEFAULT_COMPLIANCE_SYSTEM,
23
+ 'common:approvalOfOverallCompliance': 'Not defined',
24
+ 'common:nomenclatureCompliance': 'Not defined',
25
+ 'common:methodologicalCompliance': 'Not defined',
26
+ 'common:reviewCompliance': 'Not defined',
27
+ 'common:documentationCompliance': 'Not defined',
28
+ 'common:qualityCompliance': 'Not defined',
29
+ };
30
+ const REQUIRED_COMPLIANCE_FIELDS = [
31
+ 'common:approvalOfOverallCompliance',
32
+ 'common:nomenclatureCompliance',
33
+ 'common:methodologicalCompliance',
34
+ 'common:reviewCompliance',
35
+ 'common:documentationCompliance',
36
+ 'common:qualityCompliance',
37
+ ];
38
+ function normalizeUnit(value) {
39
+ const token = value?.trim();
40
+ return token || 'unit';
41
+ }
42
+ function sourceLanguageForText(value, fallback = 'en') {
43
+ return CJK_TEXT_PATTERN.test(value) ? 'zh' : fallback;
44
+ }
45
+ function textValue(value) {
46
+ if (typeof value === 'string') {
47
+ const trimmed = value.trim();
48
+ return trimmed || null;
49
+ }
50
+ if (typeof value === 'number' && Number.isFinite(value)) {
51
+ return String(value);
52
+ }
53
+ return null;
54
+ }
55
+ function asList(value) {
56
+ if (Array.isArray(value)) {
57
+ return value;
58
+ }
59
+ return value === undefined || value === null ? [] : [value];
60
+ }
61
+ function valueAtPath(root, pathExpression) {
62
+ let current = root;
63
+ for (const segment of pathExpression.split('.')) {
64
+ if (!isRecord(current)) {
65
+ return undefined;
66
+ }
67
+ current = current[segment];
68
+ }
69
+ return current;
70
+ }
71
+ function annualSupplyItems(value) {
72
+ return asList(value).filter((item) => {
73
+ return (isRecord(item) && typeof item['#text'] === 'string' && typeof item['@xml:lang'] === 'string');
74
+ });
75
+ }
76
+ function isValidAnnualSupplyVolume(value) {
77
+ const items = annualSupplyItems(value);
78
+ return (items.length > 0 &&
79
+ items.every((item) => {
80
+ const text = item['#text'].trim();
81
+ return NUMERIC_TEXT_WITH_SUFFIX_PATTERN.test(text) && ANNUAL_PERIOD_PATTERN.test(text);
82
+ }));
83
+ }
84
+ function isValidReview(value) {
85
+ return asList(value).some((item) => isRecord(item) && Boolean(firstNonEmpty(item['@type'])));
86
+ }
87
+ function isValidComplianceDeclaration(value) {
88
+ return asList(value).some((item) => {
89
+ if (!isRecord(item)) {
90
+ return false;
91
+ }
92
+ const complianceSystem = isRecord(item['common:referenceToComplianceSystem'])
93
+ ? item['common:referenceToComplianceSystem']
94
+ : {};
95
+ return (Boolean(firstNonEmpty(complianceSystem['@refObjectId'])) &&
96
+ REQUIRED_COMPLIANCE_FIELDS.every((field) => Boolean(firstNonEmpty(item[field]))));
97
+ });
98
+ }
99
+ export function collectProcessRequiredFieldIssues(payload) {
100
+ const root = datasetRoot(payload, 'process');
101
+ const modelling = isRecord(root.modellingAndValidation) ? root.modellingAndValidation : {};
102
+ const dataSources = isRecord(modelling.dataSourcesTreatmentAndRepresentativeness)
103
+ ? modelling.dataSourcesTreatmentAndRepresentativeness
104
+ : null;
105
+ if (!dataSources) {
106
+ return [
107
+ {
108
+ code: 'process_data_sources_treatment_missing',
109
+ message: 'Process payload must include dataSourcesTreatmentAndRepresentativeness.',
110
+ path: 'processDataSet.modellingAndValidation.dataSourcesTreatmentAndRepresentativeness',
111
+ },
112
+ ];
113
+ }
114
+ const issues = [];
115
+ const validation = isRecord(modelling.validation) ? modelling.validation : {};
116
+ if (!isValidReview(validation.review)) {
117
+ issues.push({
118
+ code: 'process_validation_review_missing',
119
+ message: 'Process payload must include modellingAndValidation.validation.review.',
120
+ path: 'processDataSet.modellingAndValidation.validation.review',
121
+ });
122
+ }
123
+ const complianceDeclarations = isRecord(modelling.complianceDeclarations)
124
+ ? modelling.complianceDeclarations
125
+ : {};
126
+ if (!isValidComplianceDeclaration(complianceDeclarations.compliance)) {
127
+ issues.push({
128
+ code: 'process_compliance_declaration_missing',
129
+ message: 'Process payload must include modellingAndValidation.complianceDeclarations.compliance.',
130
+ path: 'processDataSet.modellingAndValidation.complianceDeclarations.compliance',
131
+ });
132
+ }
133
+ const annualSupply = dataSources.annualSupplyOrProductionVolume;
134
+ if (isValidAnnualSupplyVolume(annualSupply)) {
135
+ return issues;
136
+ }
137
+ const items = annualSupplyItems(annualSupply);
138
+ if (items.length === 0) {
139
+ return [
140
+ ...issues,
141
+ {
142
+ code: 'annual_supply_or_production_volume_missing',
143
+ message: 'Process payload must include annualSupplyOrProductionVolume as numeric text with a unit or context suffix.',
144
+ path: ANNUAL_SUPPLY_FIELD,
145
+ },
146
+ ];
147
+ }
148
+ if (items.every((item) => NUMERIC_TEXT_WITH_SUFFIX_PATTERN.test(item['#text'].trim())) &&
149
+ items.some((item) => !ANNUAL_PERIOD_PATTERN.test(item['#text'].trim()))) {
150
+ return [
151
+ ...issues,
152
+ {
153
+ code: 'annual_supply_or_production_volume_not_annualized',
154
+ message: 'annualSupplyOrProductionVolume must express an annualized amount such as "3.6 MJ/year" or "3.6 MJ/年", not a reference-flow description.',
155
+ path: ANNUAL_SUPPLY_FIELD,
156
+ },
157
+ ];
158
+ }
159
+ return [
160
+ ...issues,
161
+ {
162
+ code: 'annual_supply_or_production_volume_invalid',
163
+ message: 'annualSupplyOrProductionVolume must start with a real number followed by a unit or context suffix.',
164
+ path: ANNUAL_SUPPLY_FIELD,
165
+ },
166
+ ];
167
+ }
168
+ function issuePath(pathSegments) {
169
+ return pathSegments.length > 0 ? pathSegments.map(String).join('.') : '<root>';
170
+ }
171
+ function collectPlaceholderIssuesFromValue(value, pathSegments, issues) {
172
+ if (typeof value === 'string') {
173
+ const normalized = value.trim().toLowerCase();
174
+ if ((pathSegments.at(-1) === 'common:referenceYear' && normalized === '9999') ||
175
+ normalized.includes('tidas_import_placeholder') ||
176
+ normalized.includes('tidas_import_trace_v1') ||
177
+ normalized.includes('not declared in source package') ||
178
+ normalized.includes('source package metadata not declared') ||
179
+ normalized === '<null>' ||
180
+ /(^|[\s"'`])(?:\/users\/|\/home\/|[a-z]:\\)/iu.test(value) ||
181
+ normalized.includes('placeholder.example') ||
182
+ normalized.includes('pending confirmation') ||
183
+ /00000000-0000-0000-0000-0000000000[0-9a-f]{2}/u.test(normalized)) {
184
+ issues.push({
185
+ code: 'process_placeholder_content',
186
+ message: 'Process payload contains placeholder or pending-confirmation content that must be replaced before save or publish.',
187
+ path: issuePath(pathSegments),
188
+ });
189
+ }
190
+ return;
191
+ }
192
+ if (typeof value === 'number') {
193
+ if (pathSegments.at(-1) === 'common:referenceYear' && value === 9999) {
194
+ issues.push({
195
+ code: 'process_placeholder_content',
196
+ message: 'Process payload contains placeholder or pending-confirmation content that must be replaced before save or publish.',
197
+ path: issuePath(pathSegments),
198
+ });
199
+ }
200
+ return;
201
+ }
202
+ if (Array.isArray(value)) {
203
+ value.forEach((item, index) => collectPlaceholderIssuesFromValue(item, [...pathSegments, index], issues));
204
+ return;
205
+ }
206
+ if (isRecord(value)) {
207
+ for (const [key, child] of Object.entries(value)) {
208
+ collectPlaceholderIssuesFromValue(child, [...pathSegments, key], issues);
209
+ }
210
+ }
211
+ }
212
+ export function collectProcessPlaceholderIssues(payload) {
213
+ const root = datasetRoot(payload, 'process');
214
+ const issues = [];
215
+ collectPlaceholderIssuesFromValue(root, ['processDataSet'], issues);
216
+ return issues;
217
+ }
218
+ function hasPlaceholderContent(value) {
219
+ const issues = [];
220
+ collectPlaceholderIssuesFromValue(value, [], issues);
221
+ return issues.length > 0;
222
+ }
223
+ function removePlaceholderField(parent, key, fieldPath, completions) {
224
+ if (!hasPlaceholderContent(parent[key])) {
225
+ return;
226
+ }
227
+ delete parent[key];
228
+ completions.push({
229
+ field_path: fieldPath,
230
+ source: 'placeholder_repair',
231
+ value: [],
232
+ amount: null,
233
+ unit: null,
234
+ reference_exchange_internal_id: null,
235
+ basis: 'Removed placeholder review metadata because the dataset is marked Not reviewed and no completed review evidence is present.',
236
+ });
237
+ }
238
+ function repairPlaceholderReviewMetadata(root) {
239
+ const modelling = isRecord(root.modellingAndValidation) ? root.modellingAndValidation : {};
240
+ const validation = isRecord(modelling.validation) ? modelling.validation : null;
241
+ if (!validation) {
242
+ return [];
243
+ }
244
+ const completions = [];
245
+ const validationPath = 'processDataSet.modellingAndValidation.validation';
246
+ for (const key of [
247
+ 'reviewDetails',
248
+ 'common:reviewDetails',
249
+ 'common:referenceToCompleteReviewReport',
250
+ 'common:referenceToNameOfReviewerAndInstitution',
251
+ ]) {
252
+ removePlaceholderField(validation, key, `${validationPath}.${key}`, completions);
253
+ }
254
+ if (isRecord(validation.review)) {
255
+ const reviewPath = `${validationPath}.review`;
256
+ for (const key of [
257
+ 'reviewDetails',
258
+ 'common:reviewDetails',
259
+ 'common:referenceToCompleteReviewReport',
260
+ 'common:referenceToNameOfReviewerAndInstitution',
261
+ ]) {
262
+ removePlaceholderField(validation.review, key, `${reviewPath}.${key}`, completions);
263
+ }
264
+ }
265
+ return completions;
266
+ }
267
+ function addRequiredStructureCompletion(completions, fieldPath, basis) {
268
+ completions.push({
269
+ field_path: fieldPath,
270
+ source: 'required_structure_repair',
271
+ value: [],
272
+ amount: null,
273
+ unit: null,
274
+ reference_exchange_internal_id: null,
275
+ basis,
276
+ });
277
+ }
278
+ function repairRequiredProcessStructures(root) {
279
+ const completions = [];
280
+ if (!isRecord(root.modellingAndValidation)) {
281
+ root.modellingAndValidation = {};
282
+ }
283
+ const modelling = root.modellingAndValidation;
284
+ if (!isRecord(modelling.validation)) {
285
+ modelling.validation = {};
286
+ }
287
+ const validation = modelling.validation;
288
+ if (!isValidReview(validation.review)) {
289
+ validation.review = {
290
+ '@type': 'Not reviewed',
291
+ };
292
+ addRequiredStructureCompletion(completions, 'processDataSet.modellingAndValidation.validation.review', 'Inserted a conservative Not reviewed marker because UI roundtrip or source data omitted the required validation.review structure.');
293
+ }
294
+ if (!isRecord(modelling.complianceDeclarations)) {
295
+ modelling.complianceDeclarations = {};
296
+ }
297
+ const complianceDeclarations = modelling.complianceDeclarations;
298
+ if (!isValidComplianceDeclaration(complianceDeclarations.compliance)) {
299
+ complianceDeclarations.compliance = cloneJson(DEFAULT_COMPLIANCE_DECLARATION);
300
+ addRequiredStructureCompletion(completions, 'processDataSet.modellingAndValidation.complianceDeclarations.compliance', 'Inserted a conservative Not defined compliance declaration against EF 3.1 because UI roundtrip or source data omitted the required compliance structure.');
301
+ }
302
+ return completions;
303
+ }
304
+ function processRootForMutation(payload) {
305
+ if (isRecord(payload.processDataSet)) {
306
+ return payload.processDataSet;
307
+ }
308
+ return payload;
309
+ }
310
+ function selectReferenceExchange(root) {
311
+ const exchangesRoot = isRecord(root.exchanges) ? root.exchanges : {};
312
+ const exchanges = asList(exchangesRoot.exchange).filter(isRecord);
313
+ const processInfo = isRecord(root.processInformation) ? root.processInformation : {};
314
+ const quantitativeReference = isRecord(processInfo.quantitativeReference)
315
+ ? processInfo.quantitativeReference
316
+ : {};
317
+ const referenceId = firstNonEmpty(quantitativeReference.referenceToReferenceFlow);
318
+ const byReferenceId = referenceId
319
+ ? exchanges.find((exchange) => firstNonEmpty(exchange['@dataSetInternalID']) === referenceId)
320
+ : null;
321
+ if (byReferenceId) {
322
+ return byReferenceId;
323
+ }
324
+ return (exchanges.find((exchange) => exchange.quantitativeReference === true) ??
325
+ exchanges.find((exchange) => String(exchange.exchangeDirection ?? '').toLowerCase() === 'output') ??
326
+ null);
327
+ }
328
+ function localizedTextIncludes(value, pattern) {
329
+ return annualSupplyItems(value).some((item) => pattern.test(item['#text']));
330
+ }
331
+ function inferSpecificUnitFromFlowPayload(payload) {
332
+ const root = datasetRoot(payload, 'flow');
333
+ const flowPropertiesRoot = isRecord(root.flowProperties) ? root.flowProperties : {};
334
+ const flowProperties = asList(flowPropertiesRoot.flowProperty).filter(isRecord);
335
+ for (const flowProperty of flowProperties) {
336
+ const direct = firstNonEmpty(flowProperty.unit, flowProperty.referenceUnit, flowProperty.flowUnit);
337
+ if (direct) {
338
+ return direct;
339
+ }
340
+ const flowPropertyRef = isRecord(flowProperty.referenceToFlowPropertyDataSet)
341
+ ? flowProperty.referenceToFlowPropertyDataSet
342
+ : {};
343
+ const flowPropertyId = firstNonEmpty(flowPropertyRef['@refObjectId']);
344
+ const flowPropertyText = flowPropertyRef['common:shortDescription'];
345
+ if (flowPropertyId === NET_CALORIFIC_VALUE_ID ||
346
+ localizedTextIncludes(flowPropertyText, /Net calorific|净热值/u)) {
347
+ return 'MJ';
348
+ }
349
+ }
350
+ const flowInformation = isRecord(root.flowInformation) ? root.flowInformation : {};
351
+ const dataSetInformation = isRecord(flowInformation.dataSetInformation)
352
+ ? flowInformation.dataSetInformation
353
+ : {};
354
+ const classificationInformation = isRecord(dataSetInformation.classificationInformation)
355
+ ? dataSetInformation.classificationInformation
356
+ : {};
357
+ const classification = isRecord(classificationInformation['common:classification'])
358
+ ? classificationInformation['common:classification']
359
+ : {};
360
+ const classificationClasses = classification['common:class'];
361
+ if (localizedTextIncludes(classificationClasses, /Electrical energy|electricity|交流电|电力|电能/iu)) {
362
+ return 'MJ';
363
+ }
364
+ return null;
365
+ }
366
+ function buildFlowUnitIndex(flowRows, fallbackUnit) {
367
+ const index = new Map();
368
+ for (const row of flowRows) {
369
+ if (row.kind !== 'flow' || !row.id) {
370
+ continue;
371
+ }
372
+ const inferred = inferSpecificUnitFromFlowPayload(row.payload);
373
+ if (inferred && inferred !== fallbackUnit) {
374
+ index.set(row.id, inferred);
375
+ }
376
+ }
377
+ return index;
378
+ }
379
+ function inferUnitFromReferenceExchange(exchange, fallbackOrContext) {
380
+ const context = typeof fallbackOrContext === 'string' ? { defaultUnit: fallbackOrContext } : fallbackOrContext;
381
+ const fallbackUnit = context.defaultUnit;
382
+ const flowRef = isRecord(exchange.referenceToFlowDataSet) ? exchange.referenceToFlowDataSet : {};
383
+ const refObjectId = firstNonEmpty(flowRef['@refObjectId']);
384
+ if (refObjectId) {
385
+ const indexedUnit = context.flowUnitById?.get(refObjectId);
386
+ if (indexedUnit) {
387
+ return indexedUnit;
388
+ }
389
+ }
390
+ const direct = firstNonEmpty(exchange.unit, exchange.referenceUnit, exchange.flowUnit);
391
+ if (direct) {
392
+ return direct;
393
+ }
394
+ const shortDescriptions = flowRef['common:shortDescription'];
395
+ if (localizedTextIncludes(shortDescriptions, /MJ|net calorific|lower calorific|Electrical energy|electricity|净热值|低位发热|交流电|电力|电能/iu)) {
396
+ return 'MJ';
397
+ }
398
+ const flowProperty = isRecord(exchange.flowProperty) ? exchange.flowProperty : {};
399
+ const flowPropertyRef = isRecord(flowProperty.referenceToFlowPropertyDataSet)
400
+ ? flowProperty.referenceToFlowPropertyDataSet
401
+ : {};
402
+ const flowPropertyId = firstNonEmpty(flowPropertyRef['@refObjectId']);
403
+ const flowPropertyText = flowPropertyRef['common:shortDescription'];
404
+ if (refObjectId === NET_CALORIFIC_VALUE_ID ||
405
+ flowPropertyId === NET_CALORIFIC_VALUE_ID ||
406
+ localizedTextIncludes(flowPropertyText, /Net calorific|净热值/u)) {
407
+ return 'MJ';
408
+ }
409
+ return fallbackUnit;
410
+ }
411
+ function buildAnnualSupplyValue(amount, unit) {
412
+ return [{ '@xml:lang': 'en', '#text': `${amount} ${unit}/year` }];
413
+ }
414
+ function annualSupplyTextParts(value) {
415
+ const match = /^\s*([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[Ee][+-]?\d+)?)\s+(\S.*)$/u.exec(value);
416
+ return match ? { amount: match[1], unit: match[2].trim() } : null;
417
+ }
418
+ function annualSupplyValueFromText(value) {
419
+ const text = value.trim();
420
+ if (!NUMERIC_TEXT_WITH_SUFFIX_PATTERN.test(text) || !ANNUAL_PERIOD_PATTERN.test(text)) {
421
+ return null;
422
+ }
423
+ const parts = annualSupplyTextParts(text);
424
+ return {
425
+ value: [{ '@xml:lang': sourceLanguageForText(text), '#text': text }],
426
+ amount: parts.amount,
427
+ unit: parts.unit,
428
+ basis: 'Evidence provided a complete annual supply / production volume text value.',
429
+ };
430
+ }
431
+ function normalizeAnnualSupplyEvidenceValue(candidate, context) {
432
+ if (typeof candidate === 'string') {
433
+ return annualSupplyValueFromText(candidate);
434
+ }
435
+ if (typeof candidate === 'number' && Number.isFinite(candidate)) {
436
+ const amount = String(candidate);
437
+ return {
438
+ value: buildAnnualSupplyValue(amount, context.defaultUnit),
439
+ amount,
440
+ unit: context.defaultUnit,
441
+ basis: 'Evidence provided a numeric annual supply / production volume; the configured default unit was applied.',
442
+ };
443
+ }
444
+ if (isValidAnnualSupplyVolume(candidate)) {
445
+ const value = [
446
+ annualSupplyItems(candidate).map((item) => ({
447
+ '@xml:lang': item['@xml:lang'],
448
+ '#text': item['#text'].trim(),
449
+ }))[0],
450
+ ];
451
+ const parts = annualSupplyTextParts(value[0]['#text']);
452
+ return {
453
+ value,
454
+ amount: parts.amount,
455
+ unit: parts.unit,
456
+ basis: 'Evidence provided a validated source-language annual supply / production volume value.',
457
+ };
458
+ }
459
+ if (!isRecord(candidate)) {
460
+ return null;
461
+ }
462
+ for (const key of [
463
+ 'value',
464
+ 'text',
465
+ 'value_text',
466
+ 'valueText',
467
+ 'annualSupplyOrProductionVolume',
468
+ 'annual_supply_or_production_volume',
469
+ ]) {
470
+ const nested = normalizeAnnualSupplyEvidenceValue(candidate[key], context);
471
+ if (nested) {
472
+ return nested;
473
+ }
474
+ }
475
+ const preferredLanguage = textValue(candidate.source_language ??
476
+ candidate.sourceLanguage ??
477
+ candidate.lang ??
478
+ candidate['@xml:lang'])?.toLowerCase();
479
+ const english = textValue(candidate.en ?? candidate.english);
480
+ const chinese = textValue(candidate.zh ?? candidate.chinese ?? candidate['zh-CN']);
481
+ const localized = [
482
+ english ? { '@xml:lang': 'en', '#text': english } : null,
483
+ chinese ? { '@xml:lang': 'zh', '#text': chinese } : null,
484
+ ].filter((item) => Boolean(item));
485
+ if (localized.length > 0 && isValidAnnualSupplyVolume(localized)) {
486
+ const selected = localized.find((item) => item['@xml:lang'].toLowerCase() === preferredLanguage) ??
487
+ localized[0];
488
+ const value = [selected];
489
+ const parts = annualSupplyTextParts(selected['#text']);
490
+ return {
491
+ value,
492
+ amount: parts.amount,
493
+ unit: parts.unit,
494
+ basis: 'Evidence provided a language-specific annual supply / production volume text value.',
495
+ };
496
+ }
497
+ const amount = textValue(candidate.amount ?? candidate.value_amount ?? candidate.valueAmount);
498
+ const unit = normalizeUnit(textValue(candidate.unit ?? candidate.uom ?? candidate.reference_unit ?? candidate.referenceUnit) ?? context.defaultUnit);
499
+ if (amount) {
500
+ return {
501
+ value: buildAnnualSupplyValue(amount, unit),
502
+ amount,
503
+ unit,
504
+ basis: 'Evidence provided annual supply / production volume amount and unit as structured fields.',
505
+ };
506
+ }
507
+ return null;
508
+ }
509
+ function isAnnualSupplyEvidencePath(pathExpression) {
510
+ if (!pathExpression) {
511
+ return false;
512
+ }
513
+ return (pathExpression === ANNUAL_SUPPLY_FIELD ||
514
+ pathExpression === ANNUAL_SUPPLY_ROOT_FIELD ||
515
+ pathExpression.endsWith(`.${ANNUAL_SUPPLY_ROOT_FIELD}`) ||
516
+ pathExpression.endsWith('.annualSupplyOrProductionVolume'));
517
+ }
518
+ function fieldPathFromEvidenceEntry(entry) {
519
+ return firstNonEmpty(entry.field_path, entry.fieldPath, entry.path, entry.field, entry.target_path, entry.targetPath);
520
+ }
521
+ function findAnnualSupplyEvidenceEntry(container, context) {
522
+ if (!isRecord(container)) {
523
+ return null;
524
+ }
525
+ const directPath = fieldPathFromEvidenceEntry(container);
526
+ if (isAnnualSupplyEvidencePath(directPath)) {
527
+ const directValue = normalizeAnnualSupplyEvidenceValue(container, context);
528
+ if (directValue) {
529
+ return directValue;
530
+ }
531
+ }
532
+ for (const key of [
533
+ 'field_values',
534
+ 'fieldValues',
535
+ 'field_bindings',
536
+ 'fieldBindings',
537
+ 'bindings',
538
+ ]) {
539
+ const entries = asList(container[key]).filter(isRecord);
540
+ for (const entry of entries) {
541
+ if (!isAnnualSupplyEvidencePath(fieldPathFromEvidenceEntry(entry))) {
542
+ continue;
543
+ }
544
+ const value = normalizeAnnualSupplyEvidenceValue(entry, context);
545
+ if (value) {
546
+ return value;
547
+ }
548
+ }
549
+ }
550
+ return null;
551
+ }
552
+ function findAnnualSupplyEvidenceValue(row, payload, context) {
553
+ const explicitPaths = [
554
+ 'annualSupplyOrProductionVolume',
555
+ 'annual_supply_or_production_volume',
556
+ 'required_fields.annualSupplyOrProductionVolume',
557
+ 'requiredFields.annualSupplyOrProductionVolume',
558
+ 'authoring.required_fields.annualSupplyOrProductionVolume',
559
+ 'authoring.requiredFields.annualSupplyOrProductionVolume',
560
+ 'process_authoring.required_fields.annualSupplyOrProductionVolume',
561
+ 'processAuthoring.requiredFields.annualSupplyOrProductionVolume',
562
+ ];
563
+ for (const source of [row, payload]) {
564
+ for (const explicitPath of explicitPaths) {
565
+ const value = normalizeAnnualSupplyEvidenceValue(valueAtPath(source, explicitPath), context);
566
+ if (value) {
567
+ return value;
568
+ }
569
+ }
570
+ }
571
+ const evidencePaths = [
572
+ 'evidence_manifest',
573
+ 'evidenceManifest',
574
+ 'authoring_evidence',
575
+ 'authoringEvidence',
576
+ 'process_authoring.evidence_manifest',
577
+ 'processAuthoring.evidenceManifest',
578
+ ];
579
+ for (const source of [row, payload]) {
580
+ for (const evidencePath of evidencePaths) {
581
+ const value = findAnnualSupplyEvidenceEntry(valueAtPath(source, evidencePath), context);
582
+ if (value) {
583
+ return value;
584
+ }
585
+ }
586
+ }
587
+ return null;
588
+ }
589
+ function ensureDataSources(root) {
590
+ if (!isRecord(root.modellingAndValidation)) {
591
+ root.modellingAndValidation = {};
592
+ }
593
+ const modelling = root.modellingAndValidation;
594
+ if (!isRecord(modelling.dataSourcesTreatmentAndRepresentativeness)) {
595
+ modelling.dataSourcesTreatmentAndRepresentativeness = {};
596
+ }
597
+ return modelling.dataSourcesTreatmentAndRepresentativeness;
598
+ }
599
+ function cloneRowWithPayload(row) {
600
+ const clonedRow = cloneJson(row.row);
601
+ if (isRecord(clonedRow.json_ordered)) {
602
+ return { row: clonedRow, payload: clonedRow.json_ordered };
603
+ }
604
+ if (isRecord(clonedRow.jsonOrdered)) {
605
+ return { row: clonedRow, payload: clonedRow.jsonOrdered };
606
+ }
607
+ if (isRecord(clonedRow.json)) {
608
+ return { row: clonedRow, payload: clonedRow.json };
609
+ }
610
+ if (isRecord(clonedRow.payload)) {
611
+ return { row: clonedRow, payload: clonedRow.payload };
612
+ }
613
+ if (isRecord(clonedRow.process)) {
614
+ return { row: clonedRow, payload: clonedRow.process };
615
+ }
616
+ return { row: clonedRow, payload: clonedRow };
617
+ }
618
+ function completeProcessRow(row, context) {
619
+ const { row: clonedRow, payload } = cloneRowWithPayload(row);
620
+ if (row.kind !== 'process') {
621
+ return {
622
+ row: clonedRow,
623
+ report: {
624
+ index: row.index,
625
+ id: row.id,
626
+ version: row.version,
627
+ type: row.kind,
628
+ status: 'skipped',
629
+ issues: [],
630
+ completions: [],
631
+ },
632
+ };
633
+ }
634
+ const root = processRootForMutation(payload);
635
+ const placeholderCompletions = repairPlaceholderReviewMetadata(root);
636
+ const requiredStructureCompletions = repairRequiredProcessStructures(root);
637
+ const repairCompletions = [...placeholderCompletions, ...requiredStructureCompletions];
638
+ const existingIssues = collectProcessRequiredFieldIssues(payload);
639
+ const placeholderIssues = collectProcessPlaceholderIssues(payload);
640
+ if (existingIssues.length === 0 && placeholderIssues.length === 0) {
641
+ return {
642
+ row: clonedRow,
643
+ report: {
644
+ index: row.index,
645
+ id: row.id,
646
+ version: row.version,
647
+ type: row.kind,
648
+ status: repairCompletions.length > 0 ? 'completed' : 'existing',
649
+ issues: [],
650
+ completions: repairCompletions,
651
+ },
652
+ };
653
+ }
654
+ if (placeholderIssues.length > 0) {
655
+ return {
656
+ row: clonedRow,
657
+ report: {
658
+ index: row.index,
659
+ id: row.id,
660
+ version: row.version,
661
+ type: row.kind,
662
+ status: 'blocked',
663
+ issues: [...existingIssues, ...placeholderIssues],
664
+ completions: repairCompletions,
665
+ },
666
+ };
667
+ }
668
+ const evidenceValue = findAnnualSupplyEvidenceValue(clonedRow, payload, context);
669
+ if (evidenceValue) {
670
+ const dataSources = ensureDataSources(root);
671
+ dataSources.annualSupplyOrProductionVolume = evidenceValue.value;
672
+ const completion = {
673
+ field_path: ANNUAL_SUPPLY_FIELD,
674
+ source: 'evidence',
675
+ value: evidenceValue.value,
676
+ amount: evidenceValue.amount,
677
+ unit: evidenceValue.unit,
678
+ reference_exchange_internal_id: null,
679
+ basis: evidenceValue.basis,
680
+ };
681
+ return {
682
+ row: clonedRow,
683
+ report: {
684
+ index: row.index,
685
+ id: row.id,
686
+ version: row.version,
687
+ type: row.kind,
688
+ status: 'completed',
689
+ issues: [],
690
+ completions: [...repairCompletions, completion],
691
+ },
692
+ };
693
+ }
694
+ return {
695
+ row: clonedRow,
696
+ report: {
697
+ index: row.index,
698
+ id: row.id,
699
+ version: row.version,
700
+ type: row.kind,
701
+ status: 'blocked',
702
+ issues: [
703
+ ...existingIssues,
704
+ {
705
+ code: 'annual_supply_evidence_missing',
706
+ message: 'Could not complete annualSupplyOrProductionVolume because no source evidence value was provided.',
707
+ path: ANNUAL_SUPPLY_FIELD,
708
+ },
709
+ ],
710
+ completions: repairCompletions,
711
+ },
712
+ };
713
+ }
714
+ function buildFiles(outPath, outDir) {
715
+ if (!outDir) {
716
+ return {
717
+ output_rows: path.resolve(outPath),
718
+ report: null,
719
+ evidence: null,
720
+ };
721
+ }
722
+ const resolved = path.resolve(outDir);
723
+ return {
724
+ output_rows: path.resolve(outPath),
725
+ report: path.join(resolved, 'outputs', 'process-required-fields-report.json'),
726
+ evidence: path.join(resolved, 'outputs', 'process-required-fields-evidence.jsonl'),
727
+ };
728
+ }
729
+ function requireOutputPath(value) {
730
+ const trimmed = value.trim();
731
+ if (!trimmed) {
732
+ throw new CliError('Missing required --out value.', {
733
+ code: 'PROCESS_REQUIRED_FIELDS_OUT_REQUIRED',
734
+ exitCode: 2,
735
+ });
736
+ }
737
+ return trimmed;
738
+ }
739
+ export async function runProcessRequiredFieldsComplete(options) {
740
+ const outPath = requireOutputPath(options.outPath);
741
+ const outDir = options.outDir?.trim() ? options.outDir.trim() : null;
742
+ const defaultUnit = normalizeUnit(options.defaultUnit);
743
+ const rows = materializeDatasetRows(options.inputPath, options.rawInput);
744
+ const flowRows = options.flowInputPath?.trim()
745
+ ? materializeDatasetRows(options.flowInputPath, options.rawFlowInput)
746
+ : [];
747
+ const flowUnitById = buildFlowUnitIndex(flowRows, defaultUnit);
748
+ const completed = rows.map((row) => completeProcessRow(row, { defaultUnit, flowUnitById }));
749
+ const rowReports = completed.map((item) => item.report);
750
+ const files = buildFiles(outPath, outDir);
751
+ const blockers = rowReports.filter((row) => row.status === 'blocked');
752
+ const report = {
753
+ generated_at_utc: (options.now ?? new Date()).toISOString(),
754
+ input_path: path.resolve(options.inputPath),
755
+ out_path: path.resolve(outPath),
756
+ out_dir: outDir ? path.resolve(outDir) : null,
757
+ status: blockers.length > 0 ? 'completed_with_blockers' : 'completed',
758
+ default_unit: defaultUnit,
759
+ counts: {
760
+ total: rowReports.length,
761
+ processes: rowReports.filter((row) => row.type === 'process').length,
762
+ completed: rowReports.filter((row) => row.status === 'completed').length,
763
+ existing: rowReports.filter((row) => row.status === 'existing').length,
764
+ blocked: blockers.length,
765
+ skipped: rowReports.filter((row) => row.status === 'skipped').length,
766
+ },
767
+ files,
768
+ rows: rowReports,
769
+ };
770
+ writeJsonLinesArtifact(files.output_rows, completed.map((item) => item.row));
771
+ if (files.report) {
772
+ writeJsonArtifact(files.report, report);
773
+ }
774
+ if (files.evidence) {
775
+ writeJsonLinesArtifact(files.evidence, rowReports.flatMap((row) => row.completions.map((completion) => ({
776
+ row_index: row.index,
777
+ dataset_id: row.id,
778
+ dataset_version: row.version,
779
+ ...completion,
780
+ }))));
781
+ }
782
+ return report;
783
+ }
784
+ export const __testInternals = {
785
+ ANNUAL_SUPPLY_ROOT_FIELD,
786
+ annualSupplyTextParts,
787
+ annualSupplyValueFromText,
788
+ cloneRowWithPayload,
789
+ collectProcessRequiredFieldIssues,
790
+ completeProcessRow,
791
+ ensureDataSources,
792
+ fieldPathFromEvidenceEntry,
793
+ findAnnualSupplyEvidenceValue,
794
+ findAnnualSupplyEvidenceEntry,
795
+ buildFlowUnitIndex,
796
+ inferUnitFromReferenceExchange,
797
+ inferSpecificUnitFromFlowPayload,
798
+ issuePath,
799
+ isAnnualSupplyEvidencePath,
800
+ isValidComplianceDeclaration,
801
+ isValidAnnualSupplyVolume,
802
+ isValidReview,
803
+ normalizeAnnualSupplyEvidenceValue,
804
+ repairRequiredProcessStructures,
805
+ requireOutputPath,
806
+ selectReferenceExchange,
807
+ textValue,
808
+ valueAtPath,
809
+ };
810
+ //# sourceMappingURL=process-required-fields.js.map