@tiangong-lca/cli 0.0.11 → 0.0.13
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/README.md +18 -7
- package/assets/tidas-schemas/tidas_contacts.json +312 -0
- package/assets/tidas-schemas/tidas_contacts_category.json +126 -0
- package/assets/tidas-schemas/tidas_data_types.json +359 -0
- package/assets/tidas-schemas/tidas_flowproperties.json +303 -0
- package/assets/tidas-schemas/tidas_flowproperties_category.json +61 -0
- package/assets/tidas-schemas/tidas_flows.json +809 -0
- package/assets/tidas-schemas/tidas_flows_elementary_category.json +720 -0
- package/assets/tidas-schemas/tidas_flows_product_category.json +59623 -0
- package/assets/tidas-schemas/tidas_lciamethods.json +1326 -0
- package/assets/tidas-schemas/tidas_lciamethods_category.json +685 -0
- package/assets/tidas-schemas/tidas_lifecyclemodels.json +1374 -0
- package/assets/tidas-schemas/tidas_locations_category.json +2593 -0
- package/assets/tidas-schemas/tidas_processes.json +1646 -0
- package/assets/tidas-schemas/tidas_processes_category.json +10795 -0
- package/assets/tidas-schemas/tidas_sources.json +228 -0
- package/assets/tidas-schemas/tidas_sources_category.json +100 -0
- package/assets/tidas-schemas/tidas_unitgroups.json +338 -0
- package/assets/tidas-schemas/tidas_unitgroups_category.json +61 -0
- package/dist/src/cli.js +847 -13
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-classification.js +947 -0
- package/dist/src/lib/dataset-classification.js.map +1 -0
- package/dist/src/lib/dataset-command.js +11 -0
- package/dist/src/lib/dataset-command.js.map +1 -1
- package/dist/src/lib/dataset-contract.js +2 -0
- package/dist/src/lib/dataset-contract.js.map +1 -1
- package/dist/src/lib/dataset-curation-queue.js +359 -2
- package/dist/src/lib/dataset-curation-queue.js.map +1 -1
- package/dist/src/lib/dataset-import-lca.js +18 -0
- package/dist/src/lib/dataset-import-lca.js.map +1 -1
- package/dist/src/lib/dataset-local.js +94 -1
- package/dist/src/lib/dataset-local.js.map +1 -1
- package/dist/src/lib/dataset-maintenance-clear-account.js +506 -0
- package/dist/src/lib/dataset-maintenance-clear-account.js.map +1 -0
- package/dist/src/lib/dataset-patch.js +797 -0
- package/dist/src/lib/dataset-patch.js.map +1 -0
- package/dist/src/lib/dataset-remote-verify.js +188 -3
- package/dist/src/lib/dataset-remote-verify.js.map +1 -1
- package/dist/src/lib/dataset-save-draft-run.js +725 -0
- package/dist/src/lib/dataset-save-draft-run.js.map +1 -0
- package/dist/src/lib/dataset-validate.js +32 -2
- package/dist/src/lib/dataset-validate.js.map +1 -1
- package/dist/src/lib/flow-publish-version.js +7 -2
- package/dist/src/lib/flow-publish-version.js.map +1 -1
- package/dist/src/lib/flow-qa.js +8 -0
- package/dist/src/lib/flow-qa.js.map +1 -1
- package/dist/src/lib/identity-preflight.js +895 -117
- package/dist/src/lib/identity-preflight.js.map +1 -1
- package/dist/src/lib/lifecyclemodel-qa.js +159 -34
- package/dist/src/lib/lifecyclemodel-qa.js.map +1 -1
- package/dist/src/lib/process-required-fields.js +62 -12
- package/dist/src/lib/process-required-fields.js.map +1 -1
- package/dist/src/lib/process-save-draft-run.js +10 -0
- package/dist/src/lib/process-save-draft-run.js.map +1 -1
- package/dist/src/lib/process-save-draft.js +59 -3
- package/dist/src/lib/process-save-draft.js.map +1 -1
- package/dist/src/lib/supabase-client.js +19 -8
- package/dist/src/lib/supabase-client.js.map +1 -1
- package/package.json +2 -1
|
@@ -14,6 +14,7 @@ const SCHEMA_EXPORTS = {
|
|
|
14
14
|
flow: 'FlowSchema',
|
|
15
15
|
process: 'ProcessSchema',
|
|
16
16
|
};
|
|
17
|
+
const REMOTE_DATA_SOURCES = new Set(['tg', 'co', 'my', 'te']);
|
|
17
18
|
const ENTITY_FACTORY_EXPORTS = {
|
|
18
19
|
flow: 'createFlow',
|
|
19
20
|
process: 'createProcess',
|
|
@@ -76,12 +77,55 @@ function normalizePositiveInteger(value, label) {
|
|
|
76
77
|
}
|
|
77
78
|
return parsed;
|
|
78
79
|
}
|
|
80
|
+
function normalizeNonNegativeNumber(value, label) {
|
|
81
|
+
if (value === undefined || value === null || value === '') {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const parsed = typeof value === 'number' ? value : Number(String(value));
|
|
85
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
86
|
+
throw new CliError(`Expected ${label} to be a non-negative number.`, {
|
|
87
|
+
code: 'IDENTITY_PREFLIGHT_INVALID_REMOTE_SEARCH_OPTION',
|
|
88
|
+
exitCode: 2,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return parsed;
|
|
92
|
+
}
|
|
93
|
+
function normalizeMatchThreshold(value) {
|
|
94
|
+
const parsed = normalizeNonNegativeNumber(value, 'remote_candidate_search.match_threshold');
|
|
95
|
+
if (parsed === null) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
if (parsed > 1) {
|
|
99
|
+
throw new CliError('Expected remote_candidate_search.match_threshold to be between 0 and 1.', {
|
|
100
|
+
code: 'IDENTITY_PREFLIGHT_INVALID_REMOTE_SEARCH_OPTION',
|
|
101
|
+
exitCode: 2,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return parsed;
|
|
105
|
+
}
|
|
106
|
+
function emptyRemoteCandidateSearchConfig(enabled = false) {
|
|
107
|
+
return {
|
|
108
|
+
enabled,
|
|
109
|
+
query: null,
|
|
110
|
+
filter: null,
|
|
111
|
+
profileHints: null,
|
|
112
|
+
limit: null,
|
|
113
|
+
dataSource: null,
|
|
114
|
+
matchThreshold: null,
|
|
115
|
+
fullTextWeight: null,
|
|
116
|
+
extractedTextWeight: null,
|
|
117
|
+
semanticWeight: null,
|
|
118
|
+
rrfK: null,
|
|
119
|
+
pageSize: null,
|
|
120
|
+
pageCurrent: null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
79
123
|
function normalizeRemoteCandidateSearch(value) {
|
|
80
124
|
if (value === undefined || value === null) {
|
|
81
|
-
return
|
|
125
|
+
return emptyRemoteCandidateSearchConfig(false);
|
|
82
126
|
}
|
|
83
127
|
if (typeof value === 'boolean') {
|
|
84
|
-
return
|
|
128
|
+
return emptyRemoteCandidateSearchConfig(value);
|
|
85
129
|
}
|
|
86
130
|
if (!isRecord(value)) {
|
|
87
131
|
throw new CliError('remote_candidate_search must be a boolean or object.', {
|
|
@@ -94,11 +138,31 @@ function normalizeRemoteCandidateSearch(value) {
|
|
|
94
138
|
const filter = value.filter === undefined || value.filter === null
|
|
95
139
|
? null
|
|
96
140
|
: normalizeToRecord(value.filter, 'remote_candidate_search.filter');
|
|
141
|
+
const profileHintsInput = value.profile_hints ?? value.profileHints ?? value.identity_profile_hints;
|
|
142
|
+
const profileHints = profileHintsInput === undefined || profileHintsInput === null
|
|
143
|
+
? null
|
|
144
|
+
: normalizeToRecord(profileHintsInput, 'remote_candidate_search.profile_hints');
|
|
145
|
+
const dataSource = textValue(value.data_source) ?? textValue(value.dataSource) ?? textValue(value.source) ?? null;
|
|
146
|
+
if (dataSource && !REMOTE_DATA_SOURCES.has(dataSource)) {
|
|
147
|
+
throw new CliError('remote_candidate_search.data_source must be one of tg, co, my, or te.', {
|
|
148
|
+
code: 'IDENTITY_PREFLIGHT_INVALID_REMOTE_DATA_SOURCE',
|
|
149
|
+
exitCode: 2,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
97
152
|
return {
|
|
98
153
|
enabled,
|
|
99
154
|
query,
|
|
100
155
|
filter,
|
|
156
|
+
profileHints,
|
|
101
157
|
limit: normalizePositiveInteger(value.limit, 'remote_candidate_search.limit'),
|
|
158
|
+
dataSource,
|
|
159
|
+
matchThreshold: normalizeMatchThreshold(value.match_threshold ?? value.matchThreshold),
|
|
160
|
+
fullTextWeight: normalizeNonNegativeNumber(value.full_text_weight ?? value.fullTextWeight, 'remote_candidate_search.full_text_weight'),
|
|
161
|
+
extractedTextWeight: normalizeNonNegativeNumber(value.extracted_text_weight ?? value.extractedTextWeight, 'remote_candidate_search.extracted_text_weight'),
|
|
162
|
+
semanticWeight: normalizeNonNegativeNumber(value.semantic_weight ?? value.semanticWeight, 'remote_candidate_search.semantic_weight'),
|
|
163
|
+
rrfK: normalizePositiveInteger(value.rrf_k ?? value.rrfK, 'remote_candidate_search.rrf_k'),
|
|
164
|
+
pageSize: normalizePositiveInteger(value.page_size ?? value.pageSize, 'remote_candidate_search.page_size'),
|
|
165
|
+
pageCurrent: normalizePositiveInteger(value.page_current ?? value.pageCurrent, 'remote_candidate_search.page_current'),
|
|
102
166
|
};
|
|
103
167
|
}
|
|
104
168
|
function pickKindTarget(input, kind) {
|
|
@@ -202,22 +266,130 @@ function readCandidateSource(candidatePath) {
|
|
|
202
266
|
};
|
|
203
267
|
}
|
|
204
268
|
function mergeRemoteCandidateSearchConfig(inputConfig, options) {
|
|
269
|
+
const dataSource = options.remoteDataSource?.trim() || inputConfig.dataSource;
|
|
270
|
+
if (dataSource && !REMOTE_DATA_SOURCES.has(dataSource)) {
|
|
271
|
+
throw new CliError('--remote-data-source must be one of tg, co, my, or te.', {
|
|
272
|
+
code: 'IDENTITY_PREFLIGHT_INVALID_REMOTE_DATA_SOURCE',
|
|
273
|
+
exitCode: 2,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
205
276
|
return {
|
|
206
277
|
enabled: options.remoteCandidateSearch ?? inputConfig.enabled,
|
|
207
278
|
query: options.remoteQuery?.trim() || inputConfig.query,
|
|
208
279
|
filter: options.remoteFilter ?? inputConfig.filter,
|
|
280
|
+
profileHints: inputConfig.profileHints,
|
|
209
281
|
limit: options.remoteLimit ?? inputConfig.limit,
|
|
282
|
+
dataSource,
|
|
283
|
+
matchThreshold: inputConfig.matchThreshold,
|
|
284
|
+
fullTextWeight: inputConfig.fullTextWeight,
|
|
285
|
+
extractedTextWeight: inputConfig.extractedTextWeight,
|
|
286
|
+
semanticWeight: inputConfig.semanticWeight,
|
|
287
|
+
rrfK: inputConfig.rrfK,
|
|
288
|
+
pageSize: inputConfig.pageSize,
|
|
289
|
+
pageCurrent: inputConfig.pageCurrent,
|
|
210
290
|
};
|
|
211
291
|
}
|
|
212
292
|
function remoteSearchEndpoint(kind) {
|
|
213
293
|
return kind === 'process' ? 'process_hybrid_search' : 'flow_hybrid_search';
|
|
214
294
|
}
|
|
295
|
+
function isRemoteQueryNoiseText(value) {
|
|
296
|
+
const text = value.normalize('NFKC').replace(/\s+/gu, ' ').trim();
|
|
297
|
+
if (!text) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
if (/^(not specified|not declared|unspecified|n\/a|none|null)$/iu.test(text)) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
if (/^not specified by the .* source\.?$/iu.test(text)) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
if (/^ilcd format$/iu.test(text)) {
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
if (/^ilcd data network\s*-\s*entry-level$/iu.test(text)) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
function queryFieldValues(value, limit = 4) {
|
|
315
|
+
return (Array.isArray(value) ? value : [value])
|
|
316
|
+
.filter((entry) => typeof entry === 'string')
|
|
317
|
+
.map((entry) => entry.trim())
|
|
318
|
+
.filter((entry) => !isRemoteQueryNoiseText(entry))
|
|
319
|
+
.slice(0, limit);
|
|
320
|
+
}
|
|
321
|
+
function appendQueryLine(lines, label, value, limit = 4) {
|
|
322
|
+
const values = queryFieldValues(value, limit);
|
|
323
|
+
if (values.length === 0) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
lines.push(`${label}: ${values.join('; ')}`);
|
|
327
|
+
}
|
|
328
|
+
function exchangeFlowRefsFromSignature(signature) {
|
|
329
|
+
const refs = new Set();
|
|
330
|
+
for (const entry of signature) {
|
|
331
|
+
const ref = entry.split(':')[0]?.trim();
|
|
332
|
+
if (ref) {
|
|
333
|
+
refs.add(ref);
|
|
334
|
+
}
|
|
335
|
+
if (refs.size >= 8) {
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return [...refs];
|
|
340
|
+
}
|
|
341
|
+
function compactRemoteQuery(lines, fallback) {
|
|
342
|
+
const text = lines
|
|
343
|
+
.map((line) => line.replace(/\s+/gu, ' ').trim())
|
|
344
|
+
.filter(Boolean)
|
|
345
|
+
.join('\n')
|
|
346
|
+
.slice(0, 1800)
|
|
347
|
+
.trim();
|
|
348
|
+
if (text) {
|
|
349
|
+
return text;
|
|
350
|
+
}
|
|
351
|
+
return fallback ? fallback.slice(0, 1800).trim() || null : null;
|
|
352
|
+
}
|
|
353
|
+
function fallbackRemoteQueryText(profile) {
|
|
354
|
+
return (Object.values(profile.fields)
|
|
355
|
+
.flat()
|
|
356
|
+
.find((value) => typeof value === 'string' && value.trim().length > 0) ??
|
|
357
|
+
profile.identity_key);
|
|
358
|
+
}
|
|
359
|
+
function flowRemoteQuery(profile) {
|
|
360
|
+
const lines = [];
|
|
361
|
+
appendQueryLine(lines, 'flow name', profile.names, 4);
|
|
362
|
+
appendQueryLine(lines, 'flow type', profile.fields.type_of_dataset);
|
|
363
|
+
appendQueryLine(lines, 'CAS', profile.fields.cas);
|
|
364
|
+
appendQueryLine(lines, 'reference property', profile.fields.flow_property);
|
|
365
|
+
appendQueryLine(lines, 'reference unit', profile.fields.reference_unit);
|
|
366
|
+
appendQueryLine(lines, 'category or compartment', profile.fields.categories, 6);
|
|
367
|
+
appendQueryLine(lines, 'geography or market', profile.fields.geography);
|
|
368
|
+
return compactRemoteQuery(lines, fallbackRemoteQueryText(profile));
|
|
369
|
+
}
|
|
370
|
+
function processRemoteQuery(profile) {
|
|
371
|
+
const lines = [];
|
|
372
|
+
appendQueryLine(lines, 'process name', profile.names, 4);
|
|
373
|
+
appendQueryLine(lines, 'reference flow', [
|
|
374
|
+
...queryFieldValues(profile.fields.reference_flow_names, 4),
|
|
375
|
+
...queryFieldValues(profile.fields.reference_flow_ids, 4),
|
|
376
|
+
], 8);
|
|
377
|
+
appendQueryLine(lines, 'quantitative reference', profile.fields.quantitative_reference);
|
|
378
|
+
appendQueryLine(lines, 'geography', profile.fields.geography);
|
|
379
|
+
appendQueryLine(lines, 'time', profile.fields.time);
|
|
380
|
+
appendQueryLine(lines, 'classification or sector', profile.fields.categories, 6);
|
|
381
|
+
appendQueryLine(lines, 'technology route', profile.fields.technology_route);
|
|
382
|
+
appendQueryLine(lines, 'system boundary', profile.fields.system_boundary);
|
|
383
|
+
appendQueryLine(lines, 'operation', profile.fields.operation);
|
|
384
|
+
appendQueryLine(lines, 'provider role', profile.fields.provider_role);
|
|
385
|
+
appendQueryLine(lines, 'exchange flow refs', exchangeFlowRefsFromSignature(profile.exchange_signature), 8);
|
|
386
|
+
return compactRemoteQuery(lines, fallbackRemoteQueryText(profile));
|
|
387
|
+
}
|
|
215
388
|
function defaultRemoteQuery(profile) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
(profile.identity_key ? profile.identity_key : null));
|
|
389
|
+
const hasProcessFields = 'reference_flow_ids' in profile.fields ||
|
|
390
|
+
'technology_route' in profile.fields ||
|
|
391
|
+
profile.exchange_signature.length > 0;
|
|
392
|
+
return hasProcessFields ? processRemoteQuery(profile) : flowRemoteQuery(profile);
|
|
221
393
|
}
|
|
222
394
|
function remoteSearchFilter(kind, profile, explicitFilter) {
|
|
223
395
|
const filter = explicitFilter ? { ...explicitFilter } : {};
|
|
@@ -231,6 +403,20 @@ function remoteSearchFilter(kind, profile, explicitFilter) {
|
|
|
231
403
|
}
|
|
232
404
|
return Object.keys(filter).length > 0 ? filter : null;
|
|
233
405
|
}
|
|
406
|
+
function remoteSearchOptions(config) {
|
|
407
|
+
const options = {
|
|
408
|
+
...(config.matchThreshold !== null ? { match_threshold: config.matchThreshold } : {}),
|
|
409
|
+
...(config.fullTextWeight !== null ? { full_text_weight: config.fullTextWeight } : {}),
|
|
410
|
+
...(config.extractedTextWeight !== null
|
|
411
|
+
? { extracted_text_weight: config.extractedTextWeight }
|
|
412
|
+
: {}),
|
|
413
|
+
...(config.semanticWeight !== null ? { semantic_weight: config.semanticWeight } : {}),
|
|
414
|
+
...(config.rrfK !== null ? { rrf_k: config.rrfK } : {}),
|
|
415
|
+
...(config.pageSize !== null ? { page_size: config.pageSize } : {}),
|
|
416
|
+
...(config.pageCurrent !== null ? { page_current: config.pageCurrent } : {}),
|
|
417
|
+
};
|
|
418
|
+
return Object.keys(options).length > 0 ? options : null;
|
|
419
|
+
}
|
|
234
420
|
function rowsFromRemoteSearchResponse(value) {
|
|
235
421
|
const rows = isRecord(value)
|
|
236
422
|
? (value.data ?? value.rows ?? value.results ?? value.candidates ?? [])
|
|
@@ -264,10 +450,21 @@ async function readRemoteCandidateSource(kind, targetProfile, config, options) {
|
|
|
264
450
|
now: options.now,
|
|
265
451
|
});
|
|
266
452
|
const filter = remoteSearchFilter(kind, targetProfile, config.filter);
|
|
453
|
+
const searchOptions = remoteSearchOptions(config);
|
|
454
|
+
const pageSize = config.pageSize ?? config.limit;
|
|
455
|
+
const sourceOptions = {
|
|
456
|
+
...(config.limit ? { limit: config.limit, match_count: config.limit } : {}),
|
|
457
|
+
...(pageSize ? { page_size: pageSize } : {}),
|
|
458
|
+
...(config.dataSource ? { data_source: config.dataSource } : {}),
|
|
459
|
+
...(searchOptions ?? {}),
|
|
460
|
+
};
|
|
267
461
|
const body = {
|
|
268
462
|
query,
|
|
269
463
|
...(filter ? { filter } : {}),
|
|
270
|
-
...(config.limit ? {
|
|
464
|
+
...(config.limit ? { match_count: config.limit } : {}),
|
|
465
|
+
...(pageSize ? { page_size: pageSize } : {}),
|
|
466
|
+
...(config.dataSource ? { data_source: config.dataSource } : {}),
|
|
467
|
+
...(searchOptions ?? {}),
|
|
271
468
|
};
|
|
272
469
|
const headers = {
|
|
273
470
|
Authorization: `Bearer ${session.accessToken}`,
|
|
@@ -295,6 +492,7 @@ async function readRemoteCandidateSource(kind, targetProfile, config, options) {
|
|
|
295
492
|
endpoint,
|
|
296
493
|
query,
|
|
297
494
|
filter,
|
|
495
|
+
options: Object.keys(sourceOptions).length > 0 ? sourceOptions : null,
|
|
298
496
|
},
|
|
299
497
|
};
|
|
300
498
|
}
|
|
@@ -414,7 +612,7 @@ function collectText(value, output = []) {
|
|
|
414
612
|
output.push(textValue(value['#text']));
|
|
415
613
|
}
|
|
416
614
|
for (const [key, entry] of Object.entries(value)) {
|
|
417
|
-
if (key === '#text') {
|
|
615
|
+
if (key === '#text' || key.startsWith('@')) {
|
|
418
616
|
continue;
|
|
419
617
|
}
|
|
420
618
|
collectText(entry, output);
|
|
@@ -452,9 +650,50 @@ function uniqueTexts(values) {
|
|
|
452
650
|
}
|
|
453
651
|
return [...normalized.values()].sort((a, b) => normalizeText(a).localeCompare(normalizeText(b)));
|
|
454
652
|
}
|
|
653
|
+
function uniqueTextsInOrder(values) {
|
|
654
|
+
const normalized = new Map();
|
|
655
|
+
for (const value of values) {
|
|
656
|
+
for (const text of collectText(value)) {
|
|
657
|
+
const key = normalizeText(text);
|
|
658
|
+
if (key && !normalized.has(key)) {
|
|
659
|
+
normalized.set(key, text.trim());
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return [...normalized.values()];
|
|
664
|
+
}
|
|
455
665
|
function firstUniqueText(...values) {
|
|
456
666
|
return uniqueTexts(values)[0] ?? null;
|
|
457
667
|
}
|
|
668
|
+
function firstTextInOrder(...values) {
|
|
669
|
+
return uniqueTextsInOrder(values)[0] ?? null;
|
|
670
|
+
}
|
|
671
|
+
function pathValue(value, keys) {
|
|
672
|
+
let current = value;
|
|
673
|
+
for (const key of keys) {
|
|
674
|
+
if (!isRecord(current)) {
|
|
675
|
+
return undefined;
|
|
676
|
+
}
|
|
677
|
+
current = current[key];
|
|
678
|
+
}
|
|
679
|
+
return current;
|
|
680
|
+
}
|
|
681
|
+
function textAtPath(value, keys) {
|
|
682
|
+
return firstTextInOrder(pathValue(value, keys));
|
|
683
|
+
}
|
|
684
|
+
function textsAtPath(value, keys) {
|
|
685
|
+
return uniqueTextsInOrder([pathValue(value, keys)]);
|
|
686
|
+
}
|
|
687
|
+
function recordsFromValue(value) {
|
|
688
|
+
const values = Array.isArray(value) ? value : [value];
|
|
689
|
+
return values.filter(isRecord);
|
|
690
|
+
}
|
|
691
|
+
function meaningfulTexts(values) {
|
|
692
|
+
return values.filter((value) => !isRemoteQueryNoiseText(value));
|
|
693
|
+
}
|
|
694
|
+
function meaningfulText(value) {
|
|
695
|
+
return value && !isRemoteQueryNoiseText(value) ? value : null;
|
|
696
|
+
}
|
|
458
697
|
function fieldFromKeys(row, payload, keys) {
|
|
459
698
|
const wanted = new Set(keys.map(normalizeKey));
|
|
460
699
|
return firstUniqueText(...collectValuesByKey(row, wanted), ...collectValuesByKey(payload, wanted));
|
|
@@ -500,29 +739,29 @@ function collectExchangeLikeRecords(value, output = []) {
|
|
|
500
739
|
return output;
|
|
501
740
|
}
|
|
502
741
|
function exchangeRecordSignature(record) {
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
'outputGroup'
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
742
|
+
const flowReference = isRecord(record.referenceToFlowDataSet)
|
|
743
|
+
? record.referenceToFlowDataSet
|
|
744
|
+
: null;
|
|
745
|
+
const flowId = firstTextInOrder(flowReference?.['@refObjectId'], record['@refObjectId'], record.refObjectId, record.flow_id, record.flowId, record.flow_uuid, record.flowUuid) ??
|
|
746
|
+
fieldFromKeys(record, record, [
|
|
747
|
+
'@refObjectId',
|
|
748
|
+
'refObjectId',
|
|
749
|
+
'flow_id',
|
|
750
|
+
'flowId',
|
|
751
|
+
'flow_uuid',
|
|
752
|
+
'flowUuid',
|
|
753
|
+
]);
|
|
754
|
+
const direction = firstTextInOrder(record.exchangeDirection, record.direction, record.inputGroup, record.outputGroup) ??
|
|
755
|
+
fieldFromKeys(record, record, ['exchangeDirection', 'direction', 'inputGroup', 'outputGroup']);
|
|
756
|
+
const amount = firstTextInOrder(record.meanAmount, record.mean_amount, record.resultingAmount, record.resulting_amount, record.amount, record.meanValue) ??
|
|
757
|
+
fieldFromKeys(record, record, [
|
|
758
|
+
'meanAmount',
|
|
759
|
+
'mean_amount',
|
|
760
|
+
'resultingAmount',
|
|
761
|
+
'resulting_amount',
|
|
762
|
+
'amount',
|
|
763
|
+
'meanValue',
|
|
764
|
+
]);
|
|
526
765
|
const normalizedFlowId = flowId ? normalizeText(flowId) : '';
|
|
527
766
|
if (!normalizedFlowId) {
|
|
528
767
|
return null;
|
|
@@ -544,60 +783,196 @@ function profileDatasetIdentity(row, payload, kind) {
|
|
|
544
783
|
version: firstUniqueText(row.version, row.dataset_version, identity.version) ?? identity.version,
|
|
545
784
|
};
|
|
546
785
|
}
|
|
786
|
+
function tidasRoot(payload, kind) {
|
|
787
|
+
if (kind === 'process' && isRecord(payload.processDataSet)) {
|
|
788
|
+
return payload.processDataSet;
|
|
789
|
+
}
|
|
790
|
+
if (kind === 'flow' && isRecord(payload.flowDataSet)) {
|
|
791
|
+
return payload.flowDataSet;
|
|
792
|
+
}
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
function processCanonicalNames(root) {
|
|
796
|
+
const nameRoot = pathValue(root, ['processInformation', 'dataSetInformation', 'name']);
|
|
797
|
+
return meaningfulTexts(uniqueTextsInOrder([
|
|
798
|
+
pathValue(nameRoot, ['baseName']),
|
|
799
|
+
pathValue(nameRoot, ['treatmentStandardsRoutes']),
|
|
800
|
+
pathValue(nameRoot, ['mixAndLocationTypes']),
|
|
801
|
+
pathValue(nameRoot, ['functionalUnitFlowProperties']),
|
|
802
|
+
]));
|
|
803
|
+
}
|
|
804
|
+
function flowCanonicalNames(root) {
|
|
805
|
+
const nameRoot = pathValue(root, ['flowInformation', 'dataSetInformation', 'name']);
|
|
806
|
+
return meaningfulTexts(uniqueTextsInOrder([
|
|
807
|
+
pathValue(nameRoot, ['baseName']),
|
|
808
|
+
pathValue(nameRoot, ['treatmentStandardsRoutes']),
|
|
809
|
+
pathValue(nameRoot, ['mixAndLocationTypes']),
|
|
810
|
+
]));
|
|
811
|
+
}
|
|
812
|
+
function classificationTexts(root, informationPath) {
|
|
813
|
+
const classes = recordsFromValue(pathValue(root, [
|
|
814
|
+
...informationPath,
|
|
815
|
+
'dataSetInformation',
|
|
816
|
+
'classificationInformation',
|
|
817
|
+
'common:classification',
|
|
818
|
+
'common:class',
|
|
819
|
+
]));
|
|
820
|
+
return meaningfulTexts(uniqueTextsInOrder(classes.flatMap((entry) => [entry['@classId'], entry['#text']])));
|
|
821
|
+
}
|
|
822
|
+
function elementaryFlowCategoryTexts(root) {
|
|
823
|
+
const categories = recordsFromValue(pathValue(root, [
|
|
824
|
+
'flowInformation',
|
|
825
|
+
'dataSetInformation',
|
|
826
|
+
'classificationInformation',
|
|
827
|
+
'common:elementaryFlowCategorization',
|
|
828
|
+
'common:category',
|
|
829
|
+
]));
|
|
830
|
+
return meaningfulTexts(uniqueTextsInOrder(categories.flatMap((entry) => [entry['@level'], entry['#text']]))).filter((entry) => !/^\d+$/u.test(entry));
|
|
831
|
+
}
|
|
832
|
+
function processReferenceExchange(root) {
|
|
833
|
+
const referenceFlowInternalIds = textsAtPath(root, [
|
|
834
|
+
'processInformation',
|
|
835
|
+
'quantitativeReference',
|
|
836
|
+
'referenceToReferenceFlow',
|
|
837
|
+
]);
|
|
838
|
+
const internalIdSet = new Set(referenceFlowInternalIds.map(normalizeText));
|
|
839
|
+
const exchanges = recordsFromValue(pathValue(root, ['exchanges', 'exchange']));
|
|
840
|
+
const referenceExchanges = internalIdSet.size > 0
|
|
841
|
+
? exchanges.filter((exchange) => internalIdSet.has(normalizeText(textValue(exchange['@dataSetInternalID']) ?? '')))
|
|
842
|
+
: [];
|
|
843
|
+
return referenceExchanges[0] ?? exchanges[0] ?? null;
|
|
844
|
+
}
|
|
845
|
+
function processReferenceFlowValues(root) {
|
|
846
|
+
const selected = processReferenceExchange(root);
|
|
847
|
+
if (!selected) {
|
|
848
|
+
return { ids: [], names: [] };
|
|
849
|
+
}
|
|
850
|
+
const reference = isRecord(selected.referenceToFlowDataSet)
|
|
851
|
+
? selected.referenceToFlowDataSet
|
|
852
|
+
: {};
|
|
853
|
+
return {
|
|
854
|
+
ids: meaningfulTexts(uniqueTextsInOrder([reference['@refObjectId'], selected['@refObjectId']])),
|
|
855
|
+
names: meaningfulTexts(uniqueTextsInOrder([reference['common:shortDescription'], reference.shortDescription])),
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
function processCanonicalFields(root) {
|
|
859
|
+
const names = processCanonicalNames(root);
|
|
860
|
+
const referenceFlow = processReferenceFlowValues(root);
|
|
861
|
+
return {
|
|
862
|
+
names,
|
|
863
|
+
referenceFlowIds: referenceFlow.ids,
|
|
864
|
+
referenceFlowNames: referenceFlow.names,
|
|
865
|
+
quantitativeReference: meaningfulText(textAtPath(root, ['processInformation', 'quantitativeReference', 'functionalUnitOrOther'])) ??
|
|
866
|
+
meaningfulText(textAtPath(root, [
|
|
867
|
+
'processInformation',
|
|
868
|
+
'quantitativeReference',
|
|
869
|
+
'referenceToReferenceFlow',
|
|
870
|
+
])),
|
|
871
|
+
geography: meaningfulText(textAtPath(root, [
|
|
872
|
+
'processInformation',
|
|
873
|
+
'geography',
|
|
874
|
+
'locationOfOperationSupplyOrProduction',
|
|
875
|
+
'@location',
|
|
876
|
+
])),
|
|
877
|
+
time: meaningfulText(firstTextInOrder(pathValue(root, ['processInformation', 'time', 'common:referenceYear']), pathValue(root, ['processInformation', 'time', 'common:timeRepresentativenessDescription']))),
|
|
878
|
+
technologyRoute: meaningfulText(textAtPath(root, [
|
|
879
|
+
'processInformation',
|
|
880
|
+
'technology',
|
|
881
|
+
'technologyDescriptionAndIncludedProcesses',
|
|
882
|
+
])),
|
|
883
|
+
systemBoundary: meaningfulText(textAtPath(root, ['processInformation', 'technology', 'includedProcesses'])),
|
|
884
|
+
categories: classificationTexts(root, ['processInformation']),
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function flowCanonicalFields(root) {
|
|
888
|
+
const flowProperties = recordsFromValue(pathValue(root, ['flowProperties', 'flowProperty']));
|
|
889
|
+
const typeOfDataset = meaningfulText(textAtPath(root, ['modellingAndValidation', 'LCIMethod', 'typeOfDataSet']));
|
|
890
|
+
const elementaryCategories = typeOfDataset === 'Elementary flow' ? elementaryFlowCategoryTexts(root) : [];
|
|
891
|
+
return {
|
|
892
|
+
names: flowCanonicalNames(root),
|
|
893
|
+
typeOfDataset,
|
|
894
|
+
cas: meaningfulText(textAtPath(root, ['flowInformation', 'dataSetInformation', 'CASNumber'])),
|
|
895
|
+
flowProperty: meaningfulText(firstTextInOrder(...flowProperties.map((property) => pathValue(property, ['referenceToFlowPropertyDataSet', 'common:shortDescription'])))),
|
|
896
|
+
referenceUnit: null,
|
|
897
|
+
categories: elementaryCategories.length
|
|
898
|
+
? elementaryCategories
|
|
899
|
+
: classificationTexts(root, ['flowInformation']),
|
|
900
|
+
geography: meaningfulText(textAtPath(root, ['flowInformation', 'dataSetInformation', 'name', 'mixAndLocationTypes'])),
|
|
901
|
+
};
|
|
902
|
+
}
|
|
547
903
|
function processProfile(row) {
|
|
548
904
|
const payload = unwrapDatasetPayload(row);
|
|
549
905
|
const identity = profileDatasetIdentity(row, payload, 'process');
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
906
|
+
const canonicalRoot = tidasRoot(payload, 'process');
|
|
907
|
+
const canonical = canonicalRoot ? processCanonicalFields(canonicalRoot) : null;
|
|
908
|
+
const names = canonical?.names.length
|
|
909
|
+
? canonical.names
|
|
910
|
+
: textListFromKeys(row, payload, [
|
|
911
|
+
'name',
|
|
912
|
+
'baseName',
|
|
913
|
+
'shortDescription',
|
|
914
|
+
'name_en',
|
|
915
|
+
'name_zh',
|
|
916
|
+
]);
|
|
917
|
+
const referenceFlowIds = canonical?.referenceFlowIds.length
|
|
918
|
+
? canonical.referenceFlowIds
|
|
919
|
+
: textListFromKeys(row, payload, [
|
|
920
|
+
'reference_flow_id',
|
|
921
|
+
'referenceFlowId',
|
|
922
|
+
'reference_product_flow',
|
|
923
|
+
'referenceProductFlow',
|
|
924
|
+
'referenceToReferenceFlow',
|
|
925
|
+
'referenceToFlowDataSet',
|
|
926
|
+
'@refObjectId',
|
|
927
|
+
'refObjectId',
|
|
928
|
+
]);
|
|
929
|
+
const referenceFlowNames = canonical?.referenceFlowNames.length
|
|
930
|
+
? canonical.referenceFlowNames
|
|
931
|
+
: textListFromKeys(row, payload, [
|
|
932
|
+
'reference_flow_name',
|
|
933
|
+
'referenceFlowName',
|
|
934
|
+
'reference_product_flow_name',
|
|
935
|
+
'referenceProductFlowName',
|
|
936
|
+
]);
|
|
567
937
|
const operation = fieldFromKeys(row, payload, ['operation', 'process_operation']);
|
|
568
|
-
const quantitativeReference =
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
938
|
+
const quantitativeReference = canonicalRoot && canonical
|
|
939
|
+
? canonical.quantitativeReference
|
|
940
|
+
: fieldFromKeys(row, payload, [
|
|
941
|
+
'quantitative_reference',
|
|
942
|
+
'quantitativeReference',
|
|
943
|
+
'qref',
|
|
944
|
+
'referenceToReferenceFlow',
|
|
945
|
+
]);
|
|
946
|
+
const geography = canonicalRoot && canonical
|
|
947
|
+
? canonical.geography
|
|
948
|
+
: fieldFromKeys(row, payload, [
|
|
949
|
+
'geography',
|
|
950
|
+
'location',
|
|
951
|
+
'locationOfOperationSupplyOrProduction',
|
|
952
|
+
]);
|
|
953
|
+
const time = canonicalRoot && canonical
|
|
954
|
+
? canonical.time
|
|
955
|
+
: fieldFromKeys(row, payload, ['time', 'reference_year', 'referenceYear', 'timePeriod']);
|
|
956
|
+
const technologyRoute = canonicalRoot && canonical
|
|
957
|
+
? canonical.technologyRoute
|
|
958
|
+
: fieldFromKeys(row, payload, [
|
|
959
|
+
'technology_route',
|
|
960
|
+
'technologyRoute',
|
|
961
|
+
'technology',
|
|
962
|
+
'treatmentStandardsRoutes',
|
|
963
|
+
]);
|
|
964
|
+
const systemBoundary = canonicalRoot && canonical
|
|
965
|
+
? canonical.systemBoundary
|
|
966
|
+
: fieldFromKeys(row, payload, ['system_boundary', 'systemBoundary', 'boundary']);
|
|
596
967
|
const providerRole = fieldFromKeys(row, payload, ['provider_role', 'providerRole']);
|
|
968
|
+
const categories = canonical?.categories.length
|
|
969
|
+
? canonical.categories
|
|
970
|
+
: textListFromKeys(row, payload, ['category', 'class', 'classification']);
|
|
597
971
|
const exchangeSignature = processExchangeSignature(row, payload);
|
|
598
972
|
const keyParts = [
|
|
599
973
|
...normalizedList(names).slice(0, 4),
|
|
600
974
|
...normalizedList(referenceFlowIds).slice(0, 4),
|
|
975
|
+
...normalizedList(referenceFlowNames).slice(0, 2),
|
|
601
976
|
normalizeText(operation ?? ''),
|
|
602
977
|
normalizeText(quantitativeReference ?? ''),
|
|
603
978
|
normalizeText(geography ?? ''),
|
|
@@ -605,6 +980,7 @@ function processProfile(row) {
|
|
|
605
980
|
normalizeText(technologyRoute ?? ''),
|
|
606
981
|
normalizeText(systemBoundary ?? ''),
|
|
607
982
|
normalizeText(providerRole ?? ''),
|
|
983
|
+
...normalizedList(categories).slice(0, 4),
|
|
608
984
|
exchangeSignature.join(','),
|
|
609
985
|
].filter(Boolean);
|
|
610
986
|
return {
|
|
@@ -617,6 +993,7 @@ function processProfile(row) {
|
|
|
617
993
|
exchange_signature: exchangeSignature,
|
|
618
994
|
fields: {
|
|
619
995
|
reference_flow_ids: referenceFlowIds,
|
|
996
|
+
reference_flow_names: referenceFlowNames,
|
|
620
997
|
operation,
|
|
621
998
|
quantitative_reference: quantitativeReference,
|
|
622
999
|
geography,
|
|
@@ -624,42 +1001,49 @@ function processProfile(row) {
|
|
|
624
1001
|
technology_route: technologyRoute,
|
|
625
1002
|
system_boundary: systemBoundary,
|
|
626
1003
|
provider_role: providerRole,
|
|
1004
|
+
categories,
|
|
627
1005
|
},
|
|
628
1006
|
};
|
|
629
1007
|
}
|
|
630
1008
|
function flowProfile(row) {
|
|
631
1009
|
const payload = unwrapDatasetPayload(row);
|
|
632
1010
|
const identity = profileDatasetIdentity(row, payload, 'flow');
|
|
633
|
-
const
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
'
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1011
|
+
const canonicalRoot = tidasRoot(payload, 'flow');
|
|
1012
|
+
const canonical = canonicalRoot ? flowCanonicalFields(canonicalRoot) : null;
|
|
1013
|
+
const names = canonical?.names.length
|
|
1014
|
+
? canonical.names
|
|
1015
|
+
: textListFromKeys(row, payload, [
|
|
1016
|
+
'name',
|
|
1017
|
+
'baseName',
|
|
1018
|
+
'shortDescription',
|
|
1019
|
+
'name_en',
|
|
1020
|
+
'name_zh',
|
|
1021
|
+
'synonyms',
|
|
1022
|
+
]);
|
|
1023
|
+
const typeOfDataset = canonicalRoot && canonical
|
|
1024
|
+
? canonical.typeOfDataset
|
|
1025
|
+
: fieldFromKeys(row, payload, ['type_of_dataset', 'typeOfDataSet', 'flow_type', 'flowType']);
|
|
1026
|
+
const cas = canonicalRoot && canonical
|
|
1027
|
+
? canonical.cas
|
|
1028
|
+
: fieldFromKeys(row, payload, ['CASNumber', 'cas_number', 'cas']);
|
|
1029
|
+
const flowProperty = canonicalRoot && canonical
|
|
1030
|
+
? canonical.flowProperty
|
|
1031
|
+
: fieldFromKeys(row, payload, [
|
|
1032
|
+
'flow_property',
|
|
1033
|
+
'flowProperty',
|
|
1034
|
+
'referenceToFlowPropertyDataSet',
|
|
1035
|
+
'reference_property',
|
|
1036
|
+
'referenceProperty',
|
|
1037
|
+
]);
|
|
1038
|
+
const referenceUnit = canonicalRoot && canonical
|
|
1039
|
+
? canonical.referenceUnit
|
|
1040
|
+
: fieldFromKeys(row, payload, ['reference_unit', 'referenceUnit', 'unit']);
|
|
1041
|
+
const categories = canonical?.categories.length
|
|
1042
|
+
? canonical.categories
|
|
1043
|
+
: textListFromKeys(row, payload, ['category', 'class', 'classification', 'compartment']);
|
|
1044
|
+
const geography = canonicalRoot && canonical
|
|
1045
|
+
? canonical.geography
|
|
1046
|
+
: fieldFromKeys(row, payload, ['geography', 'location', 'market', 'mixAndLocationTypes']);
|
|
663
1047
|
const keyParts = [
|
|
664
1048
|
normalizeText(typeOfDataset ?? ''),
|
|
665
1049
|
...normalizedList(names).slice(0, 4),
|
|
@@ -687,6 +1071,98 @@ function flowProfile(row) {
|
|
|
687
1071
|
},
|
|
688
1072
|
};
|
|
689
1073
|
}
|
|
1074
|
+
const PROFILE_ARRAY_FIELDS = new Set(['categories', 'reference_flow_ids', 'reference_flow_names']);
|
|
1075
|
+
const PROFILE_HINT_KEYS = {
|
|
1076
|
+
flow: {
|
|
1077
|
+
type_of_dataset: ['type_of_dataset', 'typeOfDataSet', 'flow_type', 'flowType'],
|
|
1078
|
+
cas: ['cas', 'CAS', 'CASNumber', 'cas_number'],
|
|
1079
|
+
flow_property: ['flow_property', 'flowProperty', 'reference_property', 'referenceProperty'],
|
|
1080
|
+
reference_unit: ['reference_unit', 'referenceUnit', 'unit'],
|
|
1081
|
+
categories: ['categories', 'category', 'classification', 'source_categories'],
|
|
1082
|
+
geography: ['geography', 'location', 'market'],
|
|
1083
|
+
},
|
|
1084
|
+
process: {
|
|
1085
|
+
reference_flow_ids: ['reference_flow_ids', 'referenceFlowIds', 'reference_flow_id'],
|
|
1086
|
+
reference_flow_names: ['reference_flow_names', 'referenceFlowNames', 'reference_flow_name'],
|
|
1087
|
+
operation: ['operation', 'process_operation'],
|
|
1088
|
+
quantitative_reference: ['quantitative_reference', 'quantitativeReference', 'qref'],
|
|
1089
|
+
geography: ['geography', 'location'],
|
|
1090
|
+
time: ['time', 'reference_year', 'referenceYear', 'timePeriod'],
|
|
1091
|
+
technology_route: ['technology_route', 'technologyRoute', 'technology'],
|
|
1092
|
+
system_boundary: ['system_boundary', 'systemBoundary', 'boundary'],
|
|
1093
|
+
provider_role: ['provider_role', 'providerRole'],
|
|
1094
|
+
categories: ['categories', 'category', 'classification', 'source_categories'],
|
|
1095
|
+
},
|
|
1096
|
+
};
|
|
1097
|
+
function hintTextValues(value, limit = 8) {
|
|
1098
|
+
return uniqueTextsInOrder([value])
|
|
1099
|
+
.map((entry) => entry.trim())
|
|
1100
|
+
.filter((entry) => !isRemoteQueryNoiseText(entry))
|
|
1101
|
+
.slice(0, limit);
|
|
1102
|
+
}
|
|
1103
|
+
function hintValuesByKeys(hints, keys, limit = 8) {
|
|
1104
|
+
const values = [];
|
|
1105
|
+
for (const key of keys) {
|
|
1106
|
+
if (hints[key] !== undefined) {
|
|
1107
|
+
values.push(hints[key]);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return hintTextValues(values, limit);
|
|
1111
|
+
}
|
|
1112
|
+
function identityKeyFromProfile(kind, names, fields, exchangeSignature) {
|
|
1113
|
+
if (kind === 'process') {
|
|
1114
|
+
return [
|
|
1115
|
+
...normalizedList(names).slice(0, 4),
|
|
1116
|
+
...normalizedList(normalizedFieldValues(fields.reference_flow_ids)).slice(0, 4),
|
|
1117
|
+
...normalizedList(normalizedFieldValues(fields.reference_flow_names)).slice(0, 2),
|
|
1118
|
+
normalizeText(typeof fields.operation === 'string' ? fields.operation : ''),
|
|
1119
|
+
normalizeText(typeof fields.quantitative_reference === 'string' ? fields.quantitative_reference : ''),
|
|
1120
|
+
normalizeText(typeof fields.geography === 'string' ? fields.geography : ''),
|
|
1121
|
+
normalizeText(typeof fields.time === 'string' ? fields.time : ''),
|
|
1122
|
+
normalizeText(typeof fields.technology_route === 'string' ? fields.technology_route : ''),
|
|
1123
|
+
normalizeText(typeof fields.system_boundary === 'string' ? fields.system_boundary : ''),
|
|
1124
|
+
normalizeText(typeof fields.provider_role === 'string' ? fields.provider_role : ''),
|
|
1125
|
+
...normalizedList(normalizedFieldValues(fields.categories)).slice(0, 4),
|
|
1126
|
+
exchangeSignature.join(','),
|
|
1127
|
+
]
|
|
1128
|
+
.filter(Boolean)
|
|
1129
|
+
.join('|');
|
|
1130
|
+
}
|
|
1131
|
+
return [
|
|
1132
|
+
normalizeText(typeof fields.type_of_dataset === 'string' ? fields.type_of_dataset : ''),
|
|
1133
|
+
...normalizedList(names).slice(0, 4),
|
|
1134
|
+
normalizeText(typeof fields.cas === 'string' ? fields.cas : ''),
|
|
1135
|
+
normalizeText(typeof fields.flow_property === 'string' ? fields.flow_property : ''),
|
|
1136
|
+
normalizeText(typeof fields.reference_unit === 'string' ? fields.reference_unit : ''),
|
|
1137
|
+
...normalizedList(normalizedFieldValues(fields.categories)).slice(0, 4),
|
|
1138
|
+
normalizeText(typeof fields.geography === 'string' ? fields.geography : ''),
|
|
1139
|
+
]
|
|
1140
|
+
.filter(Boolean)
|
|
1141
|
+
.join('|');
|
|
1142
|
+
}
|
|
1143
|
+
function applyIdentityProfileHints(profile, hints, kind) {
|
|
1144
|
+
if (!hints) {
|
|
1145
|
+
return profile;
|
|
1146
|
+
}
|
|
1147
|
+
const hintedNames = hintValuesByKeys(hints, ['names', 'name', 'name_en', 'name_zh'], 6);
|
|
1148
|
+
const names = hintedNames.length > 0 ? hintedNames : profile.names;
|
|
1149
|
+
const fields = { ...profile.fields };
|
|
1150
|
+
const fieldHints = PROFILE_HINT_KEYS[kind];
|
|
1151
|
+
for (const [field, keys] of Object.entries(fieldHints)) {
|
|
1152
|
+
const values = hintValuesByKeys(hints, keys, PROFILE_ARRAY_FIELDS.has(field) ? 12 : 4);
|
|
1153
|
+
if (values.length === 0) {
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
fields[field] = PROFILE_ARRAY_FIELDS.has(field) ? values : values[0];
|
|
1157
|
+
}
|
|
1158
|
+
return {
|
|
1159
|
+
...profile,
|
|
1160
|
+
names,
|
|
1161
|
+
normalized_names: normalizedList(names),
|
|
1162
|
+
fields,
|
|
1163
|
+
identity_key: identityKeyFromProfile(kind, names, fields, profile.exchange_signature),
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
690
1166
|
function profileForKind(row, kind) {
|
|
691
1167
|
return kind === 'process' ? processProfile(row) : flowProfile(row);
|
|
692
1168
|
}
|
|
@@ -694,6 +1170,116 @@ function intersects(left, right) {
|
|
|
694
1170
|
const rightSet = new Set(right);
|
|
695
1171
|
return left.some((entry) => rightSet.has(entry));
|
|
696
1172
|
}
|
|
1173
|
+
function normalizedNamePhrase(names) {
|
|
1174
|
+
return normalizeText(names.join(' '));
|
|
1175
|
+
}
|
|
1176
|
+
const FLOW_NAME_EQUIVALENTS = new Map([
|
|
1177
|
+
['dinitrogen monoxide', 'nitrous oxide'],
|
|
1178
|
+
['ethene', 'ethylene'],
|
|
1179
|
+
['ethylene', 'ethylene'],
|
|
1180
|
+
['heat waste', 'waste heat'],
|
|
1181
|
+
['nitrous oxide', 'nitrous oxide'],
|
|
1182
|
+
['pah polycyclic aromatic hydrocarbons', 'polycyclic aromatic hydrocarbons'],
|
|
1183
|
+
['polycyclic aromatic hydrocarbons', 'polycyclic aromatic hydrocarbons'],
|
|
1184
|
+
['waste heat', 'waste heat'],
|
|
1185
|
+
]);
|
|
1186
|
+
function normalizeCas(value) {
|
|
1187
|
+
return value.replace(/\D+/gu, '').replace(/^0+/u, '');
|
|
1188
|
+
}
|
|
1189
|
+
function normalizeFlowNameVariant(value) {
|
|
1190
|
+
const normalized = normalizeText(value).replace(/\bsulphur\b/gu, 'sulfur');
|
|
1191
|
+
return FLOW_NAME_EQUIVALENTS.get(normalized) ?? normalized;
|
|
1192
|
+
}
|
|
1193
|
+
function expandedFlowNameVariants(value) {
|
|
1194
|
+
const normalized = normalizeFlowNameVariant(value);
|
|
1195
|
+
const variants = [normalized];
|
|
1196
|
+
const transformation = normalized.match(/^transformation\s+(to|from)\s+(.+)$/u);
|
|
1197
|
+
if (transformation?.[1] && transformation[2]) {
|
|
1198
|
+
variants.push(`${transformation[1]} ${transformation[2]}`);
|
|
1199
|
+
}
|
|
1200
|
+
const occupation = normalized.match(/^occupation\s+(.+)$/u);
|
|
1201
|
+
if (occupation?.[1]) {
|
|
1202
|
+
variants.push(`occupation ${occupation[1]}`);
|
|
1203
|
+
}
|
|
1204
|
+
return variants;
|
|
1205
|
+
}
|
|
1206
|
+
function flowNameVariants(names) {
|
|
1207
|
+
return normalizedList(names).flatMap(expandedFlowNameVariants).filter(Boolean);
|
|
1208
|
+
}
|
|
1209
|
+
function nameTokens(names) {
|
|
1210
|
+
return new Set(normalizedNamePhrase(names)
|
|
1211
|
+
.split(' ')
|
|
1212
|
+
.map((entry) => entry.trim())
|
|
1213
|
+
.filter((entry) => entry.length >= 2));
|
|
1214
|
+
}
|
|
1215
|
+
function tokenOverlapRatio(left, right) {
|
|
1216
|
+
if (left.size === 0 || right.size === 0) {
|
|
1217
|
+
return 0;
|
|
1218
|
+
}
|
|
1219
|
+
let overlap = 0;
|
|
1220
|
+
for (const token of left) {
|
|
1221
|
+
if (right.has(token)) {
|
|
1222
|
+
overlap += 1;
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return overlap / Math.max(left.size, right.size);
|
|
1226
|
+
}
|
|
1227
|
+
function tokenCoverageRatio(left, right) {
|
|
1228
|
+
if (left.size === 0 || right.size === 0) {
|
|
1229
|
+
return 0;
|
|
1230
|
+
}
|
|
1231
|
+
let overlap = 0;
|
|
1232
|
+
for (const token of left) {
|
|
1233
|
+
if (right.has(token)) {
|
|
1234
|
+
overlap += 1;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return overlap / left.size;
|
|
1238
|
+
}
|
|
1239
|
+
function coversLongTargetTokens(left, right) {
|
|
1240
|
+
return [...left].filter((token) => token.length >= 5).every((token) => right.has(token));
|
|
1241
|
+
}
|
|
1242
|
+
function hasSimilarNamePhrase(target, candidate) {
|
|
1243
|
+
const targetPhrase = normalizedNamePhrase(target.names);
|
|
1244
|
+
const candidatePhrase = normalizedNamePhrase(candidate.names);
|
|
1245
|
+
if (targetPhrase.length >= 8 && candidatePhrase.length >= 8) {
|
|
1246
|
+
if (targetPhrase.includes(candidatePhrase) || candidatePhrase.includes(targetPhrase)) {
|
|
1247
|
+
return true;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
const targetTokens = nameTokens(target.names);
|
|
1251
|
+
const candidateTokens = nameTokens(candidate.names);
|
|
1252
|
+
const coversTargetQualifiers = coversLongTargetTokens(targetTokens, candidateTokens);
|
|
1253
|
+
return ((coversTargetQualifiers && tokenOverlapRatio(targetTokens, candidateTokens) >= 0.66) ||
|
|
1254
|
+
(targetTokens.size >= 3 &&
|
|
1255
|
+
tokenCoverageRatio(targetTokens, candidateTokens) >= 0.8 &&
|
|
1256
|
+
coversTargetQualifiers));
|
|
1257
|
+
}
|
|
1258
|
+
function hasStrongEquivalentFlowName(target, candidate) {
|
|
1259
|
+
return (intersects(target.normalized_names, candidate.normalized_names) ||
|
|
1260
|
+
intersects(flowNameVariants(target.names), flowNameVariants(candidate.names)));
|
|
1261
|
+
}
|
|
1262
|
+
function hasEquivalentFlowName(target, candidate) {
|
|
1263
|
+
return hasStrongEquivalentFlowName(target, candidate) || hasSimilarNamePhrase(target, candidate);
|
|
1264
|
+
}
|
|
1265
|
+
function isElementaryFlowProfile(profile) {
|
|
1266
|
+
return sameNonEmptyField(profile.fields.type_of_dataset, 'Elementary flow');
|
|
1267
|
+
}
|
|
1268
|
+
function hasConflictingFlowName(target, candidate) {
|
|
1269
|
+
if (!isElementaryFlowProfile(target) || !isElementaryFlowProfile(candidate)) {
|
|
1270
|
+
return false;
|
|
1271
|
+
}
|
|
1272
|
+
if (target.names.length === 0 || candidate.names.length === 0) {
|
|
1273
|
+
return false;
|
|
1274
|
+
}
|
|
1275
|
+
if (hasEquivalentFlowName(target, candidate)) {
|
|
1276
|
+
return false;
|
|
1277
|
+
}
|
|
1278
|
+
if (sameCasField(target.fields.cas, candidate.fields.cas)) {
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
return true;
|
|
1282
|
+
}
|
|
697
1283
|
function normalizedFieldValues(value) {
|
|
698
1284
|
return (Array.isArray(value) ? value : [value])
|
|
699
1285
|
.filter((entry) => typeof entry === 'string')
|
|
@@ -705,6 +1291,90 @@ function sameNonEmptyField(left, right) {
|
|
|
705
1291
|
const rightValues = normalizedFieldValues(right);
|
|
706
1292
|
return leftValues.length > 0 && rightValues.length > 0 && intersects(leftValues, rightValues);
|
|
707
1293
|
}
|
|
1294
|
+
function sameCasField(left, right) {
|
|
1295
|
+
const leftValues = (Array.isArray(left) ? left : [left])
|
|
1296
|
+
.filter((entry) => typeof entry === 'string')
|
|
1297
|
+
.map(normalizeCas)
|
|
1298
|
+
.filter(Boolean);
|
|
1299
|
+
const rightValues = (Array.isArray(right) ? right : [right])
|
|
1300
|
+
.filter((entry) => typeof entry === 'string')
|
|
1301
|
+
.map(normalizeCas)
|
|
1302
|
+
.filter(Boolean);
|
|
1303
|
+
return leftValues.length > 0 && rightValues.length > 0 && intersects(leftValues, rightValues);
|
|
1304
|
+
}
|
|
1305
|
+
function lastNormalizedValue(value) {
|
|
1306
|
+
const values = normalizedFieldValues(value);
|
|
1307
|
+
return values.length > 0 ? values[values.length - 1] : null;
|
|
1308
|
+
}
|
|
1309
|
+
function sameCategoryLeaf(left, right) {
|
|
1310
|
+
const leftLeaf = lastNormalizedValue(left);
|
|
1311
|
+
const rightLeaf = lastNormalizedValue(right);
|
|
1312
|
+
return Boolean(leftLeaf && rightLeaf && leftLeaf === rightLeaf);
|
|
1313
|
+
}
|
|
1314
|
+
function sameCategoryPath(left, right) {
|
|
1315
|
+
const leftValues = normalizedFieldValues(left);
|
|
1316
|
+
const rightValues = normalizedFieldValues(right);
|
|
1317
|
+
return (leftValues.length > 0 &&
|
|
1318
|
+
leftValues.length === rightValues.length &&
|
|
1319
|
+
leftValues.every((entry, index) => entry === rightValues[index]));
|
|
1320
|
+
}
|
|
1321
|
+
function elementaryCompartmentKey(value) {
|
|
1322
|
+
const normalized = normalizeText(value);
|
|
1323
|
+
if (!normalized) {
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1326
|
+
if (/\blow\s*pop\b/u.test(normalized) ||
|
|
1327
|
+
normalized.includes('low population') ||
|
|
1328
|
+
normalized.includes('non urban air') ||
|
|
1329
|
+
normalized.includes('high stacks')) {
|
|
1330
|
+
return 'air_non_urban_or_high_stacks';
|
|
1331
|
+
}
|
|
1332
|
+
if (/\bhigh\s*pop\b/u.test(normalized) ||
|
|
1333
|
+
normalized.includes('high population') ||
|
|
1334
|
+
normalized.includes('urban air close to ground')) {
|
|
1335
|
+
return 'air_urban_close_to_ground';
|
|
1336
|
+
}
|
|
1337
|
+
if (normalized.includes('air indoor') || normalized.includes('indoor air')) {
|
|
1338
|
+
return 'air_indoor';
|
|
1339
|
+
}
|
|
1340
|
+
if (normalized.includes('air unspecified long term')) {
|
|
1341
|
+
return 'air_unspecified_long_term';
|
|
1342
|
+
}
|
|
1343
|
+
if (normalized.includes('air unspecified')) {
|
|
1344
|
+
return 'air_unspecified';
|
|
1345
|
+
}
|
|
1346
|
+
if (normalized.includes('fresh water')) {
|
|
1347
|
+
return 'water_fresh';
|
|
1348
|
+
}
|
|
1349
|
+
if (normalized.includes('sea water')) {
|
|
1350
|
+
return 'water_sea';
|
|
1351
|
+
}
|
|
1352
|
+
if (normalized.includes('water unspecified long term')) {
|
|
1353
|
+
return 'water_unspecified_long_term';
|
|
1354
|
+
}
|
|
1355
|
+
if (normalized.includes('water unspecified')) {
|
|
1356
|
+
return 'water_unspecified';
|
|
1357
|
+
}
|
|
1358
|
+
if (normalized.includes('agricultural soil') && !normalized.includes('non agricultural soil')) {
|
|
1359
|
+
return 'soil_agricultural';
|
|
1360
|
+
}
|
|
1361
|
+
if (normalized.includes('non agricultural soil')) {
|
|
1362
|
+
return 'soil_non_agricultural';
|
|
1363
|
+
}
|
|
1364
|
+
if (normalized.includes('soil unspecified')) {
|
|
1365
|
+
return 'soil_unspecified';
|
|
1366
|
+
}
|
|
1367
|
+
return null;
|
|
1368
|
+
}
|
|
1369
|
+
function sameElementaryCompartment(left, right) {
|
|
1370
|
+
const leftKeys = normalizedFieldValues(left)
|
|
1371
|
+
.map(elementaryCompartmentKey)
|
|
1372
|
+
.filter((entry) => Boolean(entry));
|
|
1373
|
+
const rightKeys = normalizedFieldValues(right)
|
|
1374
|
+
.map(elementaryCompartmentKey)
|
|
1375
|
+
.filter((entry) => Boolean(entry));
|
|
1376
|
+
return leftKeys.length > 0 && rightKeys.length > 0 && intersects(leftKeys, rightKeys);
|
|
1377
|
+
}
|
|
708
1378
|
function sameExchangeSignature(left, right) {
|
|
709
1379
|
return left.length > 0 && right.length > 0 && left.join('|') === right.join('|');
|
|
710
1380
|
}
|
|
@@ -712,12 +1382,27 @@ function hasEquivalentFlowCore(target, candidate) {
|
|
|
712
1382
|
const hasSameType = sameNonEmptyField(target.fields.type_of_dataset, candidate.fields.type_of_dataset);
|
|
713
1383
|
const hasSameProperty = sameNonEmptyField(target.fields.flow_property, candidate.fields.flow_property);
|
|
714
1384
|
const hasSameUnit = sameNonEmptyField(target.fields.reference_unit, candidate.fields.reference_unit);
|
|
715
|
-
const hasSameCas =
|
|
1385
|
+
const hasSameCas = sameCasField(target.fields.cas, candidate.fields.cas);
|
|
716
1386
|
const hasSameCategory = sameNonEmptyField(target.fields.categories, candidate.fields.categories);
|
|
1387
|
+
const hasSameCategoryLeaf = sameCategoryLeaf(target.fields.categories, candidate.fields.categories);
|
|
1388
|
+
const hasSameCategoryPath = sameCategoryPath(target.fields.categories, candidate.fields.categories);
|
|
1389
|
+
const hasEquivalentName = hasEquivalentFlowName(target, candidate);
|
|
1390
|
+
const hasStrongEquivalentName = hasStrongEquivalentFlowName(target, candidate);
|
|
1391
|
+
const isElementary = sameNonEmptyField(target.fields.type_of_dataset, 'Elementary flow') &&
|
|
1392
|
+
sameNonEmptyField(candidate.fields.type_of_dataset, 'Elementary flow');
|
|
1393
|
+
const hasEquivalentElementaryCompartment = isElementary &&
|
|
1394
|
+
sameElementaryCompartment(target.fields.categories, candidate.fields.categories);
|
|
1395
|
+
if (isElementary) {
|
|
1396
|
+
return (hasSameType &&
|
|
1397
|
+
hasSameProperty &&
|
|
1398
|
+
hasStrongEquivalentName &&
|
|
1399
|
+
(hasSameCategoryLeaf || hasSameCategoryPath || hasEquivalentElementaryCompartment) &&
|
|
1400
|
+
(hasSameCas || !normalizedFieldValues(target.fields.cas).length));
|
|
1401
|
+
}
|
|
717
1402
|
return (hasSameType &&
|
|
718
1403
|
hasSameProperty &&
|
|
719
1404
|
hasSameUnit &&
|
|
720
|
-
|
|
1405
|
+
hasEquivalentName &&
|
|
721
1406
|
(hasSameCas || hasSameCategory));
|
|
722
1407
|
}
|
|
723
1408
|
function candidateEvaluation(target, candidate, kind, index) {
|
|
@@ -762,6 +1447,22 @@ function candidateEvaluation(target, candidate, kind, index) {
|
|
|
762
1447
|
decisionHint = 'manual_review';
|
|
763
1448
|
}
|
|
764
1449
|
}
|
|
1450
|
+
const hasEquivalentName = kind === 'flow' && !hasOverlappingName && hasStrongEquivalentFlowName(target, candidate);
|
|
1451
|
+
if (kind === 'flow' && hasEquivalentName) {
|
|
1452
|
+
matchScore += 18;
|
|
1453
|
+
matchReasons.push('equivalent_flow_name');
|
|
1454
|
+
if (!decisionHint) {
|
|
1455
|
+
decisionHint = 'manual_review';
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
const hasSimilarName = !hasOverlappingName && !hasEquivalentName && hasSimilarNamePhrase(target, candidate);
|
|
1459
|
+
if (hasSimilarName) {
|
|
1460
|
+
matchScore += 15;
|
|
1461
|
+
matchReasons.push('similar_name_phrase');
|
|
1462
|
+
if (!decisionHint) {
|
|
1463
|
+
decisionHint = 'manual_review';
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
765
1466
|
const targetReferenceFields = Object.values(target.fields)
|
|
766
1467
|
.flat()
|
|
767
1468
|
.filter((value) => typeof value === 'string')
|
|
@@ -777,6 +1478,38 @@ function candidateEvaluation(target, candidate, kind, index) {
|
|
|
777
1478
|
matchScore += 10;
|
|
778
1479
|
matchReasons.push('overlapping_identity_field');
|
|
779
1480
|
}
|
|
1481
|
+
if (kind === 'flow') {
|
|
1482
|
+
if (sameNonEmptyField(target.fields.type_of_dataset, candidate.fields.type_of_dataset)) {
|
|
1483
|
+
matchScore += 5;
|
|
1484
|
+
matchReasons.push('same_flow_type');
|
|
1485
|
+
}
|
|
1486
|
+
if (sameNonEmptyField(target.fields.flow_property, candidate.fields.flow_property)) {
|
|
1487
|
+
matchScore += 15;
|
|
1488
|
+
matchReasons.push('same_flow_property');
|
|
1489
|
+
}
|
|
1490
|
+
if (sameNonEmptyField(target.fields.reference_unit, candidate.fields.reference_unit)) {
|
|
1491
|
+
matchScore += 10;
|
|
1492
|
+
matchReasons.push('same_reference_unit');
|
|
1493
|
+
}
|
|
1494
|
+
if (sameCasField(target.fields.cas, candidate.fields.cas)) {
|
|
1495
|
+
matchScore += 10;
|
|
1496
|
+
matchReasons.push('same_cas');
|
|
1497
|
+
}
|
|
1498
|
+
if (sameCategoryPath(target.fields.categories, candidate.fields.categories)) {
|
|
1499
|
+
matchScore += 20;
|
|
1500
|
+
matchReasons.push('same_category_path');
|
|
1501
|
+
}
|
|
1502
|
+
else if (sameCategoryLeaf(target.fields.categories, candidate.fields.categories)) {
|
|
1503
|
+
matchScore += 15;
|
|
1504
|
+
matchReasons.push('same_category_leaf');
|
|
1505
|
+
}
|
|
1506
|
+
else if (sameNonEmptyField(target.fields.type_of_dataset, 'Elementary flow') &&
|
|
1507
|
+
sameNonEmptyField(candidate.fields.type_of_dataset, 'Elementary flow') &&
|
|
1508
|
+
sameElementaryCompartment(target.fields.categories, candidate.fields.categories)) {
|
|
1509
|
+
matchScore += 18;
|
|
1510
|
+
matchReasons.push('equivalent_elementary_compartment');
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
780
1513
|
if (kind === 'process' &&
|
|
781
1514
|
decisionHint === 'manual_review' &&
|
|
782
1515
|
matchReasons.includes('same_exchange_signature') &&
|
|
@@ -792,6 +1525,10 @@ function candidateEvaluation(target, candidate, kind, index) {
|
|
|
792
1525
|
matchReasons.push('equivalent_flow_core_fields');
|
|
793
1526
|
decisionHint = 'block_duplicate';
|
|
794
1527
|
}
|
|
1528
|
+
if (kind === 'flow' && matchScore > 0 && hasConflictingFlowName(target, candidate)) {
|
|
1529
|
+
matchScore = Math.max(1, matchScore - 35);
|
|
1530
|
+
matchReasons.push('conflicting_flow_name');
|
|
1531
|
+
}
|
|
795
1532
|
const findings = [];
|
|
796
1533
|
if (decisionHint === 'block_duplicate') {
|
|
797
1534
|
findings.push({
|
|
@@ -823,6 +1560,9 @@ function candidateEvaluation(target, candidate, kind, index) {
|
|
|
823
1560
|
id: candidate.id,
|
|
824
1561
|
version: candidate.version,
|
|
825
1562
|
state_code: candidate.state_code,
|
|
1563
|
+
names: candidate.names,
|
|
1564
|
+
fields: candidate.fields,
|
|
1565
|
+
exchange_signature: candidate.exchange_signature,
|
|
826
1566
|
identity_key: candidate.identity_key,
|
|
827
1567
|
match_score: matchScore,
|
|
828
1568
|
match_reasons: matchReasons,
|
|
@@ -885,6 +1625,15 @@ function chooseDecision(evaluations, validation, kind) {
|
|
|
885
1625
|
findings,
|
|
886
1626
|
};
|
|
887
1627
|
}
|
|
1628
|
+
function sortedEvaluations(evaluations) {
|
|
1629
|
+
return [...evaluations].sort((left, right) => {
|
|
1630
|
+
const scoreDiff = right.report.match_score - left.report.match_score;
|
|
1631
|
+
if (scoreDiff !== 0) {
|
|
1632
|
+
return scoreDiff;
|
|
1633
|
+
}
|
|
1634
|
+
return left.report.index - right.report.index;
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
888
1637
|
function statusForDecision(decision, blockers) {
|
|
889
1638
|
if (blockers.length > 0 || decision === 'block_duplicate') {
|
|
890
1639
|
return 'blocked';
|
|
@@ -934,8 +1683,8 @@ function writeArtifacts(report, outDir) {
|
|
|
934
1683
|
export async function runIdentityPreflight(kind, options) {
|
|
935
1684
|
const inputPath = requiredInputPath(options.inputPath);
|
|
936
1685
|
const normalizedInput = normalizePreflightInput(options.rawInput ?? readJsonInput(inputPath), kind);
|
|
937
|
-
const targetProfile = profileForKind(normalizedInput.target, kind);
|
|
938
1686
|
const remoteCandidateSearch = mergeRemoteCandidateSearchConfig(normalizedInput.remoteCandidateSearch, options);
|
|
1687
|
+
const targetProfile = applyIdentityProfileHints(profileForKind(normalizedInput.target, kind), remoteCandidateSearch.profileHints, kind);
|
|
939
1688
|
const candidateSourceReads = [
|
|
940
1689
|
...normalizedInput.candidateInputPaths,
|
|
941
1690
|
...(options.candidateInputPaths ?? []),
|
|
@@ -976,11 +1725,13 @@ export async function runIdentityPreflight(kind, options) {
|
|
|
976
1725
|
target: {
|
|
977
1726
|
id: targetProfile.id,
|
|
978
1727
|
version: targetProfile.version,
|
|
1728
|
+
names: targetProfile.names,
|
|
1729
|
+
fields: targetProfile.fields,
|
|
979
1730
|
identity_key: targetProfile.identity_key,
|
|
980
1731
|
exchange_signature: targetProfile.exchange_signature,
|
|
981
1732
|
schema_validation: validation,
|
|
982
1733
|
},
|
|
983
|
-
candidates: evaluations.map((evaluation) => evaluation.report),
|
|
1734
|
+
candidates: sortedEvaluations(evaluations).map((evaluation) => evaluation.report),
|
|
984
1735
|
candidate_sources: candidateSources,
|
|
985
1736
|
findings: decision.findings,
|
|
986
1737
|
blockers,
|
|
@@ -1004,18 +1755,45 @@ export async function runFlowIdentityPreflight(options) {
|
|
|
1004
1755
|
return (await runIdentityPreflight('flow', options));
|
|
1005
1756
|
}
|
|
1006
1757
|
export const __testInternals = {
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
entityFactoryExports: ENTITY_FACTORY_EXPORTS,
|
|
1010
|
-
processProfile,
|
|
1011
|
-
flowProfile,
|
|
1758
|
+
appendQueryLine,
|
|
1759
|
+
applyIdentityProfileHints,
|
|
1012
1760
|
candidateEvaluation,
|
|
1013
1761
|
chooseDecision,
|
|
1014
1762
|
collectCandidateFilesFromStats,
|
|
1015
|
-
|
|
1763
|
+
compactRemoteQuery,
|
|
1016
1764
|
defaultRemoteQuery,
|
|
1765
|
+
elementaryCompartmentKey,
|
|
1766
|
+
entityFactoryExports: ENTITY_FACTORY_EXPORTS,
|
|
1767
|
+
exchangeFlowRefsFromSignature,
|
|
1768
|
+
expandedFlowNameVariants,
|
|
1769
|
+
flowNameVariants,
|
|
1770
|
+
flowProfile,
|
|
1771
|
+
hasConflictingFlowName,
|
|
1772
|
+
identityKeyFromProfile,
|
|
1773
|
+
isRemoteQueryNoiseText,
|
|
1774
|
+
mergeRemoteCandidateSearchConfig,
|
|
1775
|
+
nameTokens,
|
|
1776
|
+
nextActionForDecision,
|
|
1777
|
+
normalizeMatchThreshold,
|
|
1778
|
+
normalizeNonNegativeNumber,
|
|
1779
|
+
normalizePreflightInput,
|
|
1017
1780
|
normalizeRemoteCandidateSearch,
|
|
1781
|
+
normalizePositiveInteger,
|
|
1782
|
+
processProfile,
|
|
1783
|
+
processReferenceExchange,
|
|
1784
|
+
processReferenceFlowValues,
|
|
1785
|
+
profileForKind,
|
|
1786
|
+
queryFieldValues,
|
|
1787
|
+
readCandidateSource,
|
|
1788
|
+
remoteSearchOptions,
|
|
1018
1789
|
remoteSearchFilter,
|
|
1019
1790
|
rowsFromRemoteSearchResponse,
|
|
1791
|
+
sameCasField,
|
|
1792
|
+
sameElementaryCompartment,
|
|
1793
|
+
sameNonEmptyField,
|
|
1794
|
+
schemaForKind,
|
|
1795
|
+
statusForDecision,
|
|
1796
|
+
tokenCoverageRatio,
|
|
1797
|
+
tokenOverlapRatio,
|
|
1020
1798
|
};
|
|
1021
1799
|
//# sourceMappingURL=identity-preflight.js.map
|