@hestia-earth/ui-components 0.42.9 → 0.42.11

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.
@@ -9018,6 +9018,8 @@ const parseLookup = ({ column, termid, 'term.id': _termid, [missingLookupPrefix]
9018
9018
  });
9019
9019
  const hasValue = (value) => !!value && value !== noValue;
9020
9020
  const parseFilename = (filepath) => {
9021
+ if (!filepath)
9022
+ return '';
9021
9023
  const [filename] = filepath.split('.');
9022
9024
  const ext = termTypes.includes(filename) ? SupportedExtensions.xlsx : SupportedExtensions.csv;
9023
9025
  return fileToExt(filename, ext);
@@ -9240,9 +9242,15 @@ const jLogEntryToLog = (entry) => {
9240
9242
  const { model, orchestrator, should_run, run_required, methodTier, model_key, is_not_relevant, missingLookup, ...rest } = entry;
9241
9243
  const logs = pickFields(rest, key => modelLogKeys.includes(key));
9242
9244
  const requirements = pickFields(rest, key => !modelLogKeys.includes(key));
9243
- // the `.jlog` lists missing lookups per entry; reuse the legacy parser so they show in the shared
9244
- // "Data missing (might be optional)" section of the logs popover
9245
- const missingLookups = Array.isArray(missingLookup) ? missingLookup.map(parseLookup) : [];
9245
+ // the `.jlog` lists missing lookups per entry (`{ lookup, row, row_value, column }`); map them to the
9246
+ // shared "Data missing (might be optional)" shape, reusing the legacy filename parser
9247
+ const missingLookups = Array.isArray(missingLookup)
9248
+ ? missingLookup.map(({ lookup, row_value, column }) => ({
9249
+ filename: parseFilename(lookup),
9250
+ termId: row_value,
9251
+ column
9252
+ }))
9253
+ : [];
9246
9254
  return {
9247
9255
  // keep the untouched `.jlog` entry so the popover can show the raw JSON
9248
9256
  raw: entry,
@@ -9354,6 +9362,10 @@ const identitySpec = (type, nodeType, nodeKey) => {
9354
9362
  const properties = [...new Set([...(def.properties || []), ...parentFields])].filter((field) => !excludedIdentityFields.includes(field) && !relations.includes(field.split('.')[0]));
9355
9363
  return { def, properties, relations };
9356
9364
  };
9365
+ // some node keys distinguish blank nodes by their recorded `model` (e.g. an Indicator's `methodModel.@id`
9366
+ // is a uniqueness field): models that ran in parallel for the same term's value are then separate rows
9367
+ // (one per model), not extra model columns on a single row
9368
+ const modelIsUniquenessField = (spec) => spec.properties.includes('methodModel.@id');
9357
9369
  const isPresent = (value) => value !== undefined && value !== null && value !== '';
9358
9370
  // the joined uniqueness-field ids of each item in a relation (e.g. an emission's `inputs[].@id`)
9359
9371
  const relationItemIds = (items, fields) => Array.isArray(items)
@@ -9663,7 +9675,10 @@ const mapValueModel = (models, valueModelId, condition, update) => models.map(mo
9663
9675
  * When the value came from the original (no model, no config), the row shows no model.
9664
9676
  */
9665
9677
  // eslint-disable-next-line complexity -- orchestrates the value-model resolution (delegated to helpers)
9666
- const valueModelRuns = (entry, value, originalValue, nodeType, termId, nodeKey, config) => {
9678
+ const valueModelRuns = (entry, value, originalValue, nodeType, termId, nodeKey, config,
9679
+ // when the recorded `model` is a uniqueness field, parallel siblings are their own rows (see
9680
+ // `parallelSiblingRows`), so they are dropped from this value row's models
9681
+ splitParallelModels = false) => {
9667
9682
  const valueModelId = blankNodeModelId(value);
9668
9683
  const isRecalculated = isRecalculatedValue(value, originalValue);
9669
9684
  const hasOriginal = !!originalValue && typeof originalValue.value !== 'undefined';
@@ -9677,9 +9692,21 @@ const valueModelRuns = (entry, value, originalValue, nodeType, termId, nodeKey,
9677
9692
  const isValueLog = (log) => !isBackgroundLog(log) && !isSubRowOrchestrator(log?.orchestrator?.value, subRowValues);
9678
9693
  const valueLog = selectValueLog(logs, valueModelId, isValueLog, isRecalculated);
9679
9694
  const valueOrchestrator = valueLog?.orchestrator?.value;
9680
- const rawLogModels = valueLog
9681
- ? jLogModelRuns(logs.filter((log) => log?.orchestrator?.value === valueOrchestrator && !isBackgroundLog(log)), nodeType, termId, false)
9682
- : [];
9695
+ const matchesValueOrchestrator = (log) => log?.orchestrator?.value === valueOrchestrator && !isBackgroundLog(log);
9696
+ // keep the parallel grouping (a nested `logs` array): parallel siblings are independent, so a model
9697
+ // that did not run failed rather than being "skipped" by a sibling that ran (a higher-tier hierarchy
9698
+ // model). `jLogModelColumns` applies the pre-group success state to each parallel sibling; flatten the
9699
+ // resulting columns back to the flat run list this function returns
9700
+ const valueLogSteps = (entry?.logs || [])
9701
+ .map((step) => Array.isArray(step) ? step.filter(matchesValueOrchestrator) : matchesValueOrchestrator(step) ? step : undefined)
9702
+ .filter((step) => (Array.isArray(step) ? step.length > 0 : !!step));
9703
+ const allLogModels = valueLog ? flattenColumns(jLogModelColumns(valueLogSteps, nodeType, termId, false)) : [];
9704
+ // when models are split into their own rows, this value row keeps only its own model (parallel siblings
9705
+ // become separate rows); otherwise parallel siblings stay as fallback/sibling columns on this row
9706
+ const siblingIds = splitParallelModels
9707
+ ? new Set(parallelSiblingLogs(entry, valueModelId).map((log) => log.model))
9708
+ : new Set();
9709
+ const rawLogModels = allLogModels.filter(model => !siblingIds.has(model.methodId));
9683
9710
  // the recorded value model (the blank node `methodModel`, e.g. `hestiaAggregatedData`) can differ from
9684
9711
  // the single orchestrator model in the logs (e.g. `linkedImpactAssessment`); relabel that run to the
9685
9712
  // recorded model so it shows under the expected name, but keep the orchestrator run's logs and its
@@ -9770,6 +9797,22 @@ const allSubRows = (entry, value, originalValue, nodeType, type, termId, getTerm
9770
9797
  ];
9771
9798
  };
9772
9799
  const flattenColumns = (columns) => columns.flatMap(column => (Array.isArray(column) ? column : [column]));
9800
+ // the distinct models (other than the value's own `model`) that ran in parallel with it - a nested
9801
+ // `logs` group holding more than one model. When `model` is a uniqueness field each becomes its own row,
9802
+ // so they are split off the value row (a sibling that did not win is its own failed/separate run, not a
9803
+ // fallback column). Keeps the first log per model.
9804
+ const parallelSiblingLogs = (entry, valueModelId) => {
9805
+ const parallelGroups = (entry?.logs || []).filter((step) => Array.isArray(step) && new Set(step.filter((e) => !!e?.model).map((e) => e.model)).size > 1);
9806
+ const byModel = new Map();
9807
+ parallelGroups
9808
+ .flat()
9809
+ .filter((log) => !!log?.model && log.model !== valueModelId)
9810
+ .forEach((log) => {
9811
+ if (!byModel.has(log.model))
9812
+ byModel.set(log.model, log);
9813
+ });
9814
+ return [...byModel.values()];
9815
+ };
9773
9816
  // a `<key>-failed` entry has no value: its identity/sub-rows are empty and the models that tried are
9774
9817
  // errors; parallel models (a nested `logs` array) are kept grouped (`modelColumns`) so they stack in one
9775
9818
  // column, while `models` stays flat for the status checks
@@ -9799,7 +9842,7 @@ const valueBlankNodeContent = (entry, value, originalValue, termId, { nodeType,
9799
9842
  subRows: allSubRows(entry, value, originalValue, nodeType, type, termId, getTerm || defaultGetTerm),
9800
9843
  isRecalculated: isRecalculatedValue(value, originalValue),
9801
9844
  recalculated: value ? [value] : [],
9802
- models: valueModelRuns(entry, value, originalValue, nodeType, termId, nodeKey, config)
9845
+ models: valueModelRuns(entry, value, originalValue, nodeType, termId, nodeKey, config, modelIsUniquenessField(spec))
9803
9846
  };
9804
9847
  };
9805
9848
  const buildBlankNode = (entry, value, originalValue, index, term, isFailed, context) => {
@@ -9821,6 +9864,42 @@ const buildBlankNode = (entry, value, originalValue, index, term, isFailed, cont
9821
9864
  : valueBlankNodeContent(entry, value, originalValue, termId, context))
9822
9865
  };
9823
9866
  };
9867
+ // a parallel model that ran for a term's value but did not produce its own blank node (e.g. it failed,
9868
+ // while a sibling set the value). When `model` is a uniqueness field it is shown as its own row -
9869
+ // distinguished by its `methodModel`, with no value and a single model column carrying its status
9870
+ const buildParallelSiblingRow = (log, term, index, context) => {
9871
+ const termId = term['@id'];
9872
+ const run = toModelRun(log, jLogModelStatus(log, false, false), context.nodeType, termId);
9873
+ // carry the model as a `methodModel` Term so the row is distinguished by a methodModel link (like a
9874
+ // value row), resolved through the same `getTerm` the component uses for names; it has no `value`, so
9875
+ // it contributes nothing to the group's combined value
9876
+ const methodModel = context.getTerm?.(log.model) ?? { '@type': NodeType.Term, '@id': log.model };
9877
+ const syntheticValue = { '@type': context.type, term, methodModel };
9878
+ return {
9879
+ index,
9880
+ term,
9881
+ termId,
9882
+ type: context.type,
9883
+ originalValue: null,
9884
+ isOriginal: false,
9885
+ isRequired: true,
9886
+ isFailed: false,
9887
+ original: [],
9888
+ recalculatedValue: null,
9889
+ identity: { 'methodModel.@id': log.model },
9890
+ identityLabel: '',
9891
+ identitySegments: [],
9892
+ isRecalculated: false,
9893
+ recalculated: [syntheticValue],
9894
+ subRows: [],
9895
+ models: [run],
9896
+ modelColumns: [run],
9897
+ // `allSucceeded` is recomputed by `withDisplayValues` at the end of the pipeline
9898
+ allSucceeded: false,
9899
+ canOpen: false,
9900
+ isOpen: false
9901
+ };
9902
+ };
9824
9903
  /**
9825
9904
  * Group the `.jlog` logs for one node key into rows, one per term, keeping every blank node distinct.
9826
9905
  *
@@ -9845,15 +9924,36 @@ const groupJLogByTerm = (jlog, nodeKey, recalculatedValues = [], originalValues
9845
9924
  const key = identityKey(blankNodeIdentity(value, spec));
9846
9925
  return (originalValues || []).find(original => original.term?.['@id'] === value.term?.['@id'] && identityKey(blankNodeIdentity(original, spec)) === key);
9847
9926
  };
9927
+ // each recalculated blank node, attaching its `.jlog` logs when present (a node may exist without logs,
9928
+ // e.g. a value provided in the original)
9929
+ const valueEntries = (recalculatedValues || [])
9930
+ .map((value, index) => ({ value, index, term: value?.term }))
9931
+ .filter(({ term }) => !!term?.['@id']);
9932
+ const valueNodes = valueEntries.map(({ value, index, term }) => buildBlankNode(keyLogs[index] || {}, value, matchOriginal(value), index, term, false, context));
9933
+ // when the recorded `model` is a uniqueness field, models that ran in parallel for a term's value but
9934
+ // produced no blank node (e.g. a sibling that failed while another set the value) are shown as their
9935
+ // own rows - skipping any model already represented by a value row for that term (avoiding duplicates)
9936
+ const seenModelsByTerm = new Map();
9937
+ valueEntries.forEach(({ value, term }) => {
9938
+ const set = seenModelsByTerm.get(term['@id']) ?? new Set();
9939
+ const modelId = blankNodeModelId(value);
9940
+ if (modelId)
9941
+ set.add(modelId);
9942
+ seenModelsByTerm.set(term['@id'], set);
9943
+ });
9944
+ let siblingIndex = recalculatedValues?.length ?? 0;
9945
+ const siblingNodes = modelIsUniquenessField(spec)
9946
+ ? valueEntries.flatMap(({ value, index, term }) => parallelSiblingLogs(keyLogs[index] || {}, blankNodeModelId(value)).flatMap(log => {
9947
+ const seen = seenModelsByTerm.get(term['@id']);
9948
+ if (seen?.has(log.model))
9949
+ return [];
9950
+ seen?.add(log.model);
9951
+ return [buildParallelSiblingRow(log, term, siblingIndex++, context)];
9952
+ }))
9953
+ : [];
9848
9954
  const blankNodes = [
9849
- // list every blank node, attaching its `.jlog` logs when present (a node may exist without logs,
9850
- // e.g. a value provided in the original)
9851
- ...(recalculatedValues || []).map((value, index) => {
9852
- const term = value?.term;
9853
- return term?.['@id']
9854
- ? buildBlankNode(keyLogs[index] || {}, value, matchOriginal(value), index, term, false, context)
9855
- : null;
9856
- }),
9955
+ ...valueNodes,
9956
+ ...siblingNodes,
9857
9957
  ...Object.entries(failedLogs).map(([termId, entry], i) => buildBlankNode(entry, undefined, undefined, -1 - i, getTerm(termId), true, context))
9858
9958
  ].filter(Boolean);
9859
9959
  return orderGroups(groupBlankNodesByTerm(blankNodes).map(withCollapsible).map(withRowLabels).map(withDisplayValues), nodeKey);
@@ -10081,7 +10181,9 @@ class NodeJLogModelsComponent {
10081
10181
  if (!this.isBlankNodes()) {
10082
10182
  return groupJLogByField(this.scopedJlog(), this.nodeKey(), this.originalValues(), this.recalculatedValues(), this.nodeType());
10083
10183
  }
10084
- const groups = groupJLogByTerm(this.scopedJlog(), this.nodeKey(), this.recalculatedValues(), this.originalValues(), this.nodeType(), id => this.failedTerms()[id] || { '@type': NodeType.Term, '@id': id, name: termTypeLabel(id) }, this.config());
10184
+ const groups = groupJLogByTerm(this.scopedJlog(), this.nodeKey(), this.recalculatedValues(), this.originalValues(), this.nodeType(), id => this.failedTerms()[id] ||
10185
+ this.methodsById()?.[id] ||
10186
+ { '@type': NodeType.Term, '@id': id, name: termTypeLabel(id) }, this.config());
10085
10187
  // a combined node key (e.g. `emissionsResourceUse`) holds several term types; keep only the requested
10086
10188
  // ones (like the legacy `computeTerms`), keeping terms whose type is not yet resolved
10087
10189
  const termTypes = this.filterTermTypes();