@hestia-earth/ui-components 0.43.9 → 0.43.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.
@@ -19,6 +19,7 @@ import { propertyValue as propertyValue$1, emptyValue } from '@hestia-earth/util
19
19
  import defaultModelId from '@hestia-earth/glossary/resources/defaultModelId.json';
20
20
  import modelGroups from '@hestia-earth/glossary/resources/modelGroups.json';
21
21
  import inHestiaDefaultSystemBoundary from '@hestia-earth/glossary/resources/inHestiaDefaultSystemBoundary.json';
22
+ import plantationIds from '@hestia-earth/glossary/resources/isPlantation.json';
22
23
  import isEqual$1 from 'lodash.isequal';
23
24
  import { DataState, filenameWithoutExt, nodeTypeToParam, allowedDataStates, SupportedExtensions, fileToExt, fileExt, maxFileSizeMb } from '@hestia-earth/api';
24
25
  import { models as models$1, loadConfig, getFormulas, renderFormula, getMaxStage } from '@hestia-earth/engine-models';
@@ -52,6 +53,7 @@ import { MarkdownComponent } from 'ngx-markdown';
52
53
  import { KatexDirective } from '@hestia-earth/ui-components/katex';
53
54
  export * from '@hestia-earth/ui-components/katex';
54
55
  import arrayTreatment from '@hestia-earth/glossary/resources/arrayTreatment.json';
56
+ import { getFormulas as getFormulas$1 } from '@hestia-earth/aggregation-engine';
55
57
  import * as semver from 'semver';
56
58
  import { parse } from 'papaparse';
57
59
  import omit from 'lodash.omit';
@@ -912,6 +914,13 @@ const isInSystemBoundary = (id) => {
912
914
  const value = inHestiaDefaultSystemBoundary[id];
913
915
  return !value || value === 'true';
914
916
  };
917
+ // the resource is the list of ids the lookup is true for; built into a Set once, as the glossary does
918
+ const plantations = new Set(plantationIds);
919
+ /**
920
+ * Whether the crop is grown as a plantation - a permanent crop producing over several years, rather
921
+ * than one planted and harvested within a Cycle.
922
+ */
923
+ const isPlantation = (id) => plantations.has(id);
915
924
 
916
925
  const maxAreaSize = 5000;
917
926
  const siteTooBig = ({ area }) => area && area / 100 > maxAreaSize;
@@ -8378,10 +8387,14 @@ const hasPreviousModelSuccess = (models, index = 0) => filterConfigModels(models
8378
8387
  const dataWithConfigModelLogs = (logs, termId) => (data) => {
8379
8388
  const subLogKey = data.blankNode ? subValueLogKey(data) : null;
8380
8389
  const log = (subLogKey ? logs[subLogKey] : null) || logs;
8381
- const termsLog = termId in log && data.key === 'input'
8382
- ? log[termId].models.reduce((p, c) => {
8390
+ // `log[termId]` only lists `models` when the entry came from a model run: a log that is not a
8391
+ // recalculation (an aggregated Cycle's, which describes the aggregation) has no models to extend
8392
+ const termModels = (termId in log && log[termId]?.models) || [];
8393
+ const termsLog = termModels.length && data.key === 'input'
8394
+ ? termModels.reduce((p, c) => {
8383
8395
  // extend requirements if none found
8384
- p[c].requirements = p[c]?.requirements || log[c]?.requirements;
8396
+ if (p[c])
8397
+ p[c].requirements = p[c].requirements || log[c]?.requirements;
8385
8398
  return p;
8386
8399
  }, log[termId])
8387
8400
  : log;
@@ -9094,7 +9107,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
9094
9107
  }]
9095
9108
  }] });
9096
9109
 
9097
- const isMissing = (value) => value === undefined || value === null || value === '';
9110
+ // documentation writes inline symbols as TeX between single `$`, e.g. "the weight of $i$"
9111
+ const INLINE_MATH_RE = /\$([^$]+)\$/g;
9112
+ /**
9113
+ * Split documentation text into its plain runs and the inline math between them, so a description
9114
+ * shows its symbols as symbols rather than the raw `$w_i$` source it is written with.
9115
+ */
9116
+ const toTextParts = (value) => {
9117
+ if (!value)
9118
+ return [];
9119
+ const parts = [];
9120
+ let last = 0;
9121
+ INLINE_MATH_RE.lastIndex = 0;
9122
+ for (let match; (match = INLINE_MATH_RE.exec(value));) {
9123
+ if (match.index > last)
9124
+ parts.push({ text: value.slice(last, match.index) });
9125
+ parts.push({ math: match[1] });
9126
+ last = INLINE_MATH_RE.lastIndex;
9127
+ }
9128
+ if (last < value.length)
9129
+ parts.push({ text: value.slice(last) });
9130
+ return parts;
9131
+ };
9132
+
9133
+ /**
9134
+ * Displays a set of formulas with the variables documented under them, and a switch between the
9135
+ * symbolic and the substituted view.
9136
+ *
9137
+ * Presentation only: the caller decides which formulas to show and how their symbols resolve, which
9138
+ * differs entirely between a recalculation (a model's jlog entry, with sub-formulas and
9139
+ * contributions) and an aggregation (the quantities the aggregation records for a data item).
9140
+ */
9141
+ class FormulaBlockComponent {
9142
+ constructor() {
9143
+ /**
9144
+ * The formulas to display, already rendered by the caller.
9145
+ */
9146
+ this.formulas = input([], ...(ngDevMode ? [{ debugName: "formulas" }] : []));
9147
+ /**
9148
+ * Whether any symbol resolves. When nothing does, the substituted view would be identical to the
9149
+ * symbolic one, so the switch is disabled rather than silently doing nothing.
9150
+ */
9151
+ this.hasSubstitutions = input(false, ...(ngDevMode ? [{ debugName: "hasSubstitutions" }] : []));
9152
+ /**
9153
+ * Shown when the switch is disabled, to say why there is nothing to substitute.
9154
+ */
9155
+ this.emptyTitle = input('No logged values to substitute', ...(ngDevMode ? [{ debugName: "emptyTitle" }] : []));
9156
+ /**
9157
+ * The label above the formulas. Defaults to "Formula(s)"; set it when several blocks sit together
9158
+ * and the reader needs to know what each one covers.
9159
+ */
9160
+ this.heading = input('', ...(ngDevMode ? [{ debugName: "heading" }] : []));
9161
+ /**
9162
+ * A line under the heading saying what the block covers, e.g. when it applies.
9163
+ */
9164
+ this.note = input('', ...(ngDevMode ? [{ debugName: "note" }] : []));
9165
+ /**
9166
+ * Whether this block carries the raw/substituted switch. Turn it off on all but the first of
9167
+ * several blocks: they share one state, so repeating the switch only repeats the same control.
9168
+ */
9169
+ this.showToggle = input(true, ...(ngDevMode ? [{ debugName: "showToggle" }] : []));
9170
+ /**
9171
+ * Whether the substituted view is shown. Two-way, so the caller can render accordingly.
9172
+ */
9173
+ this.substituted = model(false, ...(ngDevMode ? [{ debugName: "substituted" }] : []));
9174
+ // unique per instance so a label only toggles its own checkbox - several can co-exist on a page
9175
+ this.toggleId = uuid('formulaSubstituted-');
9176
+ /**
9177
+ * The formulas with their documentation text split into plain runs and inline math, and the
9178
+ * section header dropped where it repeats the formula above - so a run of formulas from one
9179
+ * section reads as one section rather than as the same heading over and over.
9180
+ */
9181
+ this.items = computed(() => this.formulas().map((formula, index) => ({
9182
+ ...formula,
9183
+ section: formula.section === this.formulas()[index - 1]?.section ? undefined : formula.section,
9184
+ contextParts: toTextParts(formula.context),
9185
+ variables: formula.variables.map(variable => ({
9186
+ ...variable,
9187
+ descriptionParts: toTextParts(variable.description)
9188
+ }))
9189
+ })), ...(ngDevMode ? [{ debugName: "items" }] : []));
9190
+ }
9191
+ toggle() {
9192
+ this.substituted.set(!this.substituted());
9193
+ }
9194
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FormulaBlockComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9195
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: FormulaBlockComponent, isStandalone: true, selector: "he-formula-block", inputs: { formulas: { classPropertyName: "formulas", publicName: "formulas", isSignal: true, isRequired: false, transformFunction: null }, hasSubstitutions: { classPropertyName: "hasSubstitutions", publicName: "hasSubstitutions", isSignal: true, isRequired: false, transformFunction: null }, emptyTitle: { classPropertyName: "emptyTitle", publicName: "emptyTitle", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, note: { classPropertyName: "note", publicName: "note", isSignal: true, isRequired: false, transformFunction: null }, showToggle: { classPropertyName: "showToggle", publicName: "showToggle", isSignal: true, isRequired: false, transformFunction: null }, substituted: { classPropertyName: "substituted", publicName: "substituted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { substituted: "substitutedChange" }, ngImport: i0, template: "@if (items().length) {\n <div class=\"formula-block is-mb-2\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span class=\"is-uppercase has-text-weight-semibold\">\n {{ heading() || 'Formula' + (items().length > 1 ? 's' : '') }}\n </span>\n @if (showToggle()) {\n <div class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : emptyTitle()\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n }\n </div>\n\n @if (note()) {\n <p class=\"is-size-8 is-mb-1 | formula-note\">{{ note() }}</p>\n }\n\n @for (item of items(); track $index) {\n @if (item.section) {\n <p class=\"is-size-8 is-uppercase has-text-weight-semibold is-mt-2 | formula-section\">{{ item.section }}</p>\n }\n\n @if (item.contextParts.length) {\n <p class=\"is-size-7 is-mt-1 | formula-context\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: item.contextParts }\" />\n </p>\n }\n\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.descriptionParts.length) {\n <span>:</span>\n }\n </span>\n @if (variable.descriptionParts.length) {\n <span class=\"is-italic | formula-variable-desc\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: variable.descriptionParts }\" />\n </span>\n }\n @if (variable.note) {\n <span class=\"formula-variable-note\">({{ variable.note }})</span>\n } @else if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n}\n\n<!-- documentation text: the symbols it names render as math, the rest as markdown -->\n<ng-template #textParts let-parts=\"parts\">\n @for (part of parts; track $index) {\n @if (part.math) {\n <span class=\"formula-inline-math\" [heKatex]=\"part.math\" [heKatexInline]=\"true\"></span>\n } @else {\n <markdown class=\"is-inline-block\" [data]=\"part.text\" />\n }\n }\n</ng-template>\n", styles: [".formula-block{border-bottom:1px solid rgba(255,255,255,.2)}.formula-section{opacity:.7;letter-spacing:.03em}.formula-note{opacity:.7}.formula-context{opacity:.9}.formula-variables{padding-inline-start:1rem}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-variable-note{opacity:.7;white-space:nowrap}.formula-context ::ng-deep markdown,.formula-context ::ng-deep markdown *,.formula-variable-desc ::ng-deep markdown,.formula-variable-desc ::ng-deep markdown *{display:inline;margin:0;white-space:pre-wrap}.formula-context .formula-inline-math ::ng-deep .katex,.formula-variable-desc .formula-inline-math ::ng-deep .katex{font-size:1em}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: KatexDirective, selector: "[heKatex]", inputs: ["heKatex", "heKatexInline"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9196
+ }
9197
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: FormulaBlockComponent, decorators: [{
9198
+ type: Component$1,
9199
+ args: [{ selector: 'he-formula-block', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet, KatexDirective, MarkdownComponent], template: "@if (items().length) {\n <div class=\"formula-block is-mb-2\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span class=\"is-uppercase has-text-weight-semibold\">\n {{ heading() || 'Formula' + (items().length > 1 ? 's' : '') }}\n </span>\n @if (showToggle()) {\n <div class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : emptyTitle()\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n }\n </div>\n\n @if (note()) {\n <p class=\"is-size-8 is-mb-1 | formula-note\">{{ note() }}</p>\n }\n\n @for (item of items(); track $index) {\n @if (item.section) {\n <p class=\"is-size-8 is-uppercase has-text-weight-semibold is-mt-2 | formula-section\">{{ item.section }}</p>\n }\n\n @if (item.contextParts.length) {\n <p class=\"is-size-7 is-mt-1 | formula-context\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: item.contextParts }\" />\n </p>\n }\n\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.descriptionParts.length) {\n <span>:</span>\n }\n </span>\n @if (variable.descriptionParts.length) {\n <span class=\"is-italic | formula-variable-desc\">\n <ng-container *ngTemplateOutlet=\"textParts; context: { parts: variable.descriptionParts }\" />\n </span>\n }\n @if (variable.note) {\n <span class=\"formula-variable-note\">({{ variable.note }})</span>\n } @else if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n}\n\n<!-- documentation text: the symbols it names render as math, the rest as markdown -->\n<ng-template #textParts let-parts=\"parts\">\n @for (part of parts; track $index) {\n @if (part.math) {\n <span class=\"formula-inline-math\" [heKatex]=\"part.math\" [heKatexInline]=\"true\"></span>\n } @else {\n <markdown class=\"is-inline-block\" [data]=\"part.text\" />\n }\n }\n</ng-template>\n", styles: [".formula-block{border-bottom:1px solid rgba(255,255,255,.2)}.formula-section{opacity:.7;letter-spacing:.03em}.formula-note{opacity:.7}.formula-context{opacity:.9}.formula-variables{padding-inline-start:1rem}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-variable-note{opacity:.7;white-space:nowrap}.formula-context ::ng-deep markdown,.formula-context ::ng-deep markdown *,.formula-variable-desc ::ng-deep markdown,.formula-variable-desc ::ng-deep markdown *{display:inline;margin:0;white-space:pre-wrap}.formula-context .formula-inline-math ::ng-deep .katex,.formula-variable-desc .formula-inline-math ::ng-deep .katex{font-size:1em}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"] }]
9200
+ }], propDecorators: { formulas: [{ type: i0.Input, args: [{ isSignal: true, alias: "formulas", required: false }] }], hasSubstitutions: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasSubstitutions", required: false }] }], emptyTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyTitle", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], note: [{ type: i0.Input, args: [{ isSignal: true, alias: "note", required: false }] }], showToggle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToggle", required: false }] }], substituted: [{ type: i0.Input, args: [{ isSignal: true, alias: "substituted", required: false }] }, { type: i0.Output, args: ["substitutedChange"] }] } });
9201
+
9202
+ const isMissing$1 = (value) => value === undefined || value === null || value === '';
9098
9203
  // the result symbol (left-hand side) - it is the model's output, not an input that "was not logged", so
9099
9204
  // it is excluded from the missing-values notice (e.g. a failed model has no result value)
9100
9205
  const resultKey = 'value';
@@ -9140,7 +9245,7 @@ const isInput = (binding, resultSymbol, intermediateKeys) => binding.symbol !==
9140
9245
  !intermediateKeys.has(binding.key) &&
9141
9246
  !isConstant(binding);
9142
9247
  // an input whose value was not substituted here (no jlog binding, or one whose value was not logged)
9143
- const isMissingInput = (binding, resultSymbol, values, intermediateKeys) => isInput(binding, resultSymbol, intermediateKeys) && isMissing(binding.key && values[binding.key]);
9248
+ const isMissingInput = (binding, resultSymbol, values, intermediateKeys) => isInput(binding, resultSymbol, intermediateKeys) && isMissing$1(binding.key && values[binding.key]);
9144
9249
  // the variables listed below a formula: every binding carrying a symbol and either a logged value or
9145
9250
  // documentation, shown as "symbol: description". `missing` flags a variable that has no value to
9146
9251
  // substitute here - whether it has no jlog binding at all, or a binding whose value was not logged - so
@@ -9169,9 +9274,7 @@ class NodeLogsModelsFormulaComponent {
9169
9274
  this.node = input(...(ngDevMode ? [undefined, { debugName: "node" }] : []));
9170
9275
  this.nodeService = inject(HeNodeService);
9171
9276
  // show the substituted formula (values in place of symbols) rather than the raw symbolic one
9172
- this.substituted = signal(false, ...(ngDevMode ? [{ debugName: "substituted" }] : []));
9173
- // a unique id per instance so the switch label only toggles its own checkbox (several popovers can co-exist)
9174
- this.toggleId = uuid('formulaSubstituted-');
9277
+ this.substituted = model(false, ...(ngDevMode ? [{ debugName: "substituted" }] : []));
9175
9278
  this.formulas = computed(() => getModelFormulas(this.model()), ...(ngDevMode ? [{ debugName: "formulas" }] : []));
9176
9279
  // whether any formula sums over per-contributor products (bound to the reserved `contributions` table)
9177
9280
  this.hasContributionBinding = computed(() => this.formulas().some(formula => formula.bindings.some(binding => binding.key === contributionsKey)), ...(ngDevMode ? [{ debugName: "hasContributionBinding" }] : []));
@@ -9196,7 +9299,7 @@ class NodeLogsModelsFormulaComponent {
9196
9299
  this.values = computed(() => {
9197
9300
  const values = logValues(this.logs());
9198
9301
  // the result (`value`) is usually the model's returned value rather than a logged field
9199
- const withResult = isMissing(values.value) && !isMissing(this.value()) ? { ...values, value: this.value() } : values;
9302
+ const withResult = isMissing$1(values.value) && !isMissing$1(this.value()) ? { ...values, value: this.value() } : values;
9200
9303
  // feed a contribution-based `\sum` from the stored contributions (nothing to merge for other formulas)
9201
9304
  const rows = this.contributionRows();
9202
9305
  return rows.length ? { ...withResult, [contributionsKey]: rows } : withResult;
@@ -9220,7 +9323,7 @@ class NodeLogsModelsFormulaComponent {
9220
9323
  const values = this.values();
9221
9324
  return this.formulas().some(formula => {
9222
9325
  const formulaValues = this.formulaValues(formula, values);
9223
- return formula.bindings.some(binding => binding.key && !isMissing(formulaValues[binding.key]));
9326
+ return formula.bindings.some(binding => binding.key && !isMissing$1(formulaValues[binding.key]));
9224
9327
  });
9225
9328
  }, ...(ngDevMode ? [{ debugName: "hasSubstitutions" }] : []));
9226
9329
  // each formula paired with the variable list shown below it: every symbol with its documentation and
@@ -9246,16 +9349,13 @@ class NodeLogsModelsFormulaComponent {
9246
9349
  : new Set([...this.intermediateKeys(), ...this.valueKeys()]);
9247
9350
  return omitKeys(values, omit);
9248
9351
  }
9249
- toggle() {
9250
- this.substituted.update(value => !value);
9251
- }
9252
9352
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsFormulaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9253
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeLogsModelsFormulaComponent, isStandalone: true, selector: "he-node-logs-models-formula", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (formulas().length) {\n <div class=\"formula-block is-mb-2 has-text-white\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span class=\"is-uppercase has-text-weight-semibold\">Formula{{ formulas().length > 1 ? 's' : '' }}</span>\n <div class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : 'No logged values to substitute'\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n </div>\n\n @for (item of renderedFormulas(); track $index) {\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.description) {\n <span>:</span>\n }\n </span>\n @if (variable.description) {\n <markdown class=\"is-inline-block is-italic | formula-variable-desc\" [data]=\"variable.description\" />\n }\n @if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n}\n", styles: [".formula-block{border-bottom:1px solid rgba(255,255,255,.2)}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-variable-desc ::ng-deep *{display:inline;margin:0}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"], dependencies: [{ kind: "directive", type: KatexDirective, selector: "[heKatex]", inputs: ["heKatex", "heKatexInline"] }, { kind: "component", type: MarkdownComponent, selector: "markdown, [markdown]", inputs: ["data", "src", "disableSanitizer", "inline", "clipboard", "clipboardButtonComponent", "clipboardButtonTemplate", "emoji", "katex", "katexOptions", "mermaid", "mermaidOptions", "lineHighlight", "line", "lineOffset", "lineNumbers", "start", "commandLine", "filterOutput", "host", "prompt", "output", "user"], outputs: ["error", "load", "ready"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9353
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.6", type: NodeLogsModelsFormulaComponent, isStandalone: true, selector: "he-node-logs-models-formula", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, substituted: { classPropertyName: "substituted", publicName: "substituted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { substituted: "substitutedChange" }, ngImport: i0, template: "<he-formula-block\n [formulas]=\"renderedFormulas()\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\" />\n", dependencies: [{ kind: "component", type: FormulaBlockComponent, selector: "he-formula-block", inputs: ["formulas", "hasSubstitutions", "emptyTitle", "heading", "note", "showToggle", "substituted"], outputs: ["substitutedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9254
9354
  }
9255
9355
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsFormulaComponent, decorators: [{
9256
9356
  type: Component$1,
9257
- args: [{ selector: 'he-node-logs-models-formula', changeDetection: ChangeDetectionStrategy.OnPush, imports: [KatexDirective, MarkdownComponent], template: "@if (formulas().length) {\n <div class=\"formula-block is-mb-2 has-text-white\">\n <div class=\"is-flex is-align-items-center is-justify-content-space-between is-gap-8 is-mb-1 is-size-8\">\n <span class=\"is-uppercase has-text-weight-semibold\">Formula{{ formulas().length > 1 ? 's' : '' }}</span>\n <div class=\"field\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"toggleId\"\n [checked]=\"substituted()\"\n [disabled]=\"!hasSubstitutions()\"\n (change)=\"toggle()\" />\n <label [for]=\"toggleId\" [title]=\"hasSubstitutions() ? '' : 'No logged values to substitute'\">\n <span>{{ substituted() ? 'Substituted' : 'Raw' }}</span>\n </label>\n </div>\n </div>\n\n @for (item of renderedFormulas(); track $index) {\n <div class=\"formula\" [heKatex]=\"item.rendered\"></div>\n\n @if (item.variables.length) {\n <ul class=\"is-size-7 is-mt-1 is-mb-2 is-list-style-disc | formula-variables\">\n @for (variable of item.variables; track $index) {\n <li>\n <div class=\"is-flex is-align-items-baseline is-gap-4\">\n <span class=\"formula-variable-symbol is-nowrap\">\n <span [heKatex]=\"variable.symbol\" [heKatexInline]=\"true\"></span>\n @if (variable.description) {\n <span>:</span>\n }\n </span>\n @if (variable.description) {\n <markdown class=\"is-inline-block is-italic | formula-variable-desc\" [data]=\"variable.description\" />\n }\n @if (variable.missing) {\n <span class=\"has-text-warning\">(missing)</span>\n }\n </div>\n </li>\n }\n </ul>\n }\n }\n </div>\n}\n", styles: [".formula-block{border-bottom:1px solid rgba(255,255,255,.2)}.formula-variable-symbol{flex-shrink:0}.formula-variable-symbol ::ng-deep .katex{font-size:1em}.formula-variable-desc{opacity:.85}.formula-variable-desc ::ng-deep *{display:inline;margin:0}.formula{overflow-x:auto;overflow-y:hidden}.formula ::ng-deep .katex-display{margin:.35rem 0}\n"] }]
9258
- }], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: false }] }], logs: [{ type: i0.Input, args: [{ isSignal: true, alias: "logs", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }] } });
9357
+ args: [{ selector: 'he-node-logs-models-formula', changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormulaBlockComponent], template: "<he-formula-block\n [formulas]=\"renderedFormulas()\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\" />\n" }]
9358
+ }], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: false }] }], logs: [{ type: i0.Input, args: [{ isSignal: true, alias: "logs", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }], substituted: [{ type: i0.Input, args: [{ isSignal: true, alias: "substituted", required: false }] }, { type: i0.Output, args: ["substitutedChange"] }] } });
9259
9359
 
9260
9360
  class NodeLogsModelsDetailsComponent {
9261
9361
  constructor() {
@@ -9299,7 +9399,7 @@ class NodeLogsModelsDetailsComponent {
9299
9399
  this.openKeys.set(next);
9300
9400
  }
9301
9401
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsDetailsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9302
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeLogsModelsDetailsComponent, isStandalone: true, selector: "he-node-logs-models-details", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, hasContributions: { classPropertyName: "hasContributions", publicName: "hasContributions", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@for (section of sections(); track section.key; let last = $last) {\n <div class=\"log-section\" [class.has-border-bottom]=\"!last || isOpen(section.key)\">\n @if (section.key === 'formula' && isOpen('formula')) {\n <!-- the formula component renders its own \"Formula BETA\" title + Raw/Substituted switch on one\n line, so keep the toggle chevron inline with it rather than adding a duplicate header -->\n <div class=\"log-section-header is-flex is-align-items-flex-start is-gap-4\">\n <a class=\"has-text-white\" (click)=\"toggle('formula')\">\n <he-svg-icon name=\"chevron-down\" size=\"16\" />\n </a>\n <div class=\"is-flex-grow-1\">\n <he-node-logs-models-formula\n [model]=\"model().model\"\n [logs]=\"model().logs\"\n [value]=\"value()\"\n [node]=\"node()\" />\n </div>\n </div>\n } @else {\n <a\n class=\"log-section-header is-flex is-align-items-center is-gap-4 has-text-white is-size-8 is-uppercase has-text-weight-semibold\"\n (click)=\"toggle(section.key)\">\n <he-svg-icon [name]=\"isOpen(section.key) ? 'chevron-down' : 'chevron-right'\" size=\"16\" />\n <span>{{ section.label }}</span>\n </a>\n\n @if (isOpen(section.key)) {\n <div class=\"log-section-body is-pb-2\">\n @switch (section.key) {\n @case ('logs') {\n <he-node-logs-models-logs [logs]=\"model().logs\" [renderRaw]=\"false\" />\n }\n @case ('raw') {\n <div class=\"is-flex is-justify-content-flex-end is-mb-1\">\n <he-clipboard clipboardClass=\"is-size-7 is-p-1\" [value]=\"rawJson()\" [hideText]=\"true\" />\n </div>\n <pre class=\"raw-log\">{{ rawJson() }}</pre>\n }\n @case ('contributions') {\n <he-node-logs-models-contributions [node]=\"node()\" [nodeKey]=\"nodeKey()\" [model]=\"model()\" />\n }\n }\n </div>\n }\n }\n </div>\n}\n", styles: [".log-section{border-color:#fff3!important}.log-section-header{cursor:pointer;padding:.5rem 0}.log-section-header ::ng-deep .formula-block{margin-bottom:0;border-bottom:none}.raw-log{max-height:300px;overflow:auto;white-space:pre;font-size:.75rem;background:#00000059;color:inherit;padding:.5rem;border-radius:3px}\n"], dependencies: [{ kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: ClipboardComponent, selector: "he-clipboard", inputs: ["icon", "value", "disabled", "hideText", "hideIcon", "size", "clipboardClass", "tooltipPlacement"] }, { kind: "component", type: NodeLogsModelsFormulaComponent, selector: "he-node-logs-models-formula", inputs: ["model", "logs", "value", "node"] }, { kind: "component", type: NodeLogsModelsLogsComponent, selector: "he-node-logs-models-logs", inputs: ["logs", "renderRaw"] }, { kind: "component", type: NodeLogsModelsContributionsComponent, selector: "he-node-logs-models-contributions", inputs: ["node", "nodeKey", "model"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9402
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeLogsModelsDetailsComponent, isStandalone: true, selector: "he-node-logs-models-details", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: true, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, hasContributions: { classPropertyName: "hasContributions", publicName: "hasContributions", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@for (section of sections(); track section.key; let last = $last) {\n <div class=\"log-section\" [class.has-border-bottom]=\"!last || isOpen(section.key)\">\n @if (section.key === 'formula' && isOpen('formula')) {\n <!-- the formula component renders its own \"Formula BETA\" title + Raw/Substituted switch on one\n line, so keep the toggle chevron inline with it rather than adding a duplicate header -->\n <div class=\"log-section-header is-flex is-align-items-flex-start is-gap-4\">\n <a class=\"has-text-white\" (click)=\"toggle('formula')\">\n <he-svg-icon name=\"chevron-down\" size=\"16\" />\n </a>\n <div class=\"is-flex-grow-1\">\n <he-node-logs-models-formula\n [model]=\"model().model\"\n [logs]=\"model().logs\"\n [value]=\"value()\"\n [node]=\"node()\" />\n </div>\n </div>\n } @else {\n <a\n class=\"log-section-header is-flex is-align-items-center is-gap-4 has-text-white is-size-8 is-uppercase has-text-weight-semibold\"\n (click)=\"toggle(section.key)\">\n <he-svg-icon [name]=\"isOpen(section.key) ? 'chevron-down' : 'chevron-right'\" size=\"16\" />\n <span>{{ section.label }}</span>\n </a>\n\n @if (isOpen(section.key)) {\n <div class=\"log-section-body is-pb-2\">\n @switch (section.key) {\n @case ('logs') {\n <he-node-logs-models-logs [logs]=\"model().logs\" [renderRaw]=\"false\" />\n }\n @case ('raw') {\n <div class=\"is-flex is-justify-content-flex-end is-mb-1\">\n <he-clipboard clipboardClass=\"is-size-7 is-p-1\" [value]=\"rawJson()\" [hideText]=\"true\" />\n </div>\n <pre class=\"raw-log\">{{ rawJson() }}</pre>\n }\n @case ('contributions') {\n <he-node-logs-models-contributions [node]=\"node()\" [nodeKey]=\"nodeKey()\" [model]=\"model()\" />\n }\n }\n </div>\n }\n }\n </div>\n}\n", styles: [".log-section{border-color:#fff3!important}.log-section-header{cursor:pointer;padding:.5rem 0}.log-section-header ::ng-deep .formula-block{margin-bottom:0;border-bottom:none}.raw-log{max-height:300px;overflow:auto;white-space:pre;font-size:.75rem;background:#00000059;color:inherit;padding:.5rem;border-radius:3px}\n"], dependencies: [{ kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: ClipboardComponent, selector: "he-clipboard", inputs: ["icon", "value", "disabled", "hideText", "hideIcon", "size", "clipboardClass", "tooltipPlacement"] }, { kind: "component", type: NodeLogsModelsFormulaComponent, selector: "he-node-logs-models-formula", inputs: ["model", "logs", "value", "node", "substituted"], outputs: ["substitutedChange"] }, { kind: "component", type: NodeLogsModelsLogsComponent, selector: "he-node-logs-models-logs", inputs: ["logs", "renderRaw"] }, { kind: "component", type: NodeLogsModelsContributionsComponent, selector: "he-node-logs-models-contributions", inputs: ["node", "nodeKey", "model"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9303
9403
  }
9304
9404
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeLogsModelsDetailsComponent, decorators: [{
9305
9405
  type: Component$1,
@@ -9312,6 +9412,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
9312
9412
  ], template: "@for (section of sections(); track section.key; let last = $last) {\n <div class=\"log-section\" [class.has-border-bottom]=\"!last || isOpen(section.key)\">\n @if (section.key === 'formula' && isOpen('formula')) {\n <!-- the formula component renders its own \"Formula BETA\" title + Raw/Substituted switch on one\n line, so keep the toggle chevron inline with it rather than adding a duplicate header -->\n <div class=\"log-section-header is-flex is-align-items-flex-start is-gap-4\">\n <a class=\"has-text-white\" (click)=\"toggle('formula')\">\n <he-svg-icon name=\"chevron-down\" size=\"16\" />\n </a>\n <div class=\"is-flex-grow-1\">\n <he-node-logs-models-formula\n [model]=\"model().model\"\n [logs]=\"model().logs\"\n [value]=\"value()\"\n [node]=\"node()\" />\n </div>\n </div>\n } @else {\n <a\n class=\"log-section-header is-flex is-align-items-center is-gap-4 has-text-white is-size-8 is-uppercase has-text-weight-semibold\"\n (click)=\"toggle(section.key)\">\n <he-svg-icon [name]=\"isOpen(section.key) ? 'chevron-down' : 'chevron-right'\" size=\"16\" />\n <span>{{ section.label }}</span>\n </a>\n\n @if (isOpen(section.key)) {\n <div class=\"log-section-body is-pb-2\">\n @switch (section.key) {\n @case ('logs') {\n <he-node-logs-models-logs [logs]=\"model().logs\" [renderRaw]=\"false\" />\n }\n @case ('raw') {\n <div class=\"is-flex is-justify-content-flex-end is-mb-1\">\n <he-clipboard clipboardClass=\"is-size-7 is-p-1\" [value]=\"rawJson()\" [hideText]=\"true\" />\n </div>\n <pre class=\"raw-log\">{{ rawJson() }}</pre>\n }\n @case ('contributions') {\n <he-node-logs-models-contributions [node]=\"node()\" [nodeKey]=\"nodeKey()\" [model]=\"model()\" />\n }\n }\n </div>\n }\n }\n </div>\n}\n", styles: [".log-section{border-color:#fff3!important}.log-section-header{cursor:pointer;padding:.5rem 0}.log-section-header ::ng-deep .formula-block{margin-bottom:0;border-bottom:none}.raw-log{max-height:300px;overflow:auto;white-space:pre;font-size:.75rem;background:#00000059;color:inherit;padding:.5rem;border-radius:3px}\n"] }]
9313
9413
  }], propDecorators: { model: [{ type: i0.Input, args: [{ isSignal: true, alias: "model", required: true }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], hasContributions: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasContributions", required: false }] }] } });
9314
9414
 
9415
+ /**
9416
+ * What tells one blank node apart from the others sharing its term, e.g. "inputs: Wheat, grain" or
9417
+ * "depths: 0-30". Shown on the sub-rows of a term group, both in the recalculation logs and in the
9418
+ * aggregation logs - the two views label their rows identically, so they share this.
9419
+ */
9420
+ class BlankNodeIdentityComponent {
9421
+ constructor() {
9422
+ /**
9423
+ * The identity fields whose value is a Term, shown as labelled term links.
9424
+ */
9425
+ this.segments = input([], ...(ngDevMode ? [{ debugName: "segments" }] : []));
9426
+ /**
9427
+ * The scalar identity fields, each as a `key: value` whose field name links to its schema docs.
9428
+ */
9429
+ this.scalars = input([], ...(ngDevMode ? [{ debugName: "scalars" }] : []));
9430
+ /**
9431
+ * The type of the blank node (e.g. `Measurement`), which the scalar field names link to.
9432
+ */
9433
+ this.type = input(...(ngDevMode ? [undefined, { debugName: "type" }] : []));
9434
+ /**
9435
+ * Shown when nothing distinguishes the blank node from its siblings (e.g. "entry 2").
9436
+ */
9437
+ this.fallback = input('', ...(ngDevMode ? [{ debugName: "fallback" }] : []));
9438
+ this.schemaBaseUrl = schemaBaseUrl;
9439
+ }
9440
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: BlankNodeIdentityComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
9441
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: BlankNodeIdentityComponent, isStandalone: true, selector: "he-blank-node-identity", inputs: { segments: { classPropertyName: "segments", publicName: "segments", isSignal: true, isRequired: false, transformFunction: null }, scalars: { classPropertyName: "scalars", publicName: "scalars", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, fallback: { classPropertyName: "fallback", publicName: "fallback", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "is-flex is-align-items-center is-flex-wrap-wrap is-gap-4" }, ngImport: i0, template: "@if (segments().length || scalars().length) {\n @for (segment of segments(); track segment.field; let s = $index) {\n @if (s > 0) {\n <span>\u00B7</span>\n }\n <span>{{ segment.field | keyToLabel }}:</span>\n @for (term of segment.terms; track term['@id']) {\n <he-node-link class=\"is-inline-block\" [node]=\"term\">\n <span class=\"break-word\" [innerHtml]=\"term.name | compound: term.termType\"></span>\n </he-node-link>\n }\n }\n @for (scalar of scalars(); track scalar.key; let i = $index) {\n <span class=\"break-word\">\n @if (i > 0 || segments().length) {\n \u00B7\n }\n @if (scalar.field && type()) {\n <a [href]=\"schemaBaseUrl + '/' + type() + '#' + scalar.field\" target=\"_blank\">\n {{ scalar.key }}\n </a>\n @if (scalar.value) {\n : {{ scalar.value }}\n }\n } @else {\n {{ scalar.key }}\n @if (scalar.value) {\n : {{ scalar.value }}\n }\n }\n </span>\n }\n} @else if (fallback()) {\n <span class=\"has-text-grey\">{{ fallback() }}</span>\n}\n", dependencies: [{ kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "pipe", type: CompoundPipe, name: "compound" }, { kind: "pipe", type: KeyToLabelPipe, name: "keyToLabel" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
9442
+ }
9443
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: BlankNodeIdentityComponent, decorators: [{
9444
+ type: Component$1,
9445
+ args: [{ selector: 'he-blank-node-identity', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'is-flex is-align-items-center is-flex-wrap-wrap is-gap-4' }, imports: [CompoundPipe, KeyToLabelPipe, NodeLinkComponent], template: "@if (segments().length || scalars().length) {\n @for (segment of segments(); track segment.field; let s = $index) {\n @if (s > 0) {\n <span>\u00B7</span>\n }\n <span>{{ segment.field | keyToLabel }}:</span>\n @for (term of segment.terms; track term['@id']) {\n <he-node-link class=\"is-inline-block\" [node]=\"term\">\n <span class=\"break-word\" [innerHtml]=\"term.name | compound: term.termType\"></span>\n </he-node-link>\n }\n }\n @for (scalar of scalars(); track scalar.key; let i = $index) {\n <span class=\"break-word\">\n @if (i > 0 || segments().length) {\n \u00B7\n }\n @if (scalar.field && type()) {\n <a [href]=\"schemaBaseUrl + '/' + type() + '#' + scalar.field\" target=\"_blank\">\n {{ scalar.key }}\n </a>\n @if (scalar.value) {\n : {{ scalar.value }}\n }\n } @else {\n {{ scalar.key }}\n @if (scalar.value) {\n : {{ scalar.value }}\n }\n }\n </span>\n }\n} @else if (fallback()) {\n <span class=\"has-text-grey\">{{ fallback() }}</span>\n}\n" }]
9446
+ }], propDecorators: { segments: [{ type: i0.Input, args: [{ isSignal: true, alias: "segments", required: false }] }], scalars: [{ type: i0.Input, args: [{ isSignal: true, alias: "scalars", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], fallback: [{ type: i0.Input, args: [{ isSignal: true, alias: "fallback", required: false }] }] } });
9447
+
9315
9448
  /**
9316
9449
  * The `.jlog` recalculation logs follow the representation of the recalculated node: model logs are
9317
9450
  * stored per blank node index (with nested `properties`), plus `<field>-failed` entries (keyed by
@@ -9588,6 +9721,7 @@ const identityDisplay = (identity, value) => {
9588
9721
  };
9589
9722
  // the raw blank node value used to derive the fallback identity (logs/`failed` rows have no value)
9590
9723
  const rowRawValue = (row) => row.recalculated[0] || row.original[0] || {};
9724
+ const identityRow = (row) => ({ identity: row.identity, raw: rowRawValue(row) });
9591
9725
  // top-level fields skipped when looking for a fallback identity: the group key (`term`), JSON-LD
9592
9726
  // metadata (`@type`/`@id`), change-tracking (`added`/`updated`/`removed` and their `Version` siblings),
9593
9727
  // the nested `logs`, and the measured `value` (shown in the value column, never a distinguishing label)
@@ -9620,8 +9754,7 @@ const fieldFingerprint = (value) => isTermObject(value)
9620
9754
  // as the fallback "key: value" label (instead of a bare "entry N")
9621
9755
  const differingRawFields = (rows) => {
9622
9756
  const seen = {};
9623
- rows.forEach(row => {
9624
- const value = rowRawValue(row);
9757
+ rows.forEach(({ raw: value }) => {
9625
9758
  Object.keys(value)
9626
9759
  .filter(isFallbackKey)
9627
9760
  .forEach(key => {
@@ -9631,16 +9764,13 @@ const differingRawFields = (rows) => {
9631
9764
  });
9632
9765
  return Object.keys(seen).filter(key => seen[key].size > 1);
9633
9766
  };
9634
- const fallbackIdentity = (row, keys) => {
9635
- const value = rowRawValue(row);
9636
- return Object.fromEntries(keys.filter(key => isPresent(value[key])).map(key => [key, value[key]]));
9637
- };
9767
+ const fallbackIdentity = ({ raw: value }, keys) => Object.fromEntries(keys.filter(key => isPresent(value[key])).map(key => [key, value[key]]));
9638
9768
  // fields that attribute a blank node to a Cycle sub-context (a transformation or an animal). When a
9639
9769
  // group has rows split between "with" and "without" one of these, the rows that have none belong to
9640
9770
  // the Cycle itself - shown as a plain "Cycle" label (mirrors the legacy "cycle" subValue)
9641
9771
  const cycleOwnerFields = ['transformation', 'animal'];
9642
- const isCycleOwnedRow = (row) => cycleOwnerFields.every(key => !isPresent(rowRawValue(row)[key]));
9643
- const hasCycleOwnerSibling = (rows) => rows.some(row => cycleOwnerFields.some(key => isPresent(rowRawValue(row)[key])));
9772
+ const isCycleOwnedRow = ({ raw }) => cycleOwnerFields.every(key => !isPresent(raw[key]));
9773
+ const hasCycleOwnerSibling = (rows) => rows.some(({ raw }) => cycleOwnerFields.some(key => isPresent(raw[key])));
9644
9774
  const isNonEmptyIdentity = ({ segments, scalars }) => segments.length > 0 || scalars.length > 0;
9645
9775
  const emptyIdentity = { segments: [], scalars: [] };
9646
9776
  // pick the first non-empty identity for a row, in order of preference:
@@ -9649,7 +9779,7 @@ const emptyIdentity = { segments: [], scalars: [] };
9649
9779
  // 3. a "Cycle" label when siblings have `transformation`/`animal` and this row has neither
9650
9780
  // (mirrors the legacy "cycle" subValue).
9651
9781
  const resolveRowIdentity = (row, fallbackKeys, keepNoisy, cycleOwned) => {
9652
- const value = rowRawValue(row);
9782
+ const value = row.raw;
9653
9783
  const candidates = [
9654
9784
  identityDisplay(keepNoisy ? row.identity : identityWithout(row.identity, isNoisyIdentityField), value),
9655
9785
  identityDisplay(fallbackIdentity(row, fallbackKeys), value),
@@ -9657,14 +9787,24 @@ const resolveRowIdentity = (row, fallbackKeys, keepNoisy, cycleOwned) => {
9657
9787
  ];
9658
9788
  return candidates.find(isNonEmptyIdentity) ?? emptyIdentity;
9659
9789
  };
9790
+ /**
9791
+ * Label every blank node of a term group: what distinguishes it from the others sharing that term.
9792
+ *
9793
+ * The decisions that need the whole group (are the noisy method fields redundant? which raw fields
9794
+ * actually differ? is a sibling owned by a transformation/animal?) are taken once, here.
9795
+ */
9796
+ const labelIdentityRows = (rows) => {
9797
+ const keepNoisy = !noisyFieldsRedundant(rows);
9798
+ const fallbackKeys = rows.length > 1 ? differingRawFields(rows) : [];
9799
+ const cycleOwned = hasCycleOwnerSibling(rows);
9800
+ return rows.map(row => resolveRowIdentity(row, fallbackKeys, keepNoisy, cycleOwned));
9801
+ };
9660
9802
  const withRowLabels = (group) => {
9661
- const keepNoisy = !noisyFieldsRedundant(group.rows);
9662
- const fallbackKeys = group.rows.length > 1 ? differingRawFields(group.rows) : [];
9663
- const cycleOwned = hasCycleOwnerSibling(group.rows);
9803
+ const labels = labelIdentityRows(group.rows.map(identityRow));
9664
9804
  return {
9665
9805
  ...group,
9666
- rows: group.rows.map(row => {
9667
- const { segments, scalars } = resolveRowIdentity(row, fallbackKeys, keepNoisy, cycleOwned);
9806
+ rows: group.rows.map((row, index) => {
9807
+ const { segments, scalars } = labels[index];
9668
9808
  return {
9669
9809
  ...row,
9670
9810
  identitySegments: segments,
@@ -10440,6 +10580,55 @@ const groupJLogByField = (jlog, nodeKey, originalValues, recalculatedValues, nod
10440
10580
  });
10441
10581
  });
10442
10582
  };
10583
+ /**
10584
+ * Group blank nodes by term, labelling those that share one - the grouping the recalculation logs
10585
+ * show, for a view that has no `.jlog` to read (the aggregation logs).
10586
+ *
10587
+ * The labels come from the schema `uniquenessFields`, so two entries for the same term read as e.g.
10588
+ * "depths: 0-30" or "inputs: Wheat, grain" exactly as they do in the recalculation logs.
10589
+ *
10590
+ * @param values The blank nodes of one node key (e.g. a cycle's `inputs`).
10591
+ * @param nodeType The type of the node holding them (e.g. `Cycle`), for the parent uniqueness fields.
10592
+ * @param nodeKey The node field they were read from (e.g. `inputs`).
10593
+ */
10594
+ const groupBlankNodesByTermIdentity = (values, nodeType, nodeKey) => {
10595
+ const entries = (values || [])
10596
+ .map((value, index) => ({ value, index, term: value?.term }))
10597
+ .filter(({ term }) => !!term?.['@id']);
10598
+ const type = entries[0]?.value?.['@type'] || entries[0]?.value?.type;
10599
+ const spec = identitySpec(type, nodeType, nodeKey);
10600
+ const groups = entries.reduce((groups, { value, index, term }) => {
10601
+ const termId = term['@id'];
10602
+ const group = groups.find(group => group.termId === termId);
10603
+ // the aggregated blank node is both what the fallback label is derived from, and what is shown
10604
+ const row = { index, value, raw: value, identity: blankNodeIdentity(value, spec) };
10605
+ if (group) {
10606
+ group.rows.push(row);
10607
+ return groups;
10608
+ }
10609
+ return [...groups, { term, termId, type, rows: [row] }];
10610
+ }, []);
10611
+ return orderBy(groups.map(group => {
10612
+ const labels = labelIdentityRows(group.rows);
10613
+ const blankNodes = group.rows.map(({ value }) => value);
10614
+ const value = reduceValues(blankNodes, group.termId, group.type);
10615
+ return {
10616
+ ...group,
10617
+ canOpen: group.rows.length > 1,
10618
+ // combined the same way the recalculation logs combine a group's rows: the term's array
10619
+ // treatment (sum/mean/...), or a depth-weighted average for Site measurements
10620
+ value,
10621
+ valueFormula: valueFormula(blankNodes, group.termId, value, group.type),
10622
+ rows: group.rows.map(({ index, value }, rowIndex) => ({
10623
+ index,
10624
+ value,
10625
+ segments: labels[rowIndex].segments,
10626
+ scalars: labels[rowIndex].scalars,
10627
+ label: identityScalarsLabel(labels[rowIndex].scalars)
10628
+ }))
10629
+ };
10630
+ }), [group => group.term?.name || group.termId], ['asc']);
10631
+ };
10443
10632
  // the number of "Model N" columns a row needs (a parallel group counts as one column)
10444
10633
  const rowColumnCount = (row) => row.modelColumns.length;
10445
10634
  /**
@@ -10612,7 +10801,7 @@ class NodeJLogModelsComponent {
10612
10801
  return model.methodId;
10613
10802
  }
10614
10803
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeJLogModelsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
10615
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeJLogModelsComponent, isStandalone: true, selector: "he-node-jlog-models", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, originalValues: { classPropertyName: "originalValues", publicName: "originalValues", isSignal: true, isRequired: false, transformFunction: null }, recalculatedValues: { classPropertyName: "recalculatedValues", publicName: "recalculatedValues", isSignal: true, isRequired: false, transformFunction: null }, filterTermTypes: { classPropertyName: "filterTermTypes", publicName: "filterTermTypes", isSignal: true, isRequired: false, transformFunction: null }, filterTermTypesLabel: { classPropertyName: "filterTermTypesLabel", publicName: "filterTermTypesLabel", isSignal: true, isRequired: false, transformFunction: null }, cycle: { classPropertyName: "cycle", publicName: "cycle", isSignal: true, isRequired: false, transformFunction: null }, jlog: { classPropertyName: "jlog", publicName: "jlog", isSignal: true, isRequired: false, transformFunction: null }, jlogParentKey: { classPropertyName: "jlogParentKey", publicName: "jlogParentKey", isSignal: true, isRequired: false, transformFunction: null }, jlogParentIndex: { classPropertyName: "jlogParentIndex", publicName: "jlogParentIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\">\n @if (enableFilterByTerm()) {\n <div class=\"field is-pb-1\">\n <div class=\"control is-expanded has-icons-right\">\n <input\n class=\"input search-input is-small\"\n [ngModel]=\"term()\"\n name=\"term\"\n placeholder=\"Select entry by name\"\n [ngbTypeahead]=\"suggestTerm\"\n [resultFormatter]=\"termFormatter\"\n [inputFormatter]=\"termFormatter\"\n [focusFirst]=\"true\"\n (focus)=\"typeaheadFocus($event)\"\n (selectItem)=\"term.set($event.item)\"\n container=\"body\"\n popupClass=\"is-small\" />\n <a class=\"icon is-small is-right\" [class.is-hidden]=\"!term()\" (click)=\"term.set(undefined)\">\n <he-svg-icon name=\"xmark\" />\n </a>\n </div>\n </div>\n }\n </th>\n @if (isBlankNodes()) {\n <th class=\"has-border-right\"><span>Units</span></th>\n }\n <th class=\"has-border-right\"><span>Original</span></th>\n <th class=\"has-border-right\"><span>Recalculated</span></th>\n @if (isBlankNodes()) {\n <th class=\"has-border-right\"><span>Difference</span></th>\n }\n @for (c of methodModelsCount() | times; track i; let i = $index) {\n <th class=\"has-border-right\">\n <span>Model {{ i + 1 }}</span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No recalculation logs to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n @if (group.term) {\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n } @else if (group.key) {\n @if (nodeKey() === 'completeness') {\n <a [href]=\"schemaBaseUrl + '/Completeness#' + group.key\" target=\"_blank\">\n <span>{{ group.key | keyToLabel }}</span>\n </a>\n } @else {\n <a [href]=\"schemaBaseUrl + '/' + nodeType() + '#' + group.key\" target=\"_blank\">\n <span>{{ group.key | keyToLabel }}</span>\n </a>\n }\n }\n </div>\n </td>\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0] }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row: group.rows[0], open: group.isOpen }\" />\n } @else {\n @if (isBlankNodes()) {\n <td class=\"has-border-right\">\n @if (group.term) {\n <span class=\"is-nowrap\" [innerHtml]=\"group.term.units | compound\"></span>\n }\n </td>\n }\n <td class=\"has-border-right\">\n @if (!isEmptyValue(group.originalValue)) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.originalValue }\" />\n } @else {\n -\n }\n </td>\n <td class=\"has-border-right\">\n @if (group.isRecalculated && !isEmptyValue(group.recalculatedValue)) {\n @if (group.recalculatedFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.recalculatedFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.recalculatedValue }\" />\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.recalculatedValue }\" />\n }\n } @else {\n -\n }\n </td>\n @if (isBlankNodes()) {\n <td class=\"has-border-right is-nowrap\">\n @if (\n group.isRecalculated && !isEmptyValue(group.originalValue) && !isEmptyValue(group.recalculatedValue)\n ) {\n <he-blank-node-value-delta\n [value]=\"group.recalculatedValue\"\n [originalValue]=\"group.originalValue\"\n [useCustomFunctions]=\"false\" />\n } @else {\n -\n }\n </td>\n }\n <td class=\"has-border-right\" [attr.colspan]=\"methodModelsCount()\">\n <span>Expand to see logs (</span>\n @if (group.allSucceeded) {\n <span>all succeeded</span>\n <he-svg-icon class=\"is-ml-1 has-text-success\" name=\"checkmark\" />\n } @else {\n <span>some failed</span>\n <he-svg-icon class=\"is-ml-1 has-text-danger\" name=\"xmark\" />\n }\n <span>)</span>\n </td>\n }\n </tr>\n\n @if (single) {\n <ng-container\n *ngTemplateOutlet=\"subRowsTemplate; context: { row: group.rows[0], open: group.isOpen, nested: false }\" />\n } @else if (group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr [class.has-sub-rows]=\"row.canOpen\" [class.is-sub-row]=\"true\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n @if (row.canOpen) {\n <a class=\"open-node\" (click)=\"toggleRow(row)\">\n <he-svg-icon [name]=\"row.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-gap-4\">\n @if (row.isFailed) {\n <span class=\"has-text-danger\">failed</span>\n } @else if (row.identitySegments.length || row.identityScalars.length) {\n @for (segment of row.identitySegments; track segment.field; let s = $index) {\n @if (s > 0) {\n <span>\u00B7</span>\n }\n <span>{{ segment.field | keyToLabel }}:</span>\n @for (term of segment.terms; track term['@id']) {\n <he-node-link class=\"is-inline-block\" [node]=\"term\">\n <span class=\"break-word\" [innerHtml]=\"term.name | compound: term.termType\"></span>\n </he-node-link>\n }\n }\n @for (scalar of row.identityScalars; track scalar.key; let i = $index) {\n <span class=\"break-word\">\n @if (i > 0 || row.identitySegments.length) {\n \u00B7\n }\n @if (scalar.field && row.type) {\n <a [href]=\"schemaBaseUrl + '/' + row.type + '#' + scalar.field\" target=\"_blank\">\n {{ scalar.key }}\n </a>\n @if (scalar.value) {\n : {{ scalar.value }}\n }\n } @else {\n {{ scalar.key }}\n @if (scalar.value) {\n : {{ scalar.value }}\n }\n }\n </span>\n }\n } @else {\n <span class=\"has-text-grey\">entry {{ row.index + 1 }}</span>\n }\n </div>\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row, open: row.isOpen }\" />\n </tr>\n <ng-container *ngTemplateOutlet=\"subRowsTemplate; context: { row, open: row.isOpen, nested: true }\" />\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<div class=\"is-size-7\">\n <div class=\"is-flex is-py-2 is-px-3 is-gap-16 | status-legend\">\n <div\n class=\"is-flex is-justify-content-center is-align-items-center is-align-content-center is-flex-wrap-wrap is-gap-8\">\n @for (status of LogStatus | keyvalue; track status.value) {\n @if (logIcon[status.value]) {\n <span class=\"is-flex is-align-items-center is-gap-8\">\n <he-svg-icon [name]=\"logIcon[status.value]\" size=\"20\" class=\"has-text-{{ logColor[status.value] }}\" />\n <span class=\"is-size-7\">{{ status.value | capitalize }}</span>\n </span>\n }\n }\n </div>\n\n @if (filteredType()) {\n <div class=\"field is-relative\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded is-secondary\"\n [(ngModel)]=\"onlyRequired\"\n [disabled]=\"!!term()\"\n [id]=\"onlyRequiredId\" />\n <label class=\"is-size-7\" [attr.for]=\"onlyRequiredId\">\n <span>Only show {{ filteredType() }} included in the default HESTIA system boundary</span>\n </label>\n </div>\n }\n </div>\n</div>\n\n<ng-template #valueCells let-row=\"row\">\n @if (isBlankNodes()) {\n <td class=\"has-border-right\">\n @if (row.term) {\n <span class=\"is-nowrap\" [innerHtml]=\"row.term.units | compound\"></span>\n }\n </td>\n }\n <td class=\"has-border-right\">\n @if (!isEmptyValue(row.originalValue)) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: row.originalValue }\" />\n } @else {\n -\n }\n </td>\n <td class=\"has-border-right\">\n @if (row.isRecalculated) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: row.recalculatedValue }\" />\n } @else if (row.key === 'backgroundData') {\n <!-- a container grouping the input's background-emission models; it has no value of its own -->\n -\n } @else if (row.isFailed || row.models.length || isEmptyValue(row.originalValue)) {\n not recalculated\n } @else {\n -\n }\n </td>\n @if (isBlankNodes()) {\n <td class=\"has-border-right is-nowrap\">\n @if (row.isOriginal && row.isRecalculated) {\n <he-blank-node-value-delta\n [value]=\"row.recalculatedValue\"\n [originalValue]=\"row.originalValue\"\n [useCustomFunctions]=\"false\" />\n } @else {\n -\n }\n </td>\n }\n</ng-template>\n\n<ng-template #subRowsTemplate let-row=\"row\" let-open=\"open\" let-nested=\"nested\">\n @if (row.canOpen && open) {\n @for (subRow of row.subRows; track trackByRow($index, subRow)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div\n class=\"is-flex is-align-items-flex-start is-flex-wrap-wrap is-gap-4\"\n [class.is-pl-3]=\"!nested\"\n [class.is-pl-5]=\"nested\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n @if (subRow.key === 'backgroundData') {\n <span>Background Data</span>\n } @else if (subRow.term) {\n <span>{{ subRow.key | keyToLabel }}:</span>\n <he-node-link class=\"is-inline-block\" [node]=\"subRow.term\">\n <span class=\"break-word\" [innerHtml]=\"subRow.term?.name | compound\"></span>\n </he-node-link>\n } @else {\n <span>Field:</span>\n @if (subRow.type) {\n <a [href]=\"schemaBaseUrl + '/' + subRow.type + '#' + subRow.key\" target=\"_blank\">{{ subRow.key }}</a>\n } @else {\n <span>{{ subRow.key }}</span>\n }\n }\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: subRow }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row: subRow, open: true }\" />\n </tr>\n }\n }\n</ng-template>\n\n<ng-template #valueContent let-value=\"value\">\n @if (isNumber(value)) {\n {{ value | precision: 3 | default: '-' }}\n } @else {\n {{ value | default: '-' }}\n }\n</ng-template>\n\n<ng-template #modelCells let-row=\"row\" let-open=\"open\">\n @if (row.canOpen && !open && row.models.length === 0) {\n <td class=\"has-border-right\" [attr.colspan]=\"methodModelsCount()\">\n <span>Expand to see logs (</span>\n @if (row.allSucceeded) {\n <span>all succeeded</span>\n <he-svg-icon class=\"is-ml-1 has-text-success\" name=\"checkmark\" />\n } @else {\n <span>some failed</span>\n <he-svg-icon class=\"is-ml-1 has-text-danger\" name=\"xmark\" />\n }\n <span>)</span>\n </td>\n } @else {\n @for (i of methodModelsCount() | times; track modelIndex; let modelIndex = $index) {\n <td class=\"has-border-right\">\n @if (row.modelColumns[modelIndex]; as column) {\n @if (isArray(column)) {\n <!-- parallel models: stacked in the same column, each with its own status -->\n @for (model of column; track model.methodId) {\n <ng-container *ngTemplateOutlet=\"modelCell; context: { model, row }\" />\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"modelCell; context: { model: column, row }\" />\n }\n } @else {\n -\n }\n </td>\n }\n }\n</ng-template>\n\n<ng-template #modelCell let-model=\"model\" let-row=\"row\">\n <div class=\"is-flex is-align-self-stretch is-justify-content-center is-align-items-center is-gap-8\">\n <div class=\"is-flex is-gap-4 is-flex-grow-1 is-align-items-center\">\n <span\n class=\"pl-1 has-text-{{ logColor[model.status] }}\"\n [class.trigger-popover]=\"hasLogs()\"\n [ngbPopover]=\"logStatusDetails\"\n [disablePopover]=\"!hasLogs()\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p1=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"$event.stopPropagation(); p1.isOpen() ? p1.close() : p1.open({ model, row })\">\n <he-svg-icon [name]=\"logIcon[model.status]\" />\n </span>\n\n <span class=\"is-flex is-flex-grow-1 is-gap-4\">\n <span class=\"is-nowrap is-capitalized\">{{ methodName(model) }}</span>\n @if (modelMethodTier(model); as methodTier) {\n <span class=\"is-nowrap\">[{{ methodTier }}]</span>\n }\n </span>\n </div>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (model.showLogs) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"logDetails\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ model, row })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n }\n @if (model.model) {\n @if (model.showLogs) {\n <div class=\"vertical-divider\"></div>\n }\n <ng-container *ngTemplateOutlet=\"docsLink; context: { $implicit: model.model }\" />\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #logDetails let-model=\"model\" let-row=\"row\">\n <he-node-logs-models-details\n [model]=\"model\"\n [value]=\"row?.recalculatedValue\"\n [node]=\"node()\"\n [nodeKey]=\"nodeKey()\"\n [hasContributions]=\"hasContributions()\" />\n</ng-template>\n\n<ng-template #logStatusDetails let-model=\"model\" let-row=\"row\">\n <he-node-logs-models-logs-status [nodeType]=\"nodeType()\" [model]=\"model\" [data]=\"$any(row)\" />\n</ng-template>\n\n<ng-template #docsLink let-model>\n @if (guideEnabled && model.guidePath) {\n <he-guide-overlay [pageId]=\"model.guidePath\" [width]=\"500\" />\n } @else {\n <a [href]=\"model.docPath || model.path\" target=\"_blank\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .status-legend{border:1px solid #dbe3ea;background:#f5f7f9}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: DataTableComponent, selector: "he-data-table", inputs: ["minHeight", "maxHeight", "small"] }, { kind: "component", type: BlankNodeValueDeltaComponent, selector: "he-blank-node-value-delta", inputs: ["value", "originalValue", "displayType", "useCustomFunctions"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "directive", type: NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "directive", type: NgbPopover, selector: "[ngbPopover]", inputs: ["animation", "autoClose", "ngbPopover", "popoverTitle", "placement", "popperOptions", "triggers", "positionTarget", "container", "disablePopover", "popoverClass", "popoverContext", "openDelay", "closeDelay"], outputs: ["shown", "hidden"], exportAs: ["ngbPopover"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: NodeLogsModelsLogsStatusComponent, selector: "he-node-logs-models-logs-status", inputs: ["nodeType", "model", "data"] }, { kind: "component", type: NodeLogsModelsDetailsComponent, selector: "he-node-logs-models-details", inputs: ["model", "value", "node", "nodeKey", "hasContributions"] }, { kind: "component", type: GuideOverlayComponent, selector: "he-guide-overlay", inputs: ["pageId", "width", "height", "positions"], outputs: ["widthChange", "heightChange"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "pipe", type: CompoundPipe, name: "compound" }, { kind: "pipe", type: DefaultPipe, name: "default" }, { kind: "pipe", type: KeyToLabelPipe, name: "keyToLabel" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }, { kind: "pipe", type: TimesPipe, name: "times" }, { kind: "pipe", type: CapitalizePipe, name: "capitalize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10804
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeJLogModelsComponent, isStandalone: true, selector: "he-node-jlog-models", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, originalValues: { classPropertyName: "originalValues", publicName: "originalValues", isSignal: true, isRequired: false, transformFunction: null }, recalculatedValues: { classPropertyName: "recalculatedValues", publicName: "recalculatedValues", isSignal: true, isRequired: false, transformFunction: null }, filterTermTypes: { classPropertyName: "filterTermTypes", publicName: "filterTermTypes", isSignal: true, isRequired: false, transformFunction: null }, filterTermTypesLabel: { classPropertyName: "filterTermTypesLabel", publicName: "filterTermTypesLabel", isSignal: true, isRequired: false, transformFunction: null }, cycle: { classPropertyName: "cycle", publicName: "cycle", isSignal: true, isRequired: false, transformFunction: null }, jlog: { classPropertyName: "jlog", publicName: "jlog", isSignal: true, isRequired: false, transformFunction: null }, jlogParentKey: { classPropertyName: "jlogParentKey", publicName: "jlogParentKey", isSignal: true, isRequired: false, transformFunction: null }, jlogParentIndex: { classPropertyName: "jlogParentIndex", publicName: "jlogParentIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\">\n @if (enableFilterByTerm()) {\n <div class=\"field is-pb-1\">\n <div class=\"control is-expanded has-icons-right\">\n <input\n class=\"input search-input is-small\"\n [ngModel]=\"term()\"\n name=\"term\"\n placeholder=\"Select entry by name\"\n [ngbTypeahead]=\"suggestTerm\"\n [resultFormatter]=\"termFormatter\"\n [inputFormatter]=\"termFormatter\"\n [focusFirst]=\"true\"\n (focus)=\"typeaheadFocus($event)\"\n (selectItem)=\"term.set($event.item)\"\n container=\"body\"\n popupClass=\"is-small\" />\n <a class=\"icon is-small is-right\" [class.is-hidden]=\"!term()\" (click)=\"term.set(undefined)\">\n <he-svg-icon name=\"xmark\" />\n </a>\n </div>\n </div>\n }\n </th>\n @if (isBlankNodes()) {\n <th class=\"has-border-right\"><span>Units</span></th>\n }\n <th class=\"has-border-right\"><span>Original</span></th>\n <th class=\"has-border-right\"><span>Recalculated</span></th>\n @if (isBlankNodes()) {\n <th class=\"has-border-right\"><span>Difference</span></th>\n }\n @for (c of methodModelsCount() | times; track i; let i = $index) {\n <th class=\"has-border-right\">\n <span>Model {{ i + 1 }}</span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No recalculation logs to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n @if (group.term) {\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n } @else if (group.key) {\n @if (nodeKey() === 'completeness') {\n <a [href]=\"schemaBaseUrl + '/Completeness#' + group.key\" target=\"_blank\">\n <span>{{ group.key | keyToLabel }}</span>\n </a>\n } @else {\n <a [href]=\"schemaBaseUrl + '/' + nodeType() + '#' + group.key\" target=\"_blank\">\n <span>{{ group.key | keyToLabel }}</span>\n </a>\n }\n }\n </div>\n </td>\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0] }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row: group.rows[0], open: group.isOpen }\" />\n } @else {\n @if (isBlankNodes()) {\n <td class=\"has-border-right\">\n @if (group.term) {\n <span class=\"is-nowrap\" [innerHtml]=\"group.term.units | compound\"></span>\n }\n </td>\n }\n <td class=\"has-border-right\">\n @if (!isEmptyValue(group.originalValue)) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.originalValue }\" />\n } @else {\n -\n }\n </td>\n <td class=\"has-border-right\">\n @if (group.isRecalculated && !isEmptyValue(group.recalculatedValue)) {\n @if (group.recalculatedFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.recalculatedFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.recalculatedValue }\" />\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.recalculatedValue }\" />\n }\n } @else {\n -\n }\n </td>\n @if (isBlankNodes()) {\n <td class=\"has-border-right is-nowrap\">\n @if (\n group.isRecalculated && !isEmptyValue(group.originalValue) && !isEmptyValue(group.recalculatedValue)\n ) {\n <he-blank-node-value-delta\n [value]=\"group.recalculatedValue\"\n [originalValue]=\"group.originalValue\"\n [useCustomFunctions]=\"false\" />\n } @else {\n -\n }\n </td>\n }\n <td class=\"has-border-right\" [attr.colspan]=\"methodModelsCount()\">\n <span>Expand to see logs (</span>\n @if (group.allSucceeded) {\n <span>all succeeded</span>\n <he-svg-icon class=\"is-ml-1 has-text-success\" name=\"checkmark\" />\n } @else {\n <span>some failed</span>\n <he-svg-icon class=\"is-ml-1 has-text-danger\" name=\"xmark\" />\n }\n <span>)</span>\n </td>\n }\n </tr>\n\n @if (single) {\n <ng-container\n *ngTemplateOutlet=\"subRowsTemplate; context: { row: group.rows[0], open: group.isOpen, nested: false }\" />\n } @else if (group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr [class.has-sub-rows]=\"row.canOpen\" [class.is-sub-row]=\"true\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n @if (row.canOpen) {\n <a class=\"open-node\" (click)=\"toggleRow(row)\">\n <he-svg-icon [name]=\"row.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n @if (row.isFailed) {\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-gap-4\">\n <span class=\"has-text-danger\">failed</span>\n </div>\n } @else {\n <he-blank-node-identity\n [segments]=\"row.identitySegments\"\n [scalars]=\"row.identityScalars\"\n [type]=\"row.type\"\n [fallback]=\"'entry ' + (row.index + 1)\" />\n }\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row, open: row.isOpen }\" />\n </tr>\n <ng-container *ngTemplateOutlet=\"subRowsTemplate; context: { row, open: row.isOpen, nested: true }\" />\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<div class=\"is-size-7\">\n <div class=\"is-flex is-py-2 is-px-3 is-gap-16 | status-legend\">\n <div\n class=\"is-flex is-justify-content-center is-align-items-center is-align-content-center is-flex-wrap-wrap is-gap-8\">\n @for (status of LogStatus | keyvalue; track status.value) {\n @if (logIcon[status.value]) {\n <span class=\"is-flex is-align-items-center is-gap-8\">\n <he-svg-icon [name]=\"logIcon[status.value]\" size=\"20\" class=\"has-text-{{ logColor[status.value] }}\" />\n <span class=\"is-size-7\">{{ status.value | capitalize }}</span>\n </span>\n }\n }\n </div>\n\n @if (filteredType()) {\n <div class=\"field is-relative\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded is-secondary\"\n [(ngModel)]=\"onlyRequired\"\n [disabled]=\"!!term()\"\n [id]=\"onlyRequiredId\" />\n <label class=\"is-size-7\" [attr.for]=\"onlyRequiredId\">\n <span>Only show {{ filteredType() }} included in the default HESTIA system boundary</span>\n </label>\n </div>\n }\n </div>\n</div>\n\n<ng-template #valueCells let-row=\"row\">\n @if (isBlankNodes()) {\n <td class=\"has-border-right\">\n @if (row.term) {\n <span class=\"is-nowrap\" [innerHtml]=\"row.term.units | compound\"></span>\n }\n </td>\n }\n <td class=\"has-border-right\">\n @if (!isEmptyValue(row.originalValue)) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: row.originalValue }\" />\n } @else {\n -\n }\n </td>\n <td class=\"has-border-right\">\n @if (row.isRecalculated) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: row.recalculatedValue }\" />\n } @else if (row.key === 'backgroundData') {\n <!-- a container grouping the input's background-emission models; it has no value of its own -->\n -\n } @else if (row.isFailed || row.models.length || isEmptyValue(row.originalValue)) {\n not recalculated\n } @else {\n -\n }\n </td>\n @if (isBlankNodes()) {\n <td class=\"has-border-right is-nowrap\">\n @if (row.isOriginal && row.isRecalculated) {\n <he-blank-node-value-delta\n [value]=\"row.recalculatedValue\"\n [originalValue]=\"row.originalValue\"\n [useCustomFunctions]=\"false\" />\n } @else {\n -\n }\n </td>\n }\n</ng-template>\n\n<ng-template #subRowsTemplate let-row=\"row\" let-open=\"open\" let-nested=\"nested\">\n @if (row.canOpen && open) {\n @for (subRow of row.subRows; track trackByRow($index, subRow)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div\n class=\"is-flex is-align-items-flex-start is-flex-wrap-wrap is-gap-4\"\n [class.is-pl-3]=\"!nested\"\n [class.is-pl-5]=\"nested\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n @if (subRow.key === 'backgroundData') {\n <span>Background Data</span>\n } @else if (subRow.term) {\n <span>{{ subRow.key | keyToLabel }}:</span>\n <he-node-link class=\"is-inline-block\" [node]=\"subRow.term\">\n <span class=\"break-word\" [innerHtml]=\"subRow.term?.name | compound\"></span>\n </he-node-link>\n } @else {\n <span>Field:</span>\n @if (subRow.type) {\n <a [href]=\"schemaBaseUrl + '/' + subRow.type + '#' + subRow.key\" target=\"_blank\">{{ subRow.key }}</a>\n } @else {\n <span>{{ subRow.key }}</span>\n }\n }\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: subRow }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row: subRow, open: true }\" />\n </tr>\n }\n }\n</ng-template>\n\n<ng-template #valueContent let-value=\"value\">\n @if (isNumber(value)) {\n {{ value | precision: 3 | default: '-' }}\n } @else {\n {{ value | default: '-' }}\n }\n</ng-template>\n\n<ng-template #modelCells let-row=\"row\" let-open=\"open\">\n @if (row.canOpen && !open && row.models.length === 0) {\n <td class=\"has-border-right\" [attr.colspan]=\"methodModelsCount()\">\n <span>Expand to see logs (</span>\n @if (row.allSucceeded) {\n <span>all succeeded</span>\n <he-svg-icon class=\"is-ml-1 has-text-success\" name=\"checkmark\" />\n } @else {\n <span>some failed</span>\n <he-svg-icon class=\"is-ml-1 has-text-danger\" name=\"xmark\" />\n }\n <span>)</span>\n </td>\n } @else {\n @for (i of methodModelsCount() | times; track modelIndex; let modelIndex = $index) {\n <td class=\"has-border-right\">\n @if (row.modelColumns[modelIndex]; as column) {\n @if (isArray(column)) {\n <!-- parallel models: stacked in the same column, each with its own status -->\n @for (model of column; track model.methodId) {\n <ng-container *ngTemplateOutlet=\"modelCell; context: { model, row }\" />\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"modelCell; context: { model: column, row }\" />\n }\n } @else {\n -\n }\n </td>\n }\n }\n</ng-template>\n\n<ng-template #modelCell let-model=\"model\" let-row=\"row\">\n <div class=\"is-flex is-align-self-stretch is-justify-content-center is-align-items-center is-gap-8\">\n <div class=\"is-flex is-gap-4 is-flex-grow-1 is-align-items-center\">\n <span\n class=\"pl-1 has-text-{{ logColor[model.status] }}\"\n [class.trigger-popover]=\"hasLogs()\"\n [ngbPopover]=\"logStatusDetails\"\n [disablePopover]=\"!hasLogs()\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p1=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"$event.stopPropagation(); p1.isOpen() ? p1.close() : p1.open({ model, row })\">\n <he-svg-icon [name]=\"logIcon[model.status]\" />\n </span>\n\n <span class=\"is-flex is-flex-grow-1 is-gap-4\">\n <span class=\"is-nowrap is-capitalized\">{{ methodName(model) }}</span>\n @if (modelMethodTier(model); as methodTier) {\n <span class=\"is-nowrap\">[{{ methodTier }}]</span>\n }\n </span>\n </div>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (model.showLogs) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"logDetails\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ model, row })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n }\n @if (model.model) {\n @if (model.showLogs) {\n <div class=\"vertical-divider\"></div>\n }\n <ng-container *ngTemplateOutlet=\"docsLink; context: { $implicit: model.model }\" />\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #logDetails let-model=\"model\" let-row=\"row\">\n <he-node-logs-models-details\n [model]=\"model\"\n [value]=\"row?.recalculatedValue\"\n [node]=\"node()\"\n [nodeKey]=\"nodeKey()\"\n [hasContributions]=\"hasContributions()\" />\n</ng-template>\n\n<ng-template #logStatusDetails let-model=\"model\" let-row=\"row\">\n <he-node-logs-models-logs-status [nodeType]=\"nodeType()\" [model]=\"model\" [data]=\"$any(row)\" />\n</ng-template>\n\n<ng-template #docsLink let-model>\n @if (guideEnabled && model.guidePath) {\n <he-guide-overlay [pageId]=\"model.guidePath\" [width]=\"500\" />\n } @else {\n <a [href]=\"model.docPath || model.path\" target=\"_blank\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}:host .status-legend{border:1px solid #dbe3ea;background:#f5f7f9}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: DataTableComponent, selector: "he-data-table", inputs: ["minHeight", "maxHeight", "small"] }, { kind: "component", type: BlankNodeValueDeltaComponent, selector: "he-blank-node-value-delta", inputs: ["value", "originalValue", "displayType", "useCustomFunctions"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "directive", type: NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "directive", type: NgbPopover, selector: "[ngbPopover]", inputs: ["animation", "autoClose", "ngbPopover", "popoverTitle", "placement", "popperOptions", "triggers", "positionTarget", "container", "disablePopover", "popoverClass", "popoverContext", "openDelay", "closeDelay"], outputs: ["shown", "hidden"], exportAs: ["ngbPopover"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: NodeLogsModelsLogsStatusComponent, selector: "he-node-logs-models-logs-status", inputs: ["nodeType", "model", "data"] }, { kind: "component", type: NodeLogsModelsDetailsComponent, selector: "he-node-logs-models-details", inputs: ["model", "value", "node", "nodeKey", "hasContributions"] }, { kind: "component", type: BlankNodeIdentityComponent, selector: "he-blank-node-identity", inputs: ["segments", "scalars", "type", "fallback"] }, { kind: "component", type: GuideOverlayComponent, selector: "he-guide-overlay", inputs: ["pageId", "width", "height", "positions"], outputs: ["widthChange", "heightChange"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "pipe", type: CompoundPipe, name: "compound" }, { kind: "pipe", type: DefaultPipe, name: "default" }, { kind: "pipe", type: KeyToLabelPipe, name: "keyToLabel" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }, { kind: "pipe", type: TimesPipe, name: "times" }, { kind: "pipe", type: CapitalizePipe, name: "capitalize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
10616
10805
  }
10617
10806
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeJLogModelsComponent, decorators: [{
10618
10807
  type: Component$1,
@@ -10634,8 +10823,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
10634
10823
  HESvgIconComponent,
10635
10824
  NodeLogsModelsLogsStatusComponent,
10636
10825
  NodeLogsModelsDetailsComponent,
10826
+ BlankNodeIdentityComponent,
10637
10827
  GuideOverlayComponent
10638
- ], template: "<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\">\n @if (enableFilterByTerm()) {\n <div class=\"field is-pb-1\">\n <div class=\"control is-expanded has-icons-right\">\n <input\n class=\"input search-input is-small\"\n [ngModel]=\"term()\"\n name=\"term\"\n placeholder=\"Select entry by name\"\n [ngbTypeahead]=\"suggestTerm\"\n [resultFormatter]=\"termFormatter\"\n [inputFormatter]=\"termFormatter\"\n [focusFirst]=\"true\"\n (focus)=\"typeaheadFocus($event)\"\n (selectItem)=\"term.set($event.item)\"\n container=\"body\"\n popupClass=\"is-small\" />\n <a class=\"icon is-small is-right\" [class.is-hidden]=\"!term()\" (click)=\"term.set(undefined)\">\n <he-svg-icon name=\"xmark\" />\n </a>\n </div>\n </div>\n }\n </th>\n @if (isBlankNodes()) {\n <th class=\"has-border-right\"><span>Units</span></th>\n }\n <th class=\"has-border-right\"><span>Original</span></th>\n <th class=\"has-border-right\"><span>Recalculated</span></th>\n @if (isBlankNodes()) {\n <th class=\"has-border-right\"><span>Difference</span></th>\n }\n @for (c of methodModelsCount() | times; track i; let i = $index) {\n <th class=\"has-border-right\">\n <span>Model {{ i + 1 }}</span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No recalculation logs to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n @if (group.term) {\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n } @else if (group.key) {\n @if (nodeKey() === 'completeness') {\n <a [href]=\"schemaBaseUrl + '/Completeness#' + group.key\" target=\"_blank\">\n <span>{{ group.key | keyToLabel }}</span>\n </a>\n } @else {\n <a [href]=\"schemaBaseUrl + '/' + nodeType() + '#' + group.key\" target=\"_blank\">\n <span>{{ group.key | keyToLabel }}</span>\n </a>\n }\n }\n </div>\n </td>\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0] }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row: group.rows[0], open: group.isOpen }\" />\n } @else {\n @if (isBlankNodes()) {\n <td class=\"has-border-right\">\n @if (group.term) {\n <span class=\"is-nowrap\" [innerHtml]=\"group.term.units | compound\"></span>\n }\n </td>\n }\n <td class=\"has-border-right\">\n @if (!isEmptyValue(group.originalValue)) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.originalValue }\" />\n } @else {\n -\n }\n </td>\n <td class=\"has-border-right\">\n @if (group.isRecalculated && !isEmptyValue(group.recalculatedValue)) {\n @if (group.recalculatedFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.recalculatedFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.recalculatedValue }\" />\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.recalculatedValue }\" />\n }\n } @else {\n -\n }\n </td>\n @if (isBlankNodes()) {\n <td class=\"has-border-right is-nowrap\">\n @if (\n group.isRecalculated && !isEmptyValue(group.originalValue) && !isEmptyValue(group.recalculatedValue)\n ) {\n <he-blank-node-value-delta\n [value]=\"group.recalculatedValue\"\n [originalValue]=\"group.originalValue\"\n [useCustomFunctions]=\"false\" />\n } @else {\n -\n }\n </td>\n }\n <td class=\"has-border-right\" [attr.colspan]=\"methodModelsCount()\">\n <span>Expand to see logs (</span>\n @if (group.allSucceeded) {\n <span>all succeeded</span>\n <he-svg-icon class=\"is-ml-1 has-text-success\" name=\"checkmark\" />\n } @else {\n <span>some failed</span>\n <he-svg-icon class=\"is-ml-1 has-text-danger\" name=\"xmark\" />\n }\n <span>)</span>\n </td>\n }\n </tr>\n\n @if (single) {\n <ng-container\n *ngTemplateOutlet=\"subRowsTemplate; context: { row: group.rows[0], open: group.isOpen, nested: false }\" />\n } @else if (group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr [class.has-sub-rows]=\"row.canOpen\" [class.is-sub-row]=\"true\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n @if (row.canOpen) {\n <a class=\"open-node\" (click)=\"toggleRow(row)\">\n <he-svg-icon [name]=\"row.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-gap-4\">\n @if (row.isFailed) {\n <span class=\"has-text-danger\">failed</span>\n } @else if (row.identitySegments.length || row.identityScalars.length) {\n @for (segment of row.identitySegments; track segment.field; let s = $index) {\n @if (s > 0) {\n <span>\u00B7</span>\n }\n <span>{{ segment.field | keyToLabel }}:</span>\n @for (term of segment.terms; track term['@id']) {\n <he-node-link class=\"is-inline-block\" [node]=\"term\">\n <span class=\"break-word\" [innerHtml]=\"term.name | compound: term.termType\"></span>\n </he-node-link>\n }\n }\n @for (scalar of row.identityScalars; track scalar.key; let i = $index) {\n <span class=\"break-word\">\n @if (i > 0 || row.identitySegments.length) {\n \u00B7\n }\n @if (scalar.field && row.type) {\n <a [href]=\"schemaBaseUrl + '/' + row.type + '#' + scalar.field\" target=\"_blank\">\n {{ scalar.key }}\n </a>\n @if (scalar.value) {\n : {{ scalar.value }}\n }\n } @else {\n {{ scalar.key }}\n @if (scalar.value) {\n : {{ scalar.value }}\n }\n }\n </span>\n }\n } @else {\n <span class=\"has-text-grey\">entry {{ row.index + 1 }}</span>\n }\n </div>\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row, open: row.isOpen }\" />\n </tr>\n <ng-container *ngTemplateOutlet=\"subRowsTemplate; context: { row, open: row.isOpen, nested: true }\" />\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<div class=\"is-size-7\">\n <div class=\"is-flex is-py-2 is-px-3 is-gap-16 | status-legend\">\n <div\n class=\"is-flex is-justify-content-center is-align-items-center is-align-content-center is-flex-wrap-wrap is-gap-8\">\n @for (status of LogStatus | keyvalue; track status.value) {\n @if (logIcon[status.value]) {\n <span class=\"is-flex is-align-items-center is-gap-8\">\n <he-svg-icon [name]=\"logIcon[status.value]\" size=\"20\" class=\"has-text-{{ logColor[status.value] }}\" />\n <span class=\"is-size-7\">{{ status.value | capitalize }}</span>\n </span>\n }\n }\n </div>\n\n @if (filteredType()) {\n <div class=\"field is-relative\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded is-secondary\"\n [(ngModel)]=\"onlyRequired\"\n [disabled]=\"!!term()\"\n [id]=\"onlyRequiredId\" />\n <label class=\"is-size-7\" [attr.for]=\"onlyRequiredId\">\n <span>Only show {{ filteredType() }} included in the default HESTIA system boundary</span>\n </label>\n </div>\n }\n </div>\n</div>\n\n<ng-template #valueCells let-row=\"row\">\n @if (isBlankNodes()) {\n <td class=\"has-border-right\">\n @if (row.term) {\n <span class=\"is-nowrap\" [innerHtml]=\"row.term.units | compound\"></span>\n }\n </td>\n }\n <td class=\"has-border-right\">\n @if (!isEmptyValue(row.originalValue)) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: row.originalValue }\" />\n } @else {\n -\n }\n </td>\n <td class=\"has-border-right\">\n @if (row.isRecalculated) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: row.recalculatedValue }\" />\n } @else if (row.key === 'backgroundData') {\n <!-- a container grouping the input's background-emission models; it has no value of its own -->\n -\n } @else if (row.isFailed || row.models.length || isEmptyValue(row.originalValue)) {\n not recalculated\n } @else {\n -\n }\n </td>\n @if (isBlankNodes()) {\n <td class=\"has-border-right is-nowrap\">\n @if (row.isOriginal && row.isRecalculated) {\n <he-blank-node-value-delta\n [value]=\"row.recalculatedValue\"\n [originalValue]=\"row.originalValue\"\n [useCustomFunctions]=\"false\" />\n } @else {\n -\n }\n </td>\n }\n</ng-template>\n\n<ng-template #subRowsTemplate let-row=\"row\" let-open=\"open\" let-nested=\"nested\">\n @if (row.canOpen && open) {\n @for (subRow of row.subRows; track trackByRow($index, subRow)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div\n class=\"is-flex is-align-items-flex-start is-flex-wrap-wrap is-gap-4\"\n [class.is-pl-3]=\"!nested\"\n [class.is-pl-5]=\"nested\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n @if (subRow.key === 'backgroundData') {\n <span>Background Data</span>\n } @else if (subRow.term) {\n <span>{{ subRow.key | keyToLabel }}:</span>\n <he-node-link class=\"is-inline-block\" [node]=\"subRow.term\">\n <span class=\"break-word\" [innerHtml]=\"subRow.term?.name | compound\"></span>\n </he-node-link>\n } @else {\n <span>Field:</span>\n @if (subRow.type) {\n <a [href]=\"schemaBaseUrl + '/' + subRow.type + '#' + subRow.key\" target=\"_blank\">{{ subRow.key }}</a>\n } @else {\n <span>{{ subRow.key }}</span>\n }\n }\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: subRow }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row: subRow, open: true }\" />\n </tr>\n }\n }\n</ng-template>\n\n<ng-template #valueContent let-value=\"value\">\n @if (isNumber(value)) {\n {{ value | precision: 3 | default: '-' }}\n } @else {\n {{ value | default: '-' }}\n }\n</ng-template>\n\n<ng-template #modelCells let-row=\"row\" let-open=\"open\">\n @if (row.canOpen && !open && row.models.length === 0) {\n <td class=\"has-border-right\" [attr.colspan]=\"methodModelsCount()\">\n <span>Expand to see logs (</span>\n @if (row.allSucceeded) {\n <span>all succeeded</span>\n <he-svg-icon class=\"is-ml-1 has-text-success\" name=\"checkmark\" />\n } @else {\n <span>some failed</span>\n <he-svg-icon class=\"is-ml-1 has-text-danger\" name=\"xmark\" />\n }\n <span>)</span>\n </td>\n } @else {\n @for (i of methodModelsCount() | times; track modelIndex; let modelIndex = $index) {\n <td class=\"has-border-right\">\n @if (row.modelColumns[modelIndex]; as column) {\n @if (isArray(column)) {\n <!-- parallel models: stacked in the same column, each with its own status -->\n @for (model of column; track model.methodId) {\n <ng-container *ngTemplateOutlet=\"modelCell; context: { model, row }\" />\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"modelCell; context: { model: column, row }\" />\n }\n } @else {\n -\n }\n </td>\n }\n }\n</ng-template>\n\n<ng-template #modelCell let-model=\"model\" let-row=\"row\">\n <div class=\"is-flex is-align-self-stretch is-justify-content-center is-align-items-center is-gap-8\">\n <div class=\"is-flex is-gap-4 is-flex-grow-1 is-align-items-center\">\n <span\n class=\"pl-1 has-text-{{ logColor[model.status] }}\"\n [class.trigger-popover]=\"hasLogs()\"\n [ngbPopover]=\"logStatusDetails\"\n [disablePopover]=\"!hasLogs()\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p1=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"$event.stopPropagation(); p1.isOpen() ? p1.close() : p1.open({ model, row })\">\n <he-svg-icon [name]=\"logIcon[model.status]\" />\n </span>\n\n <span class=\"is-flex is-flex-grow-1 is-gap-4\">\n <span class=\"is-nowrap is-capitalized\">{{ methodName(model) }}</span>\n @if (modelMethodTier(model); as methodTier) {\n <span class=\"is-nowrap\">[{{ methodTier }}]</span>\n }\n </span>\n </div>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (model.showLogs) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"logDetails\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ model, row })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n }\n @if (model.model) {\n @if (model.showLogs) {\n <div class=\"vertical-divider\"></div>\n }\n <ng-container *ngTemplateOutlet=\"docsLink; context: { $implicit: model.model }\" />\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #logDetails let-model=\"model\" let-row=\"row\">\n <he-node-logs-models-details\n [model]=\"model\"\n [value]=\"row?.recalculatedValue\"\n [node]=\"node()\"\n [nodeKey]=\"nodeKey()\"\n [hasContributions]=\"hasContributions()\" />\n</ng-template>\n\n<ng-template #logStatusDetails let-model=\"model\" let-row=\"row\">\n <he-node-logs-models-logs-status [nodeType]=\"nodeType()\" [model]=\"model\" [data]=\"$any(row)\" />\n</ng-template>\n\n<ng-template #docsLink let-model>\n @if (guideEnabled && model.guidePath) {\n <he-guide-overlay [pageId]=\"model.guidePath\" [width]=\"500\" />\n } @else {\n <a [href]=\"model.docPath || model.path\" target=\"_blank\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .status-legend{border:1px solid #dbe3ea;background:#f5f7f9}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}\n"] }]
10828
+ ], template: "<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\">\n @if (enableFilterByTerm()) {\n <div class=\"field is-pb-1\">\n <div class=\"control is-expanded has-icons-right\">\n <input\n class=\"input search-input is-small\"\n [ngModel]=\"term()\"\n name=\"term\"\n placeholder=\"Select entry by name\"\n [ngbTypeahead]=\"suggestTerm\"\n [resultFormatter]=\"termFormatter\"\n [inputFormatter]=\"termFormatter\"\n [focusFirst]=\"true\"\n (focus)=\"typeaheadFocus($event)\"\n (selectItem)=\"term.set($event.item)\"\n container=\"body\"\n popupClass=\"is-small\" />\n <a class=\"icon is-small is-right\" [class.is-hidden]=\"!term()\" (click)=\"term.set(undefined)\">\n <he-svg-icon name=\"xmark\" />\n </a>\n </div>\n </div>\n }\n </th>\n @if (isBlankNodes()) {\n <th class=\"has-border-right\"><span>Units</span></th>\n }\n <th class=\"has-border-right\"><span>Original</span></th>\n <th class=\"has-border-right\"><span>Recalculated</span></th>\n @if (isBlankNodes()) {\n <th class=\"has-border-right\"><span>Difference</span></th>\n }\n @for (c of methodModelsCount() | times; track i; let i = $index) {\n <th class=\"has-border-right\">\n <span>Model {{ i + 1 }}</span>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No recalculation logs to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n @if (group.term) {\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n } @else if (group.key) {\n @if (nodeKey() === 'completeness') {\n <a [href]=\"schemaBaseUrl + '/Completeness#' + group.key\" target=\"_blank\">\n <span>{{ group.key | keyToLabel }}</span>\n </a>\n } @else {\n <a [href]=\"schemaBaseUrl + '/' + nodeType() + '#' + group.key\" target=\"_blank\">\n <span>{{ group.key | keyToLabel }}</span>\n </a>\n }\n }\n </div>\n </td>\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0] }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row: group.rows[0], open: group.isOpen }\" />\n } @else {\n @if (isBlankNodes()) {\n <td class=\"has-border-right\">\n @if (group.term) {\n <span class=\"is-nowrap\" [innerHtml]=\"group.term.units | compound\"></span>\n }\n </td>\n }\n <td class=\"has-border-right\">\n @if (!isEmptyValue(group.originalValue)) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.originalValue }\" />\n } @else {\n -\n }\n </td>\n <td class=\"has-border-right\">\n @if (group.isRecalculated && !isEmptyValue(group.recalculatedValue)) {\n @if (group.recalculatedFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.recalculatedFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.recalculatedValue }\" />\n </span>\n } @else {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: group.recalculatedValue }\" />\n }\n } @else {\n -\n }\n </td>\n @if (isBlankNodes()) {\n <td class=\"has-border-right is-nowrap\">\n @if (\n group.isRecalculated && !isEmptyValue(group.originalValue) && !isEmptyValue(group.recalculatedValue)\n ) {\n <he-blank-node-value-delta\n [value]=\"group.recalculatedValue\"\n [originalValue]=\"group.originalValue\"\n [useCustomFunctions]=\"false\" />\n } @else {\n -\n }\n </td>\n }\n <td class=\"has-border-right\" [attr.colspan]=\"methodModelsCount()\">\n <span>Expand to see logs (</span>\n @if (group.allSucceeded) {\n <span>all succeeded</span>\n <he-svg-icon class=\"is-ml-1 has-text-success\" name=\"checkmark\" />\n } @else {\n <span>some failed</span>\n <he-svg-icon class=\"is-ml-1 has-text-danger\" name=\"xmark\" />\n }\n <span>)</span>\n </td>\n }\n </tr>\n\n @if (single) {\n <ng-container\n *ngTemplateOutlet=\"subRowsTemplate; context: { row: group.rows[0], open: group.isOpen, nested: false }\" />\n } @else if (group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr [class.has-sub-rows]=\"row.canOpen\" [class.is-sub-row]=\"true\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n @if (row.canOpen) {\n <a class=\"open-node\" (click)=\"toggleRow(row)\">\n <he-svg-icon [name]=\"row.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n @if (row.isFailed) {\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-gap-4\">\n <span class=\"has-text-danger\">failed</span>\n </div>\n } @else {\n <he-blank-node-identity\n [segments]=\"row.identitySegments\"\n [scalars]=\"row.identityScalars\"\n [type]=\"row.type\"\n [fallback]=\"'entry ' + (row.index + 1)\" />\n }\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row, open: row.isOpen }\" />\n </tr>\n <ng-container *ngTemplateOutlet=\"subRowsTemplate; context: { row, open: row.isOpen, nested: true }\" />\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<div class=\"is-size-7\">\n <div class=\"is-flex is-py-2 is-px-3 is-gap-16 | status-legend\">\n <div\n class=\"is-flex is-justify-content-center is-align-items-center is-align-content-center is-flex-wrap-wrap is-gap-8\">\n @for (status of LogStatus | keyvalue; track status.value) {\n @if (logIcon[status.value]) {\n <span class=\"is-flex is-align-items-center is-gap-8\">\n <he-svg-icon [name]=\"logIcon[status.value]\" size=\"20\" class=\"has-text-{{ logColor[status.value] }}\" />\n <span class=\"is-size-7\">{{ status.value | capitalize }}</span>\n </span>\n }\n }\n </div>\n\n @if (filteredType()) {\n <div class=\"field is-relative\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded is-secondary\"\n [(ngModel)]=\"onlyRequired\"\n [disabled]=\"!!term()\"\n [id]=\"onlyRequiredId\" />\n <label class=\"is-size-7\" [attr.for]=\"onlyRequiredId\">\n <span>Only show {{ filteredType() }} included in the default HESTIA system boundary</span>\n </label>\n </div>\n }\n </div>\n</div>\n\n<ng-template #valueCells let-row=\"row\">\n @if (isBlankNodes()) {\n <td class=\"has-border-right\">\n @if (row.term) {\n <span class=\"is-nowrap\" [innerHtml]=\"row.term.units | compound\"></span>\n }\n </td>\n }\n <td class=\"has-border-right\">\n @if (!isEmptyValue(row.originalValue)) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: row.originalValue }\" />\n } @else {\n -\n }\n </td>\n <td class=\"has-border-right\">\n @if (row.isRecalculated) {\n <ng-container *ngTemplateOutlet=\"valueContent; context: { value: row.recalculatedValue }\" />\n } @else if (row.key === 'backgroundData') {\n <!-- a container grouping the input's background-emission models; it has no value of its own -->\n -\n } @else if (row.isFailed || row.models.length || isEmptyValue(row.originalValue)) {\n not recalculated\n } @else {\n -\n }\n </td>\n @if (isBlankNodes()) {\n <td class=\"has-border-right is-nowrap\">\n @if (row.isOriginal && row.isRecalculated) {\n <he-blank-node-value-delta\n [value]=\"row.recalculatedValue\"\n [originalValue]=\"row.originalValue\"\n [useCustomFunctions]=\"false\" />\n } @else {\n -\n }\n </td>\n }\n</ng-template>\n\n<ng-template #subRowsTemplate let-row=\"row\" let-open=\"open\" let-nested=\"nested\">\n @if (row.canOpen && open) {\n @for (subRow of row.subRows; track trackByRow($index, subRow)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div\n class=\"is-flex is-align-items-flex-start is-flex-wrap-wrap is-gap-4\"\n [class.is-pl-3]=\"!nested\"\n [class.is-pl-5]=\"nested\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n @if (subRow.key === 'backgroundData') {\n <span>Background Data</span>\n } @else if (subRow.term) {\n <span>{{ subRow.key | keyToLabel }}:</span>\n <he-node-link class=\"is-inline-block\" [node]=\"subRow.term\">\n <span class=\"break-word\" [innerHtml]=\"subRow.term?.name | compound\"></span>\n </he-node-link>\n } @else {\n <span>Field:</span>\n @if (subRow.type) {\n <a [href]=\"schemaBaseUrl + '/' + subRow.type + '#' + subRow.key\" target=\"_blank\">{{ subRow.key }}</a>\n } @else {\n <span>{{ subRow.key }}</span>\n }\n }\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: subRow }\" />\n <ng-container *ngTemplateOutlet=\"modelCells; context: { row: subRow, open: true }\" />\n </tr>\n }\n }\n</ng-template>\n\n<ng-template #valueContent let-value=\"value\">\n @if (isNumber(value)) {\n {{ value | precision: 3 | default: '-' }}\n } @else {\n {{ value | default: '-' }}\n }\n</ng-template>\n\n<ng-template #modelCells let-row=\"row\" let-open=\"open\">\n @if (row.canOpen && !open && row.models.length === 0) {\n <td class=\"has-border-right\" [attr.colspan]=\"methodModelsCount()\">\n <span>Expand to see logs (</span>\n @if (row.allSucceeded) {\n <span>all succeeded</span>\n <he-svg-icon class=\"is-ml-1 has-text-success\" name=\"checkmark\" />\n } @else {\n <span>some failed</span>\n <he-svg-icon class=\"is-ml-1 has-text-danger\" name=\"xmark\" />\n }\n <span>)</span>\n </td>\n } @else {\n @for (i of methodModelsCount() | times; track modelIndex; let modelIndex = $index) {\n <td class=\"has-border-right\">\n @if (row.modelColumns[modelIndex]; as column) {\n @if (isArray(column)) {\n <!-- parallel models: stacked in the same column, each with its own status -->\n @for (model of column; track model.methodId) {\n <ng-container *ngTemplateOutlet=\"modelCell; context: { model, row }\" />\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"modelCell; context: { model: column, row }\" />\n }\n } @else {\n -\n }\n </td>\n }\n }\n</ng-template>\n\n<ng-template #modelCell let-model=\"model\" let-row=\"row\">\n <div class=\"is-flex is-align-self-stretch is-justify-content-center is-align-items-center is-gap-8\">\n <div class=\"is-flex is-gap-4 is-flex-grow-1 is-align-items-center\">\n <span\n class=\"pl-1 has-text-{{ logColor[model.status] }}\"\n [class.trigger-popover]=\"hasLogs()\"\n [ngbPopover]=\"logStatusDetails\"\n [disablePopover]=\"!hasLogs()\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p1=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"$event.stopPropagation(); p1.isOpen() ? p1.close() : p1.open({ model, row })\">\n <he-svg-icon [name]=\"logIcon[model.status]\" />\n </span>\n\n <span class=\"is-flex is-flex-grow-1 is-gap-4\">\n <span class=\"is-nowrap is-capitalized\">{{ methodName(model) }}</span>\n @if (modelMethodTier(model); as methodTier) {\n <span class=\"is-nowrap\">[{{ methodTier }}]</span>\n }\n </span>\n </div>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (model.showLogs) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"logDetails\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ model, row })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n }\n @if (model.model) {\n @if (model.showLogs) {\n <div class=\"vertical-divider\"></div>\n }\n <ng-container *ngTemplateOutlet=\"docsLink; context: { $implicit: model.model }\" />\n }\n </div>\n </div>\n</ng-template>\n\n<ng-template #logDetails let-model=\"model\" let-row=\"row\">\n <he-node-logs-models-details\n [model]=\"model\"\n [value]=\"row?.recalculatedValue\"\n [node]=\"node()\"\n [nodeKey]=\"nodeKey()\"\n [hasContributions]=\"hasContributions()\" />\n</ng-template>\n\n<ng-template #logStatusDetails let-model=\"model\" let-row=\"row\">\n <he-node-logs-models-logs-status [nodeType]=\"nodeType()\" [model]=\"model\" [data]=\"$any(row)\" />\n</ng-template>\n\n<ng-template #docsLink let-model>\n @if (guideEnabled && model.guidePath) {\n <he-guide-overlay [pageId]=\"model.guidePath\" [width]=\"500\" />\n } @else {\n <a [href]=\"model.docPath || model.path\" target=\"_blank\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}:host .status-legend{border:1px solid #dbe3ea;background:#f5f7f9}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}\n"] }]
10639
10829
  }], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], originalValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "originalValues", required: false }] }], recalculatedValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "recalculatedValues", required: false }] }], filterTermTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterTermTypes", required: false }] }], filterTermTypesLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterTermTypesLabel", required: false }] }], cycle: [{ type: i0.Input, args: [{ isSignal: true, alias: "cycle", required: false }] }], jlog: [{ type: i0.Input, args: [{ isSignal: true, alias: "jlog", required: false }] }], jlogParentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "jlogParentKey", required: false }] }], jlogParentIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "jlogParentIndex", required: false }] }] } });
10640
10830
 
10641
10831
  const groupTerms = (terms) => terms.reduce((prev, curr) => ({ ...prev, [curr['@id']]: curr }), {});
@@ -11375,6 +11565,482 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
11375
11565
  ], template: "@if (showInline()) {\n @for (key of keys; track key) {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\" />\n }\n @for (key of additionalKeys; track key) {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\" />\n }\n\n <ng-container *ngTemplateOutlet=\"showModels\" />\n\n @if (node().distribution?.length) {\n <p>\n <a class=\"is-dark\" [href]=\"schemaBaseUrl + '/' + node['@type'] + '#distribution'\" target=\"_blank\">\n <b>distribution</b>\n </a>\n <span class=\"pr-2\">:</span>\n <ng-container *ngTemplateOutlet=\"distributionContent; context: { node: node() }\" />\n </p>\n }\n} @else {\n <he-link-key-value\n [node]=\"node()\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n key=\"term\" />\n\n <ng-container *ngTemplateOutlet=\"showModels\" />\n\n <div class=\"columns is-p-0 is-my-0 is-overflow-visible\">\n <div class=\"column is-p-0 is-my-0\"></div>\n <div class=\"column is-p-0 is-my-0 is-narrow is-overflow-visible\">\n <div ngbDropdown class=\"is-overflow-visible\" autoClose=\"outside\" placement=\"bottom-end\">\n <button\n ngbDropdownToggle\n class=\"button is-small is-ghost has-text-white\"\n type=\"button\"\n aria-controls=\"config-menu\">\n <span>Customise fields</span>\n <span class=\"icon is-small\">\n <he-svg-icon name=\"settings\" aria-hidden=\"true\" />\n </span>\n </button>\n\n <div ngbDropdownMenu id=\"config-menu\">\n <div\n class=\"dropdown-content is-overflow-y-auto\"\n (click)=\"$event.stopPropagation()\"\n cdkDropList\n (cdkDropListDropped)=\"dropTableKey($event)\">\n @for (key of tableKeys(); track key.key; let keyIndex = $index) {\n <div class=\"dropdown-item cdk-drag-item\" cdkDrag>\n <div class=\"field is-relative\">\n <input\n type=\"checkbox\"\n class=\"selector\"\n [id]=\"key.key\"\n [name]=\"key.key\"\n [checked]=\"key.selected\"\n (change)=\"onTableKeyChange(key, keyIndex, $event.target.checked)\" />\n <label class=\"is-pl-2\" [for]=\"key.key\">{{ key.key }}</label>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"table-container is-mt-2\">\n <table class=\"table is-dark is-narrow is-striped\">\n <thead>\n @for (key of visibleTableKeys(); track key) {\n <th>\n <a class=\"is-dark\" [href]=\"schemaBaseUrl + '/' + type() + '#' + key.split('.')[0]\" target=\"_blank\">\n <b>{{ key.includes('.') ? key.split('.')[1] : key }}</b>\n </a>\n </th>\n }\n </thead>\n <tbody>\n @for (node of nodes(); track node) {\n <tr>\n @for (key of visibleTableKeys(); track key) {\n <td>\n @if (key === 'distribution') {\n <ng-container *ngTemplateOutlet=\"distributionContent; context: { node }\" />\n } @else {\n <he-link-key-value\n [node]=\"node\"\n [nodeType]=\"nodeType()\"\n [dataState]=\"dataState()\"\n [dataKey]=\"dataKey()\"\n [key]=\"key\"\n [defaultValue]=\"defaultValue(key)\" />\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n}\n\n<ng-template #distributionContent let-node=\"node\">\n @if (node.distribution?.length) {\n @if (showDistribution() === node) {\n <a class=\"has-text-white\" (click)=\"showDistribution.set(undefined)\">Hide</a>\n } @else {\n <a class=\"has-text-white\" (click)=\"showDistribution.set(node)\">Show</a>\n }\n } @else {\n <span>N/A</span>\n }\n</ng-template>\n\n@if (chartDistribution()) {\n <div class=\"has-background-white is-mt-2 is-p-2 is-rounded | chart-container\">\n <he-distribution-chart\n [distribution]=\"chartDistribution()\"\n [value]=\"chartValue()\"\n [label]=\"chartLabel()\"\n [nbBins]=\"10\"\n [maxPercentile]=\"0.99\" />\n </div>\n}\n\n<ng-template #showModels>\n @if (models().length) {\n <p>\n <span class=\"is-inline-block\">\n <b>possible models used</b>\n </span>\n <span class=\"pr-2\">:</span>\n @for (model of models(); track model; let lastModel = $last) {\n <a class=\"is-dark\" [href]=\"model.link\">{{ model.name }}</a>\n @if (!lastModel) {\n <span class=\"is-pr-1 is-inline-block\">;</span>\n }\n }\n </p>\n }\n</ng-template>\n", styles: ["table{background-color:transparent}table::ng-deep he-link-key-value>a:first-child,table::ng-deep he-link-key-value>a:first-child+span{display:none}.dropdown-content{max-height:300px}.cdk-drag-preview{box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;list-style:none}.cdk-drag-placeholder{opacity:0}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drag-item{cursor:move}.chart-container{border-radius:3px}.chart-container he-distribution-chart{height:250px;min-width:400px}\n"] }]
11376
11566
  }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: true }] }], nodeType: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeType", required: true }] }], dataState: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataState", required: true }] }], dataKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataKey", required: true }] }], aggregated: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregated", required: false }] }] } });
11377
11567
 
11568
+ const isMissing = (value) => value === undefined || value === null || value === '';
11569
+ /**
11570
+ * A symbol that should carry a value: the result and the inputs, but never a fixed constant or a
11571
+ * quantity the aggregation deliberately does not store.
11572
+ */
11573
+ const isSubstitutable = (binding) => !!binding.key && binding.constant === undefined && !binding.display;
11574
+ /**
11575
+ * The number of rows a table would have had, which the aggregation records under `<table>_count`
11576
+ * when there were too many contributors to name every one of them.
11577
+ */
11578
+ const rowCount = (values, binding) => {
11579
+ const count = binding.column ? values[`${binding.key}_count`] : undefined;
11580
+ return typeof count === 'number' && count > 0 ? count : undefined;
11581
+ };
11582
+ /**
11583
+ * How a symbol that did not resolve is explained: the contributors were not recorded one by one,
11584
+ * only counted - which is a different thing from a value the aggregation never logged.
11585
+ */
11586
+ const notListedNote = (values, binding) => {
11587
+ const count = rowCount(values, binding);
11588
+ return isSubstitutable(binding) && isMissing(values[binding.key]) && count
11589
+ ? `${count} Cycles, not listed`
11590
+ : undefined;
11591
+ };
11592
+ /**
11593
+ * Whether a symbol that should have resolved did not. A symbol whose contributors were counted
11594
+ * rather than listed is not missing: `notListedNote` says how many there were instead.
11595
+ */
11596
+ const isMissingValue = (values, binding) => isSubstitutable(binding) && isMissing(values[binding.key]) && !rowCount(values, binding);
11597
+ /**
11598
+ * Whether any symbol of these formulas resolves. When none does, the substituted view would be
11599
+ * identical to the symbolic one.
11600
+ */
11601
+ const hasAnySubstitution = (values, bindings) => bindings.some(binding => isSubstitutable(binding) && !isMissing(values[binding.key]));
11602
+
11603
+ // The page every aggregation follows, whatever the product.
11604
+ const GENERAL_PAGE = 'general-process';
11605
+ // The product-specific page, by the primary product's `termType`. A term type with no page of its
11606
+ // own (e.g. `animalProduct`) shows the general rules alone rather than borrowing another product's.
11607
+ const PRODUCT_PAGES = {
11608
+ crop: 'crop',
11609
+ processedFood: 'processed-food'
11610
+ };
11611
+ // How each page's rules relate to the others, so a reader told "here are two sets of formulas" knows
11612
+ // which is which. Named after the guide pages they come from, which the row's guide button opens.
11613
+ const PAGE_HEADINGS = {
11614
+ 'general-process': { heading: 'General rules', applies: 'Applied to every aggregation, whatever the product.' },
11615
+ crop: { heading: 'Crop aggregation', applies: 'Applied on top of the general rules, for crop products.' },
11616
+ 'processed-food': {
11617
+ heading: 'Processed food aggregation',
11618
+ applies: 'Applied on top of the general rules, for processed food.'
11619
+ }
11620
+ };
11621
+ // A symbol worth listing under its formula: it carries documentation the reader needs. Constants
11622
+ // (e.g. `365`) are self-evident in the formula itself.
11623
+ const isDocumented = (binding) => !!binding.symbol && !!binding.description;
11624
+ // Formulas are selected by the fields they bind to rather than by their position in the document,
11625
+ // so re-ordering or re-wording the documentation cannot silently change what a row shows.
11626
+ const bindsTo = (formula, key) => formula.bindings.some(binding => binding.key === key);
11627
+ // The economic value share is a share of the Cycle's revenue, so it is only rescaled for products.
11628
+ const isEconomicValueShare = (formula) => bindsTo(formula, 'product_weight');
11629
+ // Zero-filling only applies where a term belongs to a completeness area: a blank value there means
11630
+ // "none", whereas elsewhere (a measurement, most practices) it means "not measured" and is skipped.
11631
+ const isCompletenessZeroFill = (formula) => bindsTo(formula, 'zero_filled_weight');
11632
+ // The production-share weight is what combines country aggregations into a World one, so it is only
11633
+ // part of how a World aggregation was produced - a country aggregation never applies it.
11634
+ const isWorldWeight = (formula) => bindsTo(formula, 'world_production');
11635
+ // The sub-system weight is the product of one sub-aggregation's organic and irrigation factors, so
11636
+ // it is only part of how that sub-aggregation was produced. On the Cycle they are combined into,
11637
+ // each of its symbols holds one value per sub-system rather than the single one bound here.
11638
+ const isSubSystemWeight = (formula) => bindsTo(formula, 'organic_factor');
11639
+ // The phase weighting splits a plantation's lifespan between its productive and non-productive
11640
+ // years, so it only ran where the product is a permanent crop.
11641
+ const isPlantationWeight = (formula) => bindsTo(formula, 'plantation_lifespan');
11642
+ // The blank node keys whose terms carry a completeness area.
11643
+ const COMPLETENESS_KEYS = ['products', 'emissions', 'inputs', 'practices'];
11644
+ const PRODUCTS_KEY = 'products';
11645
+ class NodeAggregatedFormulasComponent {
11646
+ constructor() {
11647
+ /**
11648
+ * The `termType` of the aggregation's primary product, which selects the product-specific page.
11649
+ * Omit it to show the general rules only.
11650
+ */
11651
+ this.termType = input(...(ngDevMode ? [undefined, { debugName: "termType" }] : []));
11652
+ /**
11653
+ * The `@id` of the aggregation's primary product, which decides whether the plantation rules ran.
11654
+ */
11655
+ this.termId = input(...(ngDevMode ? [undefined, { debugName: "termId" }] : []));
11656
+ /**
11657
+ * The blank node key the formulas are shown for (`products`, `emissions`, ...). Omit it to show
11658
+ * every rule; set it and only the rules that apply to that kind of data item are kept.
11659
+ */
11660
+ this.nodeKey = input(...(ngDevMode ? [undefined, { debugName: "nodeKey" }] : []));
11661
+ /**
11662
+ * The quantities the formulas bind to, for this data item. Supplied by the caller from the
11663
+ * node's `.jlog` entries recorded by the aggregation (`model: "aggregation"`). Leave it empty
11664
+ * and every symbol stays symbolic, which is a valid state: aggregation logs are opt-in
11665
+ * (`LOG_JSON_ENABLED`) and absent for most aggregations.
11666
+ */
11667
+ this.values = input({}, ...(ngDevMode ? [{ debugName: "values" }] : []));
11668
+ /**
11669
+ * Whether the aggregation covers the World rather than one country. A World aggregation combines
11670
+ * country aggregations by their share of world production; a country one never does, so that rule
11671
+ * is not part of how its values were produced.
11672
+ */
11673
+ this.worldAggregation = input(false, ...(ngDevMode ? [{ debugName: "worldAggregation" }] : []));
11674
+ /**
11675
+ * Whether the Cycle is one of the sub-aggregations an aggregation is combined from, in which case
11676
+ * the sub-system weighting is one of the stages that produced its values. Defaults to showing the
11677
+ * stage, so a caller that cannot tell keeps the rule documented.
11678
+ */
11679
+ this.subAggregation = input(true, ...(ngDevMode ? [{ debugName: "subAggregation" }] : []));
11680
+ // show the substituted formula rather than the symbolic one, when there is anything to substitute
11681
+ this.substituted = model(false, ...(ngDevMode ? [{ debugName: "substituted" }] : []));
11682
+ /**
11683
+ * The pages whose formulas are shown: the general rules, then the product-specific ones.
11684
+ */
11685
+ this.pages = computed(() => {
11686
+ const productPage = PRODUCT_PAGES[this.termType() ?? ''];
11687
+ return [GENERAL_PAGE, ...(productPage ? [productPage] : [])];
11688
+ }, ...(ngDevMode ? [{ debugName: "pages" }] : []));
11689
+ // whether any symbol actually resolves; when none does, the substituted view would be identical
11690
+ // to the symbolic one, so the toggle is disabled rather than silently doing nothing
11691
+ this.hasSubstitutions = computed(() => {
11692
+ const values = this.values();
11693
+ return this.sections().some(section => section.formulas.some(formula => hasAnySubstitution(values, formula.bindings)));
11694
+ }, ...(ngDevMode ? [{ debugName: "hasSubstitutions" }] : []));
11695
+ /**
11696
+ * Each page with its formulas, rendered symbolically or with values substituted, and the
11697
+ * variables documented under each. A variable with nothing to substitute is flagged, so the
11698
+ * reader can tell a value that was not recorded from one that is genuinely absent.
11699
+ */
11700
+ this.sections = computed(() => this.pages()
11701
+ .map(page => ({
11702
+ page,
11703
+ ...(PAGE_HEADINGS[page] ?? { heading: 'Formulas', applies: '' }),
11704
+ formulas: getFormulas$1(page).filter((formula) => this.applies(formula))
11705
+ }))
11706
+ .filter(section => section.formulas.length > 0), ...(ngDevMode ? [{ debugName: "sections" }] : []));
11707
+ this.renderedSections = computed(() => {
11708
+ const values = this.values();
11709
+ const showValues = this.substituted() && this.hasSubstitutions();
11710
+ return this.sections().map(section => ({
11711
+ ...section,
11712
+ formulas: section.formulas.map(formula => {
11713
+ const rendered = renderFormula(formula, values);
11714
+ return {
11715
+ rendered: showValues ? rendered.substituted : rendered.symbolic,
11716
+ // what the formula is for and the sentence introducing it, both written in the guide page
11717
+ // this formula was extracted from - so the panel explains it in the documentation's words
11718
+ section: formula.section,
11719
+ context: formula.context,
11720
+ variables: formula.bindings.filter(isDocumented).map(binding => ({
11721
+ symbol: binding.symbol,
11722
+ description: binding.description,
11723
+ // only an input that should have resolved is flagged - a constant, or a quantity the
11724
+ // aggregation deliberately does not store, is never substitutable rather than missing
11725
+ missing: showValues && isMissingValue(values, binding),
11726
+ // a symbol reading from a table of contributors too long to record says how many there
11727
+ // were, which is not the same as a value the aggregation never logged
11728
+ note: showValues ? notListedNote(values, binding) : undefined
11729
+ }))
11730
+ };
11731
+ })
11732
+ }));
11733
+ }, ...(ngDevMode ? [{ debugName: "renderedSections" }] : []));
11734
+ }
11735
+ /**
11736
+ * Whether the rule ran for this aggregation at all. Some stages depend on what was aggregated
11737
+ * rather than on the data item shown: the production-share weighting only combines countries into
11738
+ * a World aggregation, and the phase weighting only splits the lifespan of a plantation crop.
11739
+ */
11740
+ ranForAggregation(formula) {
11741
+ // the stages that only run for some aggregations, and whether each ran for this one. A formula
11742
+ // matching none of them is part of every aggregation.
11743
+ const stages = [
11744
+ { applies: isWorldWeight, ran: () => this.worldAggregation() },
11745
+ { applies: isSubSystemWeight, ran: () => this.subAggregation() },
11746
+ { applies: isPlantationWeight, ran: () => isPlantation(this.termId() ?? '') }
11747
+ ];
11748
+ return stages.find(({ applies }) => applies(formula))?.ran() ?? true;
11749
+ }
11750
+ /**
11751
+ * Whether the rule applies to the kind of data item shown. With no `nodeKey` every rule is kept,
11752
+ * which is the whole-aggregation view.
11753
+ */
11754
+ appliesToNodeKey(formula) {
11755
+ const nodeKey = this.nodeKey();
11756
+ if (!nodeKey)
11757
+ return true;
11758
+ if (isEconomicValueShare(formula))
11759
+ return nodeKey === PRODUCTS_KEY;
11760
+ if (isCompletenessZeroFill(formula))
11761
+ return COMPLETENESS_KEYS.includes(nodeKey);
11762
+ return true;
11763
+ }
11764
+ applies(formula) {
11765
+ return this.ranForAggregation(formula) && this.appliesToNodeKey(formula);
11766
+ }
11767
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregatedFormulasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11768
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregatedFormulasComponent, isStandalone: true, selector: "he-node-aggregated-formulas", inputs: { termType: { classPropertyName: "termType", publicName: "termType", isSignal: true, isRequired: false, transformFunction: null }, termId: { classPropertyName: "termId", publicName: "termId", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, worldAggregation: { classPropertyName: "worldAggregation", publicName: "worldAggregation", isSignal: true, isRequired: false, transformFunction: null }, subAggregation: { classPropertyName: "subAggregation", publicName: "subAggregation", isSignal: true, isRequired: false, transformFunction: null }, substituted: { classPropertyName: "substituted", publicName: "substituted", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { substituted: "substitutedChange" }, ngImport: i0, template: "@if (renderedSections().length) {\n <div class=\"aggregated-formulas\">\n <p class=\"is-size-7 is-mb-2 | formula-intro\">\n Aggregated values are calculated from the underlying Cycles, not measured. These are the rules that produced this\n one, in the order they are applied.\n </p>\n\n <div class=\"aggregated-formulas-rules\">\n @for (section of renderedSections(); track section.page; let first = $first) {\n <he-formula-block\n [formulas]=\"section.formulas\"\n [heading]=\"section.heading\"\n [note]=\"section.applies\"\n [showToggle]=\"first\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\"\n emptyTitle=\"No recorded values to substitute\" />\n }\n </div>\n </div>\n}\n", styles: [".formula-intro{opacity:.85}.aggregated-formulas-rules{max-height:50vh;overflow-y:auto;padding-right:.25rem}\n"], dependencies: [{ kind: "component", type: FormulaBlockComponent, selector: "he-formula-block", inputs: ["formulas", "hasSubstitutions", "emptyTitle", "heading", "note", "showToggle", "substituted"], outputs: ["substitutedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11769
+ }
11770
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregatedFormulasComponent, decorators: [{
11771
+ type: Component$1,
11772
+ args: [{ selector: 'he-node-aggregated-formulas', changeDetection: ChangeDetectionStrategy.OnPush, imports: [FormulaBlockComponent], template: "@if (renderedSections().length) {\n <div class=\"aggregated-formulas\">\n <p class=\"is-size-7 is-mb-2 | formula-intro\">\n Aggregated values are calculated from the underlying Cycles, not measured. These are the rules that produced this\n one, in the order they are applied.\n </p>\n\n <div class=\"aggregated-formulas-rules\">\n @for (section of renderedSections(); track section.page; let first = $first) {\n <he-formula-block\n [formulas]=\"section.formulas\"\n [heading]=\"section.heading\"\n [note]=\"section.applies\"\n [showToggle]=\"first\"\n [hasSubstitutions]=\"hasSubstitutions()\"\n [(substituted)]=\"substituted\"\n emptyTitle=\"No recorded values to substitute\" />\n }\n </div>\n </div>\n}\n", styles: [".formula-intro{opacity:.85}.aggregated-formulas-rules{max-height:50vh;overflow-y:auto;padding-right:.25rem}\n"] }]
11773
+ }], propDecorators: { termType: [{ type: i0.Input, args: [{ isSignal: true, alias: "termType", required: false }] }], termId: [{ type: i0.Input, args: [{ isSignal: true, alias: "termId", required: false }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }], worldAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "worldAggregation", required: false }] }], subAggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "subAggregation", required: false }] }], substituted: [{ type: i0.Input, args: [{ isSignal: true, alias: "substituted", required: false }] }, { type: i0.Output, args: ["substitutedChange"] }] } });
11774
+
11775
+ // the `.jlog` entries the aggregation records are tagged with its own model name
11776
+ const JLOG_MODEL = 'aggregation';
11777
+ const LOGS_KEY = 'logs';
11778
+ // the table of contributors an aggregated value is recorded with, one row per Cycle it was
11779
+ // combined from: `id:<cycle id>_value:<value>_weight:<weight>;...`
11780
+ const WEIGHTS_KEY = 'weights';
11781
+ const ID_COLUMN = 'id:';
11782
+ /**
11783
+ * The share of world production a country aggregation was weighted by. It is only recorded on a
11784
+ * Cycle that a World aggregation combines, so its presence is what tells the two node-level
11785
+ * entries apart.
11786
+ */
11787
+ const WORLD_STAGE_KEY = 'world_production';
11788
+ // the sub-system stage: the organic and irrigated factors a sub-aggregation's weight is the product of
11789
+ const SUB_SYSTEM_KEY = 'organic_factor';
11790
+ /**
11791
+ * The shares of the country's area an aggregation is weighted by. They come from lookups keyed by
11792
+ * country and period, so every sub-aggregation of the same country and period records the same
11793
+ * values, and any one of them answers for the aggregation as a whole.
11794
+ */
11795
+ const LOOKUP_KEYS = [
11796
+ 'organic_weight',
11797
+ 'organic_weight_lookup_value',
11798
+ 'irrigated_weight',
11799
+ 'irrigated_area',
11800
+ 'total_area'
11801
+ ];
11802
+ /**
11803
+ * A Cycle combined from sub-aggregations has a handful of contributors (organic or conventional,
11804
+ * irrigated or rainfed); one aggregated straight from source Cycles has hundreds, and those record
11805
+ * no aggregation logs at all - so a longer list means there is nothing to fetch.
11806
+ */
11807
+ const MAX_CONTRIBUTORS = 4;
11808
+ // the quantities of one `.jlog` entry, dropping the `model` marker that tags them
11809
+ const entryValues = (entry) => (entry?.[LOGS_KEY] ?? [])
11810
+ .filter(log => log?.model === JLOG_MODEL)
11811
+ .reduce((values, { model: _model, ...fields }) => ({ ...values, ...fields }), {});
11812
+ /**
11813
+ * The quantities recorded for one blank node. A field of the blank node (e.g. a product's
11814
+ * `economicValueShare`) is logged one level deeper, the way the models' `.jlog` nests it - merge
11815
+ * those in so a formula resolves whichever of the two it describes, the blank node's own values
11816
+ * winning any collision.
11817
+ */
11818
+ const loggedValues = (entry) => {
11819
+ const nested = Object.entries(entry ?? {})
11820
+ .filter(([key]) => key !== LOGS_KEY)
11821
+ .reduce((values, [, field]) => ({ ...values, ...entryValues(field) }), {});
11822
+ return { ...nested, ...entryValues(entry) };
11823
+ };
11824
+ /**
11825
+ * The quantities describing the Cycle itself, recorded once at the top of its `.jlog` rather than
11826
+ * on each data item: how it was weighted into the aggregation it belongs to, and the shares that
11827
+ * weight was derived from.
11828
+ *
11829
+ * A country aggregation records its share of world production here, weighted by `weight` - the same
11830
+ * key the sub-system stage binds to. Drop it there, so the world share can never stand in for a
11831
+ * sub-system weight.
11832
+ */
11833
+ const nodeValues = (jlog) => {
11834
+ const values = entryValues(jlog);
11835
+ return WORLD_STAGE_KEY in values
11836
+ ? Object.fromEntries(Object.entries(values).filter(([key]) => key !== 'weight'))
11837
+ : values;
11838
+ };
11839
+ /**
11840
+ * Whether the sub-system stage is one of the stages that produced this Cycle's values, which its
11841
+ * own node-level entry says. Everywhere else its symbols hold one value per sub-system rather than
11842
+ * the single one the formula binds, so it describes another Cycle rather than this one.
11843
+ *
11844
+ * With nothing logged at all the stage is left to render symbolically, as every other rule does,
11845
+ * rather than hidden on a guess.
11846
+ */
11847
+ const isSubAggregation = (jlog) => {
11848
+ const values = nodeValues(jlog);
11849
+ return isEmpty(values) || SUB_SYSTEM_KEY in values;
11850
+ };
11851
+ // the ids of a packed contributors table, in the order they were recorded
11852
+ const tableIds = (packed) => typeof packed === 'string'
11853
+ ? packed
11854
+ .split(';')
11855
+ .map(row => row.split('_')[0])
11856
+ .filter(column => column.startsWith(ID_COLUMN))
11857
+ .map(column => column.slice(ID_COLUMN.length))
11858
+ : [];
11859
+ /**
11860
+ * The Cycles this one was combined from, named in the contributors table of any of its data items -
11861
+ * the only place a sub-aggregation is named, as the Cycle links the source Cycles it covers rather
11862
+ * than the sub-aggregations it was built from.
11863
+ *
11864
+ * Empty when the Cycle already records the country shares itself (nothing to look up), and when
11865
+ * there are more contributors than sub-systems (they are source Cycles, which log nothing).
11866
+ */
11867
+ const contributorIds = (jlog) => {
11868
+ if (!isEmpty(sharedLookups([jlog])))
11869
+ return [];
11870
+ const tables = Object.values(jlog ?? {})
11871
+ .filter(section => !!section && typeof section === 'object' && !Array.isArray(section))
11872
+ .flatMap(section => Object.values(section).map(entry => entryValues(entry)[WEIGHTS_KEY]));
11873
+ const ids = unique(tableIds(tables.find(table => typeof table === 'string')));
11874
+ return ids.length > MAX_CONTRIBUTORS ? [] : ids;
11875
+ };
11876
+ /**
11877
+ * The country shares, taken from the first contributor that records them. They are the same on
11878
+ * every sub-aggregation of the country and period, so one readable Cycle answers for all of them -
11879
+ * and none being readable leaves those symbols symbolic, as they are today.
11880
+ */
11881
+ const sharedLookups = (jlogs) => (jlogs ?? [])
11882
+ .map(jlog => Object.entries(nodeValues(jlog)).filter(([key]) => LOOKUP_KEYS.includes(key)))
11883
+ .map(entries => Object.fromEntries(entries))
11884
+ .find(values => !isEmpty(values)) ?? {};
11885
+
11886
+ // Aggregation has a single "model" - unlike a recalculation, where each term may be produced by a
11887
+ // different one - so the column is a constant rather than something resolved per row.
11888
+ const MODEL_NAME = 'Aggregation';
11889
+ // The guide page the row's rules are documented on, by its page id - the id the guide resolves both
11890
+ // the overlay (`/guide/overlay/<id>`) and the page itself (`/guide/<id>`) by, not its path.
11891
+ // `general-process` covers every aggregation; the crop page adds the weighting used to combine
11892
+ // sub-aggregations and countries.
11893
+ const GUIDE_PAGES = {
11894
+ crop: 'guide-aggregated-data-crop',
11895
+ processedFood: 'guide-aggregated-data-processed-food'
11896
+ };
11897
+ const DEFAULT_GUIDE_PAGE = 'guide-aggregated-data-general-process';
11898
+ // A World aggregation combines country aggregations, so it follows one rule a country aggregation
11899
+ // never does. The aggregated Cycle carries its Site as a link, so fall back to the country segment
11900
+ // of its name ("<product> - <country> - <period>"), as the quality score does.
11901
+ const WORLD_COUNTRY_ID = 'region-world';
11902
+ const WORLD_COUNTRY_NAME = 'World';
11903
+ const isWorldAggregation = (node) => {
11904
+ const country = node?.site?.country;
11905
+ return country
11906
+ ? country['@id'] === WORLD_COUNTRY_ID
11907
+ : node?.name?.split(' - ')?.[1]?.trim() === WORLD_COUNTRY_NAME;
11908
+ };
11909
+ const groupObservations = (rows) => {
11910
+ const counts = rows.map(({ observations }) => observations).filter(count => typeof count === 'number');
11911
+ if (!counts.length)
11912
+ return undefined;
11913
+ const [min, max] = [Math.min(...counts), Math.max(...counts)];
11914
+ return min === max ? `${min}` : `${min}-${max}`;
11915
+ };
11916
+ class NodeAggregationLogsComponent {
11917
+ constructor() {
11918
+ this.nodeLogsModelsService = inject(NodeLogsModelsService);
11919
+ /**
11920
+ * The aggregated node the data items are read from.
11921
+ */
11922
+ this.node = input(...(ngDevMode ? [undefined, { debugName: "node" }] : []));
11923
+ /**
11924
+ * The blank node key shown, e.g. `products` or `emissions`. It also selects which rules apply
11925
+ * to each row: the economic value share is only rescaled for products, and zero-filling only
11926
+ * applies where terms carry a completeness area.
11927
+ */
11928
+ this.nodeKey = input('', ...(ngDevMode ? [{ debugName: "nodeKey" }] : []));
11929
+ /**
11930
+ * The `termType` of the aggregation's primary product, which selects the product-specific rules.
11931
+ */
11932
+ this.termType = input(...(ngDevMode ? [undefined, { debugName: "termType" }] : []));
11933
+ /**
11934
+ * The `@id` of the aggregation's primary product, which decides whether the plantation rules ran.
11935
+ */
11936
+ this.termId = input(...(ngDevMode ? [undefined, { debugName: "termId" }] : []));
11937
+ /**
11938
+ * For a grouped sub-node view (e.g. an animal's inputs), the `.jlog` is nested under its parent:
11939
+ * scope to `jlog[<parentKey>][<parentIndex>]`, as the recalculation logs do.
11940
+ */
11941
+ this.jlogParentKey = input(...(ngDevMode ? [undefined, { debugName: "jlogParentKey" }] : []));
11942
+ this.jlogParentIndex = input(...(ngDevMode ? [undefined, { debugName: "jlogParentIndex" }] : []));
11943
+ // the in-app guide overlay is only available where the guide is bundled; elsewhere the row
11944
+ // links out to the same page instead, matching how the recalculation table shows its Docs link
11945
+ this.guideEnabled = inject(GUIDE_ENABLED, { optional: true }) ?? false;
11946
+ this.modelName = MODEL_NAME;
11947
+ this.guidePage = computed(() => GUIDE_PAGES[this.termType() ?? ''] ?? DEFAULT_GUIDE_PAGE, ...(ngDevMode ? [{ debugName: "guidePage" }] : []));
11948
+ this.guideHref = computed(() => guideModelUrl({ guidePath: this.guidePage() }), ...(ngDevMode ? [{ debugName: "guideHref" }] : []));
11949
+ this.nodeType = computed(() => nodeType(this.node()), ...(ngDevMode ? [{ debugName: "nodeType" }] : []));
11950
+ // whether the rules shown are those of a World aggregation rather than a country one
11951
+ this.worldAggregation = computed(() => isWorldAggregation(this.node()), ...(ngDevMode ? [{ debugName: "worldAggregation" }] : []));
11952
+ /**
11953
+ * The node's `.jlog`, fetched like the recalculation logs fetch theirs: the aggregation records the
11954
+ * quantities its formulas bind to in the same file, under the same `{<field>: {<index>: {logs: []}}}`
11955
+ * shape. It is empty for most aggregations - the logs are opt-in (`LOG_JSON_ENABLED`) - and every
11956
+ * rule then renders symbolically, which is a valid state rather than an error.
11957
+ */
11958
+ this.jlogResource = rxResource({
11959
+ params: () => ({ node: this.node() }),
11960
+ stream: ({ params: { node } }) => this.nodeLogsModelsService.getJLog$(node)
11961
+ });
11962
+ this.jlog = computed(() => this.jlogResource.value() ?? {}, ...(ngDevMode ? [{ debugName: "jlog" }] : []));
11963
+ // the `.jlog` section the rows are read from - the whole jlog, or the parent-scoped sub-node entry
11964
+ this.scopedJlog = computed(() => {
11965
+ const key = this.jlogParentKey();
11966
+ const index = this.jlogParentIndex();
11967
+ return key && typeof index === 'number' && index >= 0 ? (this.jlog()?.[key]?.[index] ?? {}) : this.jlog();
11968
+ }, ...(ngDevMode ? [{ debugName: "scopedJlog" }] : []));
11969
+ // the quantities describing the Cycle itself rather than one of its data items, which the
11970
+ // aggregation records once at the top of the `.jlog`
11971
+ this.nodeValues = computed(() => nodeValues(this.scopedJlog()), ...(ngDevMode ? [{ debugName: "nodeValues" }] : []));
11972
+ // whether the sub-system weighting is one of the stages that produced these values
11973
+ this.subAggregation = computed(() => isSubAggregation(this.scopedJlog()), ...(ngDevMode ? [{ debugName: "subAggregation" }] : []));
11974
+ /**
11975
+ * The `.jlog` of every sub-aggregation this Cycle was combined from, for the country shares the
11976
+ * aggregation only records on them. Best effort: a reader without access to a sub-aggregation
11977
+ * gets nothing back, and those symbols stay symbolic - which is how they render today.
11978
+ */
11979
+ this.contributorsResource = rxResource({
11980
+ params: () => ({ ids: contributorIds(this.scopedJlog()) }),
11981
+ stream: ({ params: { ids } }) => ids.length
11982
+ ? forkJoin(ids.map(id => this.nodeLogsModelsService.getJLog$({ '@type': NodeType.Cycle, '@id': id, aggregated: true })))
11983
+ : of([])
11984
+ });
11985
+ this.sharedLookups = computed(() => sharedLookups(this.contributorsResource.value() ?? []), ...(ngDevMode ? [{ debugName: "sharedLookups" }] : []));
11986
+ // groups kept open, by term id: expanding is view state, so it survives the rows being rebuilt
11987
+ this.openGroups = signal(new Set(), ...(ngDevMode ? [{ debugName: "openGroups" }] : []));
11988
+ /**
11989
+ * The aggregated data items, grouped by term exactly as the recalculation logs group them: several
11990
+ * entries for one term (e.g. a measurement at two depths) collapse into one expandable group whose
11991
+ * rows are labelled by what tells them apart.
11992
+ */
11993
+ this.groups = computed(() => {
11994
+ const blankNodes = (this.node()?.[this.nodeKey()] ?? []);
11995
+ const logs = this.scopedJlog()?.[this.nodeKey()] ?? {};
11996
+ const open = this.openGroups();
11997
+ // a data item's own quantities win over the ones describing the whole Cycle, which in turn win
11998
+ // over the country shares read from a sub-aggregation
11999
+ const shared = { ...this.sharedLookups(), ...this.nodeValues() };
12000
+ return groupBlankNodesByTermIdentity(blankNodes, this.nodeType(), this.nodeKey()).map(group => {
12001
+ const rows = group.rows.map(row => ({
12002
+ ...row,
12003
+ values: { ...shared, ...loggedValues(logs[row.index]) },
12004
+ displayValue: propertyValue$1(row.value?.value, group.termId),
12005
+ observations: row.value?.observations
12006
+ }));
12007
+ return { ...group, rows, isOpen: open.has(group.termId), observations: groupObservations(rows) };
12008
+ });
12009
+ }, ...(ngDevMode ? [{ debugName: "groups" }] : []));
12010
+ }
12011
+ toggleGroup(group) {
12012
+ this.openGroups.update(open => {
12013
+ const next = new Set(open);
12014
+ next.has(group.termId) ? next.delete(group.termId) : next.add(group.termId);
12015
+ return next;
12016
+ });
12017
+ }
12018
+ trackByGroup(_index, group) {
12019
+ return group.termId;
12020
+ }
12021
+ trackByRow(_index, row) {
12022
+ return `${row.index}-${row.label}`;
12023
+ }
12024
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregationLogsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12025
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregationLogsComponent, isStandalone: true, selector: "he-node-aggregation-logs", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: false, transformFunction: null }, nodeKey: { classPropertyName: "nodeKey", publicName: "nodeKey", isSignal: true, isRequired: false, transformFunction: null }, termType: { classPropertyName: "termType", publicName: "termType", isSignal: true, isRequired: false, transformFunction: null }, termId: { classPropertyName: "termId", publicName: "termId", isSignal: true, isRequired: false, transformFunction: null }, jlogParentKey: { classPropertyName: "jlogParentKey", publicName: "jlogParentKey", isSignal: true, isRequired: false, transformFunction: null }, jlogParentIndex: { classPropertyName: "jlogParentIndex", publicName: "jlogParentIndex", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"is-flex is-gap-8 is-justify-content-space-between is-align-items-center is-my-2\">\n <div>\n <ng-content />\n </div>\n</div>\n\n<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\"><span>Units</span></th>\n <th class=\"has-border-right\"><span>Aggregated</span></th>\n <th class=\"has-border-right\">\n <span title=\"The number of Cycles the value was aggregated from\">Observations</span>\n </th>\n <th class=\"has-border-right\"><span>Model</span></th>\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No aggregated data to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n </div>\n </td>\n\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0], term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row: group.rows[0] }\" />\n } @else {\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"group.term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n @if (group.valueFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.valueFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n {{ group.value | precision: 3 | default: '-' }}\n </span>\n } @else {\n {{ group.value | precision: 3 | default: '-' }}\n }\n </td>\n <td class=\"has-border-right is-nowrap\">{{ group.observations | default: '-' }}</td>\n <ng-container *ngTemplateOutlet=\"modelCell\" />\n }\n </tr>\n\n @if (!single && group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n <he-blank-node-identity\n [segments]=\"row.segments\"\n [scalars]=\"row.scalars\"\n [type]=\"group.type\"\n [fallback]=\"'entry ' + (row.index + 1)\" />\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row, term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row }\" />\n </tr>\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<ng-template #valueCells let-row=\"row\" let-term=\"term\">\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.displayValue | precision: 3 | default: '-' }}\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.observations | default: '-' }}\n </td>\n</ng-template>\n\n<!-- `row` is left out on a term group: the rules are the same for every entry it holds, so the guide\n is shown there too, but the values to substitute belong to one entry - those stay on the sub-rows -->\n<ng-template #modelCell let-row=\"row\">\n <td class=\"has-border-right\">\n <div class=\"is-flex is-align-self-stretch is-align-items-center is-gap-8\">\n <span class=\"is-flex-grow-1 is-nowrap\">{{ modelName }}</span>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (row) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"aggregationRules\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ values: row.values })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n } @else {\n <span class=\"has-text-grey is-nowrap\">Expand for logs</span>\n }\n\n <div class=\"vertical-divider\"></div>\n\n @if (guideEnabled) {\n <he-guide-overlay [pageId]=\"guidePage()\" [width]=\"500\" />\n } @else {\n <a [href]=\"guideHref()\" target=\"_blank\" rel=\"noopener\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n </div>\n </div>\n </td>\n</ng-template>\n\n<ng-template #aggregationRules let-values=\"values\">\n <he-node-aggregated-formulas\n [termType]=\"termType()\"\n [termId]=\"termId()\"\n [nodeKey]=\"nodeKey()\"\n [values]=\"values\"\n [worldAggregation]=\"worldAggregation()\"\n [subAggregation]=\"subAggregation()\" />\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}::ng-deep .table th:last-child,::ng-deep .table td:last-child{min-width:12rem}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgbPopover, selector: "[ngbPopover]", inputs: ["animation", "autoClose", "ngbPopover", "popoverTitle", "placement", "popperOptions", "triggers", "positionTarget", "container", "disablePopover", "popoverClass", "popoverContext", "openDelay", "closeDelay"], outputs: ["shown", "hidden"], exportAs: ["ngbPopover"] }, { kind: "component", type: DataTableComponent, selector: "he-data-table", inputs: ["minHeight", "maxHeight", "small"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "component", type: BlankNodeIdentityComponent, selector: "he-blank-node-identity", inputs: ["segments", "scalars", "type", "fallback"] }, { kind: "component", type: GuideOverlayComponent, selector: "he-guide-overlay", inputs: ["pageId", "width", "height", "positions"], outputs: ["widthChange", "heightChange"] }, { kind: "component", type: NodeAggregatedFormulasComponent, selector: "he-node-aggregated-formulas", inputs: ["termType", "termId", "nodeKey", "values", "worldAggregation", "subAggregation", "substituted"], outputs: ["substitutedChange"] }, { kind: "pipe", type: CompoundPipe, name: "compound" }, { kind: "pipe", type: DefaultPipe, name: "default" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12026
+ }
12027
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregationLogsComponent, decorators: [{
12028
+ type: Component$1,
12029
+ args: [{ selector: 'he-node-aggregation-logs', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
12030
+ NgTemplateOutlet,
12031
+ NgbPopover,
12032
+ CompoundPipe,
12033
+ DefaultPipe,
12034
+ PrecisionPipe,
12035
+ DataTableComponent,
12036
+ HESvgIconComponent,
12037
+ NodeLinkComponent,
12038
+ BlankNodeIdentityComponent,
12039
+ GuideOverlayComponent,
12040
+ NodeAggregatedFormulasComponent
12041
+ ], template: "<div class=\"is-flex is-gap-8 is-justify-content-space-between is-align-items-center is-my-2\">\n <div>\n <ng-content />\n </div>\n</div>\n\n<he-data-table class=\"is-mt-2 is-mb-1 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n <tr>\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\"><span>Units</span></th>\n <th class=\"has-border-right\"><span>Aggregated</span></th>\n <th class=\"has-border-right\">\n <span title=\"The number of Cycles the value was aggregated from\">Observations</span>\n </th>\n <th class=\"has-border-right\"><span>Model</span></th>\n </tr>\n </thead>\n <tbody>\n @if (groups().length === 0) {\n <tr>\n <td class=\"has-border-right has-text-centered\" colspan=\"100\">\n <p class=\"is-p-1\">No aggregated data to show.</p>\n </td>\n </tr>\n }\n @for (group of groups(); track trackByGroup($index, group)) {\n @let single = group.rows.length === 1;\n <tr [class.has-sub-rows]=\"group.canOpen\" [class.is-open]=\"group.isOpen\">\n <td class=\"width-auto has-border-right is-nowrap\" [attr.title]=\"group.term?.name\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4\">\n @if (group.canOpen) {\n <a class=\"open-node\" (click)=\"toggleGroup(group)\">\n <he-svg-icon [name]=\"group.isOpen ? 'chevron-down' : 'chevron-right'\" />\n </a>\n }\n <he-node-link class=\"is-inline-block is-pre-wrap is-pr-2\" [node]=\"group.term\">\n <span class=\"break-word\" [innerHtml]=\"group.term?.name | compound: group.term?.termType\"></span>\n </he-node-link>\n </div>\n </td>\n\n @if (single) {\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row: group.rows[0], term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row: group.rows[0] }\" />\n } @else {\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"group.term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n @if (group.valueFormula) {\n <span\n class=\"has-formula\"\n [ngbPopover]=\"group.valueFormula\"\n popoverClass=\"is-narrow\"\n triggers=\"click\"\n container=\"body\">\n {{ group.value | precision: 3 | default: '-' }}\n </span>\n } @else {\n {{ group.value | precision: 3 | default: '-' }}\n }\n </td>\n <td class=\"has-border-right is-nowrap\">{{ group.observations | default: '-' }}</td>\n <ng-container *ngTemplateOutlet=\"modelCell\" />\n }\n </tr>\n\n @if (!single && group.isOpen) {\n @for (row of group.rows; track trackByRow($index, row)) {\n <tr class=\"is-sub-row\">\n <td class=\"width-auto has-border-right\">\n <div class=\"is-flex is-align-items-flex-start is-gap-4 is-pl-3\">\n <he-svg-icon class=\"sub-sub-row-icon\" name=\"chevron-double-right\" />\n <he-blank-node-identity\n [segments]=\"row.segments\"\n [scalars]=\"row.scalars\"\n [type]=\"group.type\"\n [fallback]=\"'entry ' + (row.index + 1)\" />\n </div>\n </td>\n <ng-container *ngTemplateOutlet=\"valueCells; context: { row, term: group.term }\" />\n <ng-container *ngTemplateOutlet=\"modelCell; context: { row }\" />\n </tr>\n }\n }\n }\n </tbody>\n </table>\n</he-data-table>\n\n<ng-template #valueCells let-row=\"row\" let-term=\"term\">\n <td class=\"has-border-right\">\n <span class=\"is-nowrap\" [innerHtml]=\"term?.units | compound\"></span>\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.displayValue | precision: 3 | default: '-' }}\n </td>\n <td class=\"has-border-right is-nowrap\">\n {{ row.observations | default: '-' }}\n </td>\n</ng-template>\n\n<!-- `row` is left out on a term group: the rules are the same for every entry it holds, so the guide\n is shown there too, but the values to substitute belong to one entry - those stay on the sub-rows -->\n<ng-template #modelCell let-row=\"row\">\n <td class=\"has-border-right\">\n <div class=\"is-flex is-align-self-stretch is-align-items-center is-gap-8\">\n <span class=\"is-flex-grow-1 is-nowrap\">{{ modelName }}</span>\n\n <div class=\"is-flex is-gap-4 is-flex-shrink-0 is-align-items-center\">\n @if (row) {\n <span\n class=\"is-nowrap is-clickable\"\n [ngbPopover]=\"aggregationRules\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow\"\n triggers=\"manual\"\n #p=\"ngbPopover\"\n placement=\"bottom left right auto\"\n container=\"body\"\n (click)=\"p.isOpen() ? p.close() : p.open({ values: row.values })\">\n <span class=\"has-text-link\">Logs</span>\n </span>\n } @else {\n <span class=\"has-text-grey is-nowrap\">Expand for logs</span>\n }\n\n <div class=\"vertical-divider\"></div>\n\n @if (guideEnabled) {\n <he-guide-overlay [pageId]=\"guidePage()\" [width]=\"500\" />\n } @else {\n <a [href]=\"guideHref()\" target=\"_blank\" rel=\"noopener\" (click)=\"$event.stopPropagation()\">\n <span>Docs</span>\n <he-svg-icon name=\"external-link\" class=\"ml-2\" />\n </a>\n }\n </div>\n </div>\n </td>\n</ng-template>\n\n<ng-template #aggregationRules let-values=\"values\">\n <he-node-aggregated-formulas\n [termType]=\"termType()\"\n [termId]=\"termId()\"\n [nodeKey]=\"nodeKey()\"\n [values]=\"values\"\n [worldAggregation]=\"worldAggregation()\"\n [subAggregation]=\"subAggregation()\" />\n</ng-template>\n", styles: [":host{display:block}:host .vertical-divider{width:1px;height:20px;background:#dbe3ea}:host .has-formula{cursor:help;border-bottom:1px dotted currentColor}::ng-deep .table{background-color:transparent}::ng-deep .table td.has-border-right{box-shadow:1px 0 #4c7194}::ng-deep .table td>div{min-height:24px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before,::ng-deep .table .is-sub-row>td:first-child:before{display:block;position:absolute;content:\" \";background-color:#4c719433;height:100%;width:1px;top:0;left:14px}::ng-deep .table .has-sub-rows.is-open>td:first-child:before{top:25px}::ng-deep .table .is-sub-row .open-node>he-svg-icon,::ng-deep .table .is-sub-row .sub-sub-row-icon{height:16px!important;width:16px!important}::ng-deep .table .is-sub-sub-row td:first-child{padding-left:24px}::ng-deep .table th:last-child,::ng-deep .table td:last-child{min-width:12rem}\n"] }]
12042
+ }], propDecorators: { node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: false }] }], nodeKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKey", required: false }] }], termType: [{ type: i0.Input, args: [{ isSignal: true, alias: "termType", required: false }] }], termId: [{ type: i0.Input, args: [{ isSignal: true, alias: "termId", required: false }] }], jlogParentKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "jlogParentKey", required: false }] }], jlogParentIndex: [{ type: i0.Input, args: [{ isSignal: true, alias: "jlogParentIndex", required: false }] }] } });
12043
+
11378
12044
  const isValidDate = (date) => (date || '').trim().length === 10;
11379
12045
  const dateRange = (startDate, endDate) => {
11380
12046
  const start = formatDate(startDate, true);
@@ -11503,20 +12169,22 @@ var View$2;
11503
12169
  View["chart"] = "Chart";
11504
12170
  View["timeline"] = "Operations Timeline";
11505
12171
  View["logs"] = "Recalculations logs";
12172
+ View["aggregationLogs"] = "Aggregation logs";
11506
12173
  })(View$2 || (View$2 = {}));
11507
12174
  const timelineTermType = [TermTermType.operation];
11508
12175
  const viewIcon$2 = {
12176
+ [View$2.aggregationLogs]: 'aggregation',
11509
12177
  [View$2.chart]: 'chart',
11510
12178
  [View$2.logs]: 'calculator',
11511
12179
  [View$2.table]: 'table',
11512
12180
  [View$2.timeline]: 'filter-slider'
11513
12181
  };
11514
12182
  const nodeKeyViews$2 = {
11515
- [BlankNodesKey.animals]: [View$2.table, View$2.logs],
11516
- [BlankNodesKey.emissions]: [View$2.table, View$2.chart, View$2.logs],
11517
- [BlankNodesKey.inputs]: [View$2.table, View$2.logs],
11518
- [BlankNodesKey.products]: [View$2.table, View$2.logs],
11519
- [BlankNodesKey.practices]: [View$2.table, View$2.timeline, View$2.logs]
12183
+ [BlankNodesKey.animals]: [View$2.table, View$2.logs, View$2.aggregationLogs],
12184
+ [BlankNodesKey.emissions]: [View$2.table, View$2.chart, View$2.logs, View$2.aggregationLogs],
12185
+ [BlankNodesKey.inputs]: [View$2.table, View$2.logs, View$2.aggregationLogs],
12186
+ [BlankNodesKey.products]: [View$2.table, View$2.logs, View$2.aggregationLogs],
12187
+ [BlankNodesKey.practices]: [View$2.table, View$2.timeline, View$2.logs, View$2.aggregationLogs]
11520
12188
  };
11521
12189
  const nodeKeyFilterTermTypes$1 = {
11522
12190
  [BlankNodesKey.emissions]: [TermTermType.emission]
@@ -11573,6 +12241,7 @@ class CyclesNodesComponent {
11573
12241
  this.View = View$2;
11574
12242
  this.viewIcon = viewIcon$2;
11575
12243
  this.showView = computed(() => ({
12244
+ [View$2.aggregationLogs]: this.hasAggregatedNodes(),
11576
12245
  [View$2.chart]: [this.isEmission() && this.cycles().length > 1].some(Boolean),
11577
12246
  [View$2.logs]: !this.isOriginal() && this.hasRecalculatedNodes(),
11578
12247
  [View$2.table]: true,
@@ -11589,7 +12258,23 @@ class CyclesNodesComponent {
11589
12258
  this.cycles = computed(() => this.nodeKeyGroup()
11590
12259
  ? filterGroupNodesByTerm(this.nodeKeyGroup(), this.currentNodes(), this.selectedGroup())
11591
12260
  : this.currentNodes(), ...(ngDevMode ? [{ debugName: "cycles" }] : []));
11592
- this.showSelectCycle = computed(() => [View$2.timeline, View$2.logs].includes(this.selectedView()), ...(ngDevMode ? [{ debugName: "showSelectCycle" }] : []));
12261
+ // the views that show a single cycle's rows, so the cycle being shown must be selectable
12262
+ this.showSelectCycle = computed(() => [View$2.timeline, View$2.logs, View$2.aggregationLogs].includes(this.selectedView()), ...(ngDevMode ? [{ debugName: "showSelectCycle" }] : []));
12263
+ /**
12264
+ * The cycles the current view can show, each with its position in `cycles()`.
12265
+ *
12266
+ * The two logs views read a different file for the same cycle: the recalculation logs parse the
12267
+ * model logs, which an aggregated cycle does not have (its log describes the aggregation), and the
12268
+ * aggregation logs only mean anything for an aggregated one. So each view offers only the cycles
12269
+ * it can actually read - showing a cycle the view cannot parse is not an empty table, it throws.
12270
+ */
12271
+ this.selectableCycles = computed(() => {
12272
+ const view = this.selectedView();
12273
+ const isSelectable = ({ aggregated }) => view === View$2.logs ? !aggregated : view === View$2.aggregationLogs ? !!aggregated : true;
12274
+ return this.cycles()
12275
+ .map((cycle, index) => ({ cycle, index }))
12276
+ .filter(({ cycle }) => isSelectable(cycle));
12277
+ }, ...(ngDevMode ? [{ debugName: "selectableCycles" }] : []));
11593
12278
  this.selectedIndex = signal(0, ...(ngDevMode ? [{ debugName: "selectedIndex" }] : []));
11594
12279
  this.ogirinalSelectedCycle = computed(() => this.originalCycles()?.[this.selectedIndex()], ...(ngDevMode ? [{ debugName: "ogirinalSelectedCycle" }] : []));
11595
12280
  this.selectedCycle = computed(() => this.cycles()?.[this.selectedIndex()], ...(ngDevMode ? [{ debugName: "selectedCycle" }] : []));
@@ -11607,6 +12292,17 @@ class CyclesNodesComponent {
11607
12292
  : null, ...(ngDevMode ? [{ debugName: "selectedNode" }] : []));
11608
12293
  this.isOriginal = computed(() => this.dataState() === DataState.original, ...(ngDevMode ? [{ debugName: "isOriginal" }] : []));
11609
12294
  this.hasRecalculatedNodes = computed(() => this.cycles().some(({ aggregated }) => !aggregated), ...(ngDevMode ? [{ debugName: "hasRecalculatedNodes" }] : []));
12295
+ // an aggregated Cycle has no "original" to compare against - its values were calculated by the
12296
+ // aggregation, and the rules that produced them are what the Aggregation logs view shows
12297
+ this.hasAggregatedNodes = computed(() => this.cycles().some(({ aggregated }) => !!aggregated), ...(ngDevMode ? [{ debugName: "hasAggregatedNodes" }] : []));
12298
+ // the selected node is a Cycle or a grouped node (e.g. an Animal), and only a Cycle has products -
12299
+ // without one the aggregation shows its general rules, which apply whatever the product is
12300
+ this.primaryProduct = computed(() => {
12301
+ const products = this.selectedCycle()?.products ?? [];
12302
+ return products.find(({ primary }) => primary)?.term;
12303
+ }, ...(ngDevMode ? [{ debugName: "primaryProduct" }] : []));
12304
+ this.primaryProductTermType = computed(() => this.primaryProduct()?.termType ?? '', ...(ngDevMode ? [{ debugName: "primaryProductTermType" }] : []));
12305
+ this.primaryProductTermId = computed(() => this.primaryProduct()?.['@id'] ?? '', ...(ngDevMode ? [{ debugName: "primaryProductTermId" }] : []));
11610
12306
  this.showSwitchToRecalculated = computed(() => this.isOriginal() && this.hasRecalculatedNodes(), ...(ngDevMode ? [{ debugName: "showSwitchToRecalculated" }] : []));
11611
12307
  this.timelineValues = computed(() => filterValuesTimeline(this.selectedCycle()?.[this.selectedNodeKey()] || []), ...(ngDevMode ? [{ debugName: "timelineValues" }] : []));
11612
12308
  this.enableTimeline = computed(() => this.timelineValues().length > 0, ...(ngDevMode ? [{ debugName: "enableTimeline" }] : []));
@@ -11666,6 +12362,14 @@ class CyclesNodesComponent {
11666
12362
  this.selectedIndex.set(0);
11667
12363
  }
11668
12364
  });
12365
+ effect(() => {
12366
+ // switching to a logs view keeps whichever cycle was selected, which the view may not be able to
12367
+ // read (a recalculated cycle under Aggregation logs, or the reverse): move to one it can
12368
+ const selectable = this.selectableCycles();
12369
+ if (selectable.length > 0 && !selectable.some(({ index }) => index === this.selectedIndex())) {
12370
+ this.selectedIndex.set(selectable[0].index);
12371
+ }
12372
+ });
11669
12373
  }
11670
12374
  groupNodesByKey({ nodeKey }) {
11671
12375
  const nodesPerCycle = groupNodesByTerm(this.cycles(), nodeKey, filterBlankNode$1(this.filterTerm()), this.hideZeroValues(), this.hideIdenticalValues());
@@ -11695,7 +12399,7 @@ class CyclesNodesComponent {
11695
12399
  component.headerKeys.set(this.headerKeys());
11696
12400
  }
11697
12401
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CyclesNodesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
11698
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: CyclesNodesComponent, isStandalone: true, selector: "he-cycles-nodes", inputs: { dataState: { classPropertyName: "dataState", publicName: "dataState", isSignal: true, isRequired: false, transformFunction: null }, nodeKeys: { classPropertyName: "nodeKeys", publicName: "nodeKeys", isSignal: true, isRequired: true, transformFunction: null }, nodeKeyGroup: { classPropertyName: "nodeKeyGroup", publicName: "nodeKeyGroup", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isGroupNode()) {\n <div class=\"tabs is-mb-1 | group-nodes-tabs\">\n <ul>\n @for (value of groupNodeValues(); track value) {\n <li\n [class.is-active]=\"selectedGroup() === value.value\"\n [ngbTooltip]=\"value.term.name\"\n placement=\"top\"\n container=\"body\">\n <a (click)=\"selectedGroup.set(value.value)\">\n <span class=\"is-capitalized is-pr-1\">{{ nodeKeyGroup() | pluralize: 1 }}:</span>\n <span>{{ value.value }}</span>\n </a>\n </li>\n }\n </ul>\n </div>\n}\n\n@if (isNodeKeyAllowed()) {\n @switch (selectedView()) {\n @case (View.table) {\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (hasData()) {\n <he-data-table class=\"is-mt-3 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n @if (dataKeys().length > 1) {\n <tr class=\"has-text-weight-bold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\"></th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @let blankNodesCount = countGroupVisibleNodes(blankNodes);\n @if (blankNodesCount > 0) {\n <th [attr.colspan]=\"blankNodesCount\" [class.has-border-right]=\"!dataKeyLast\">\n <span>{{ dataKey | keyToLabel }}</span>\n </th>\n }\n }\n </tr>\n }\n <tr class=\"has-text-weight-semibold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\"></th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n <th\n [attr.title]=\"node.value.term.name\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n <he-node-link [node]=\"node.value.term\">\n <span\n [innerHtml]=\"\n node.value.term.name | ellipsis: 30 | compound: node.value.term.termType\n \"></span>\n </he-node-link>\n </th>\n }\n }\n }\n </tr>\n <tr class=\"is-italic has-text-weight-semibold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\">\n <a [href]=\"schemaBaseUrl + '/Cycle#functionalUnit'\" target=\"_blank\">Functional unit</a>\n </th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n <th\n [attr.title]=\"node.value.term.units\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n <span [innerHtml]=\"node.value.term.units | compound\"></span>\n <he-terms-units-description class=\"is-inline-block is-ml-2\" [term]=\"node.value.term\" />\n </th>\n }\n }\n }\n </tr>\n </thead>\n <tbody>\n @for (cycle of cycles(); track nodeVersionKey(cycle); let cycleIndex = $index) {\n <tr [class.is-suggested]=\"$any(cycle).suggested\">\n <td class=\"width-auto has-border-right\" [attr.title]=\"defaultLabel(cycle)\">\n <he-node-link [node]=\"cycleNode(cycle)\">\n <span class=\"has-text-ellipsis is-ellipsis-3\">\n @if ($any(cycle).suggested) {\n <he-svg-icon name=\"compare\" />\n } @else {\n <span>{{ cycleIndex + 1 }}.</span>\n }\n <span class=\"is-pl-1\">{{ defaultLabel(cycle) }}</span>\n </span>\n </he-node-link>\n </td>\n <td class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\">\n <he-cycles-functional-unit-measure [cycle]=\"cycle\" />\n </td>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n @let cycleData = node.value.values[nodeVersionKey(cycle)];\n <td\n class=\"is-nowrap\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n @if (cycleData) {\n <span\n class=\"trigger-popover\"\n [ngbPopover]=\"details\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow is-overflow-visible\"\n placement=\"left bottom auto\"\n container=\"body\"\n [popoverContext]=\"{ data: cycleData, cycle, key: dataKey }\">\n <span pointer>\n {{ cycleData.propertyValue | precision: 3 | default: '-' }}\n </span>\n <he-blank-node-state\n class=\"ml-1\"\n [dataState]=\"dataState()\"\n [node]=\"cycleData.node\"\n key=\"value\" />\n </span>\n } @else {\n <span>-</span>\n }\n </td>\n }\n }\n }\n </tr>\n }\n </tbody>\n </table>\n </he-data-table>\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-justify-content-space-between is-mt-2\">\n <he-blank-node-state-notice\n [dataState]=\"dataState()\"\n [showDeleted]=\"firstNodeKey() === BlankNodesKey.emissions\" />\n <div class=\"is-flex is-flex-wrap-wrap is-gap-8\">\n @if (showHideZeroValues()) {\n <div class=\"field is-relative is-mb-0\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"componentId() + 'hideZeroValues'\"\n name=\"hideZeroValues\"\n [(ngModel)]=\"hideZeroValues\" />\n <label [for]=\"componentId() + 'hideZeroValues'\">\n <span>\n Hide\n <b>0</b>\n values\n </span>\n </label>\n </div>\n }\n @if (cycles().length > 1) {\n <div class=\"field is-relative is-mb-0\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"componentId() + 'hideIdenticalValues'\"\n name=\"hideIdenticalValues\"\n [(ngModel)]=\"hideIdenticalValues\" />\n <label [for]=\"componentId() + 'hideIdenticalValues'\">\n <span>Hide identical values</span>\n </label>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"is-pt-3 has-text-centered\">\n <span>No data available</span>\n @if (filterTerm()) {\n <span class=\"is-pl-1\">matching your search criteria</span>\n }\n <span>.</span>\n @if (showSwitchToRecalculated()) {\n <span>\n Switch to\n <code>recalculated</code>\n version.\n </span>\n }\n </div>\n }\n }\n @case (View.chart) {\n @switch (firstNodeKey()) {\n @case (BlankNodesKey.emissions) {\n <he-cycles-emissions-chart [cycles]=\"cycles()\">\n <ng-container *ngTemplateOutlet=\"selectView\" />\n </he-cycles-emissions-chart>\n }\n }\n }\n @case (View.timeline) {\n <he-cycles-nodes-timeline\n [values]=\"timelineValues()\"\n [minDate]=\"selectedCycle().startDate\"\n [maxDate]=\"selectedCycle().endDate\">\n <ng-container *ngTemplateOutlet=\"selectView\" />\n </he-cycles-nodes-timeline>\n }\n @case (View.logs) {\n <!-- keep the cycle selector visible even when the selected cycle has no grouped node (e.g. a cycle\n with no transformations under \"Transformation: Inputs\"), so the user can switch to one that has -->\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (selectedNode()) {\n <he-node-logs-models\n [node]=\"selectedNode()\"\n [cycle]=\"selectedNode()\"\n [nodeKey]=\"selectedNodeKey()\"\n [logsKey]=\"selectedLogsKey()\"\n [jlogParentKey]=\"nodeKeyGroup()\"\n [jlogParentIndex]=\"jlogParentIndex()\"\n [originalValues]=\"selectedOriginalValues()\"\n [recalculatedValues]=\"selectedRecalculatedValues()\"\n [filterTermTypes]=\"filterTermTypes()\">\n @if (nodeKeys().length > 1) {\n <div class=\"tabs is-m-0\">\n <ul>\n @for (nodeKey of nodeKeys(); track nodeKey) {\n <li [class.is-active]=\"selectedNodeKey() === nodeKey\">\n <a (click)=\"selectedNodeKey.set(nodeKey)\">{{ nodeKey | keyToLabel }}</a>\n </li>\n }\n </ul>\n </div>\n }\n </he-node-logs-models>\n } @else if (isGroupNode()) {\n <p class=\"is-p-3 has-text-grey\">\n No recalculation logs for the selected {{ nodeKeyGroup() | pluralize: 1 }} in this cycle \u2014 select another\n cycle above.\n </p>\n }\n }\n }\n}\n\n<ng-template #selectView>\n <div class=\"is-flex is-gap-8 is-align-items-center is-justify-content-space-between\">\n <div class=\"is-flex is-gap-8 is-align-items-center\">\n @if (selectedView() === View.table) {\n @if (hasData()) {\n <button class=\"button is-small is-ghost is-p-2\" (click)=\"showDownload()\">\n <he-svg-icon name=\"download\" />\n </button>\n }\n <he-search-extend\n class=\"is-secondary\"\n collapsedClass=\"is-p-2\"\n placeholder=\"Filter terms by name\"\n (searchText)=\"filterTerm.set($event)\" />\n } @else if (showSelectCycle()) {\n <ng-container *ngTemplateOutlet=\"selectCycle\" />\n }\n </div>\n\n @if (views()?.length > 1) {\n <div class=\"field has-addons button-segments\">\n @for (view of views(); track view) {\n <div class=\"control\">\n <button\n class=\"button is-small\"\n [class.is-selected]=\"selectedView() === view\"\n (click)=\"selectedView.set(view)\">\n <he-svg-icon\n name=\"checkmark\"\n aria-hidden=\"true\"\n class=\"is-hidden-mobile\"\n [class.is-hidden-tablet]=\"selectedView() !== view\" />\n <he-svg-icon\n [name]=\"viewIcon[view]\"\n aria-hidden=\"true\"\n [class.is-hidden-tablet]=\"selectedView() === view\" />\n <span class=\"is-hidden-mobile\">{{ view }}</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n</ng-template>\n\n<ng-template #selectCycle>\n @if (cycles().length > 1) {\n <div class=\"field is-horizontal is-mb-0\">\n <div class=\"field-label is-normal\">\n <label class=\"label has-text-secondary is-nowrap\" for=\"selectCycle\">Cycle</label>\n </div>\n <div class=\"field-body\">\n <div class=\"field\">\n <div class=\"control is-expanded\">\n <div class=\"select is-small is-fullwidth\">\n <select (change)=\"selectIndex($event)\" if=\"selectCycle\">\n @for (value of cycles(); track nodeVersionKey(value); let cycleIndex = $index) {\n <option [value]=\"cycleIndex\">{{ cycleIndex + 1 }}. {{ defaultLabel(value) }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n</ng-template>\n\n<ng-template #details let-node=\"cycle\" let-data=\"data\" let-key=\"key\">\n <p>\n <b>{{ defaultLabel(node) }}</b>\n </p>\n <he-node-value-details\n class=\"is-overflow-visible\"\n [data]=\"data\"\n [dataState]=\"dataState()\"\n [nodeType]=\"node['@type']\"\n [dataKey]=\"key\"\n [aggregated]=\"node.aggregated\" />\n</ng-template>\n", styles: [":host{display:block}he-data-table ::ng-deep .table thead tr th:nth-child(2),he-data-table ::ng-deep .table tbody tr td:nth-child(2){max-width:106px;width:106px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgbPopover, selector: "[ngbPopover]", inputs: ["animation", "autoClose", "ngbPopover", "popoverTitle", "placement", "popperOptions", "triggers", "positionTarget", "container", "disablePopover", "popoverClass", "popoverContext", "openDelay", "closeDelay"], outputs: ["shown", "hidden"], exportAs: ["ngbPopover"] }, { kind: "directive", type: NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "popperOptions", "triggers", "positionTarget", "container", "disableTooltip", "tooltipClass", "tooltipContext", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: SearchExtendComponent, selector: "he-search-extend", inputs: ["value", "disabled", "placeholder", "class", "collapsedClass"], outputs: ["valueChange", "searchText"] }, { kind: "component", type: DataTableComponent, selector: "he-data-table", inputs: ["minHeight", "maxHeight", "small"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "component", type: TermsUnitsDescriptionComponent, selector: "he-terms-units-description", inputs: ["term", "iconTemplate"] }, { kind: "component", type: CyclesFunctionalUnitMeasureComponent, selector: "he-cycles-functional-unit-measure", inputs: ["cycle"] }, { kind: "component", type: BlankNodeStateComponent, selector: "he-blank-node-state", inputs: ["dataState", "nodeType", "dataKey", "key", "node", "state", "linkClass"] }, { kind: "component", type: BlankNodeStateNoticeComponent, selector: "he-blank-node-state-notice", inputs: ["dataState", "showDeleted"] }, { kind: "component", type: CyclesEmissionsChartComponent, selector: "he-cycles-emissions-chart", inputs: ["cycles"] }, { kind: "component", type: CyclesNodesTimelineComponent, selector: "he-cycles-nodes-timeline", inputs: ["values", "maxDate", "minDate"] }, { kind: "component", type: NodeLogsModelsComponent, selector: "he-node-logs-models", inputs: ["node", "nodeKey", "originalValues", "recalculatedValues", "terms", "filterTermTypes", "filterTermTypesLabel", "logsKey", "noDataMessage", "cycle", "jlogParentKey", "jlogParentIndex"] }, { kind: "component", type: NodeValueDetailsComponent, selector: "he-node-value-details", inputs: ["data", "nodeType", "dataState", "dataKey", "aggregated"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: CompoundPipe, name: "compound" }, { kind: "pipe", type: DefaultPipe, name: "default" }, { kind: "pipe", type: EllipsisPipe, name: "ellipsis" }, { kind: "pipe", type: KeyToLabelPipe, name: "keyToLabel" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }, { kind: "pipe", type: PluralizePipe, name: "pluralize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12402
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: CyclesNodesComponent, isStandalone: true, selector: "he-cycles-nodes", inputs: { dataState: { classPropertyName: "dataState", publicName: "dataState", isSignal: true, isRequired: false, transformFunction: null }, nodeKeys: { classPropertyName: "nodeKeys", publicName: "nodeKeys", isSignal: true, isRequired: true, transformFunction: null }, nodeKeyGroup: { classPropertyName: "nodeKeyGroup", publicName: "nodeKeyGroup", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (isGroupNode()) {\n <div class=\"tabs is-mb-1 | group-nodes-tabs\">\n <ul>\n @for (value of groupNodeValues(); track value) {\n <li\n [class.is-active]=\"selectedGroup() === value.value\"\n [ngbTooltip]=\"value.term.name\"\n placement=\"top\"\n container=\"body\">\n <a (click)=\"selectedGroup.set(value.value)\">\n <span class=\"is-capitalized is-pr-1\">{{ nodeKeyGroup() | pluralize: 1 }}:</span>\n <span>{{ value.value }}</span>\n </a>\n </li>\n }\n </ul>\n </div>\n}\n\n@if (isNodeKeyAllowed()) {\n @switch (selectedView()) {\n @case (View.table) {\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (hasData()) {\n <he-data-table class=\"is-mt-3 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n @if (dataKeys().length > 1) {\n <tr class=\"has-text-weight-bold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\"></th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @let blankNodesCount = countGroupVisibleNodes(blankNodes);\n @if (blankNodesCount > 0) {\n <th [attr.colspan]=\"blankNodesCount\" [class.has-border-right]=\"!dataKeyLast\">\n <span>{{ dataKey | keyToLabel }}</span>\n </th>\n }\n }\n </tr>\n }\n <tr class=\"has-text-weight-semibold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\"></th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n <th\n [attr.title]=\"node.value.term.name\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n <he-node-link [node]=\"node.value.term\">\n <span\n [innerHtml]=\"\n node.value.term.name | ellipsis: 30 | compound: node.value.term.termType\n \"></span>\n </he-node-link>\n </th>\n }\n }\n }\n </tr>\n <tr class=\"is-italic has-text-weight-semibold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\">\n <a [href]=\"schemaBaseUrl + '/Cycle#functionalUnit'\" target=\"_blank\">Functional unit</a>\n </th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n <th\n [attr.title]=\"node.value.term.units\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n <span [innerHtml]=\"node.value.term.units | compound\"></span>\n <he-terms-units-description class=\"is-inline-block is-ml-2\" [term]=\"node.value.term\" />\n </th>\n }\n }\n }\n </tr>\n </thead>\n <tbody>\n @for (cycle of cycles(); track nodeVersionKey(cycle); let cycleIndex = $index) {\n <tr [class.is-suggested]=\"$any(cycle).suggested\">\n <td class=\"width-auto has-border-right\" [attr.title]=\"defaultLabel(cycle)\">\n <he-node-link [node]=\"cycleNode(cycle)\">\n <span class=\"has-text-ellipsis is-ellipsis-3\">\n @if ($any(cycle).suggested) {\n <he-svg-icon name=\"compare\" />\n } @else {\n <span>{{ cycleIndex + 1 }}.</span>\n }\n <span class=\"is-pl-1\">{{ defaultLabel(cycle) }}</span>\n </span>\n </he-node-link>\n </td>\n <td class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\">\n <he-cycles-functional-unit-measure [cycle]=\"cycle\" />\n </td>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n @let cycleData = node.value.values[nodeVersionKey(cycle)];\n <td\n class=\"is-nowrap\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n @if (cycleData) {\n <span\n class=\"trigger-popover\"\n [ngbPopover]=\"details\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow is-overflow-visible\"\n placement=\"left bottom auto\"\n container=\"body\"\n [popoverContext]=\"{ data: cycleData, cycle, key: dataKey }\">\n <span pointer>\n {{ cycleData.propertyValue | precision: 3 | default: '-' }}\n </span>\n <he-blank-node-state\n class=\"ml-1\"\n [dataState]=\"dataState()\"\n [node]=\"cycleData.node\"\n key=\"value\" />\n </span>\n } @else {\n <span>-</span>\n }\n </td>\n }\n }\n }\n </tr>\n }\n </tbody>\n </table>\n </he-data-table>\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-justify-content-space-between is-mt-2\">\n <he-blank-node-state-notice\n [dataState]=\"dataState()\"\n [showDeleted]=\"firstNodeKey() === BlankNodesKey.emissions\" />\n <div class=\"is-flex is-flex-wrap-wrap is-gap-8\">\n @if (showHideZeroValues()) {\n <div class=\"field is-relative is-mb-0\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"componentId() + 'hideZeroValues'\"\n name=\"hideZeroValues\"\n [(ngModel)]=\"hideZeroValues\" />\n <label [for]=\"componentId() + 'hideZeroValues'\">\n <span>\n Hide\n <b>0</b>\n values\n </span>\n </label>\n </div>\n }\n @if (cycles().length > 1) {\n <div class=\"field is-relative is-mb-0\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"componentId() + 'hideIdenticalValues'\"\n name=\"hideIdenticalValues\"\n [(ngModel)]=\"hideIdenticalValues\" />\n <label [for]=\"componentId() + 'hideIdenticalValues'\">\n <span>Hide identical values</span>\n </label>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"is-pt-3 has-text-centered\">\n <span>No data available</span>\n @if (filterTerm()) {\n <span class=\"is-pl-1\">matching your search criteria</span>\n }\n <span>.</span>\n @if (showSwitchToRecalculated()) {\n <span>\n Switch to\n <code>recalculated</code>\n version.\n </span>\n }\n </div>\n }\n }\n @case (View.chart) {\n @switch (firstNodeKey()) {\n @case (BlankNodesKey.emissions) {\n <he-cycles-emissions-chart [cycles]=\"cycles()\">\n <ng-container *ngTemplateOutlet=\"selectView\" />\n </he-cycles-emissions-chart>\n }\n }\n }\n @case (View.aggregationLogs) {\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (selectedNode()) {\n <he-node-aggregation-logs\n [node]=\"selectedNode()\"\n [nodeKey]=\"selectedNodeKey()\"\n [termType]=\"primaryProductTermType()\"\n [termId]=\"primaryProductTermId()\"\n [jlogParentKey]=\"nodeKeyGroup()\"\n [jlogParentIndex]=\"jlogParentIndex()\">\n @if (nodeKeys().length > 1) {\n <div class=\"tabs is-m-0\">\n <ul>\n @for (nodeKey of nodeKeys(); track nodeKey) {\n <li [class.is-active]=\"selectedNodeKey() === nodeKey\">\n <a (click)=\"selectedNodeKey.set(nodeKey)\">{{ nodeKey | keyToLabel }}</a>\n </li>\n }\n </ul>\n </div>\n }\n </he-node-aggregation-logs>\n }\n }\n @case (View.timeline) {\n <he-cycles-nodes-timeline\n [values]=\"timelineValues()\"\n [minDate]=\"selectedCycle().startDate\"\n [maxDate]=\"selectedCycle().endDate\">\n <ng-container *ngTemplateOutlet=\"selectView\" />\n </he-cycles-nodes-timeline>\n }\n @case (View.logs) {\n <!-- keep the cycle selector visible even when the selected cycle has no grouped node (e.g. a cycle\n with no transformations under \"Transformation: Inputs\"), so the user can switch to one that has -->\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (selectedNode()) {\n <he-node-logs-models\n [node]=\"selectedNode()\"\n [cycle]=\"selectedNode()\"\n [nodeKey]=\"selectedNodeKey()\"\n [logsKey]=\"selectedLogsKey()\"\n [jlogParentKey]=\"nodeKeyGroup()\"\n [jlogParentIndex]=\"jlogParentIndex()\"\n [originalValues]=\"selectedOriginalValues()\"\n [recalculatedValues]=\"selectedRecalculatedValues()\"\n [filterTermTypes]=\"filterTermTypes()\">\n @if (nodeKeys().length > 1) {\n <div class=\"tabs is-m-0\">\n <ul>\n @for (nodeKey of nodeKeys(); track nodeKey) {\n <li [class.is-active]=\"selectedNodeKey() === nodeKey\">\n <a (click)=\"selectedNodeKey.set(nodeKey)\">{{ nodeKey | keyToLabel }}</a>\n </li>\n }\n </ul>\n </div>\n }\n </he-node-logs-models>\n } @else if (isGroupNode()) {\n <p class=\"is-p-3 has-text-grey\">\n No recalculation logs for the selected {{ nodeKeyGroup() | pluralize: 1 }} in this cycle \u2014 select another\n cycle above.\n </p>\n }\n }\n }\n}\n\n<ng-template #selectView>\n <div class=\"is-flex is-gap-8 is-align-items-center is-justify-content-space-between\">\n <div class=\"is-flex is-gap-8 is-align-items-center\">\n @if (selectedView() === View.table) {\n @if (hasData()) {\n <button class=\"button is-small is-ghost is-p-2\" (click)=\"showDownload()\">\n <he-svg-icon name=\"download\" />\n </button>\n }\n <he-search-extend\n class=\"is-secondary\"\n collapsedClass=\"is-p-2\"\n placeholder=\"Filter terms by name\"\n (searchText)=\"filterTerm.set($event)\" />\n } @else if (showSelectCycle()) {\n <ng-container *ngTemplateOutlet=\"selectCycle\" />\n }\n </div>\n\n @if (views()?.length > 1) {\n <div class=\"field has-addons button-segments\">\n @for (view of views(); track view) {\n <div class=\"control\">\n <button\n class=\"button is-small\"\n [class.is-selected]=\"selectedView() === view\"\n (click)=\"selectedView.set(view)\">\n <he-svg-icon\n name=\"checkmark\"\n aria-hidden=\"true\"\n class=\"is-hidden-mobile\"\n [class.is-hidden-tablet]=\"selectedView() !== view\" />\n <he-svg-icon\n [name]=\"viewIcon[view]\"\n aria-hidden=\"true\"\n [class.is-hidden-tablet]=\"selectedView() === view\" />\n <span class=\"is-hidden-mobile\">{{ view }}</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n</ng-template>\n\n<ng-template #selectCycle>\n @if (selectableCycles().length > 1) {\n <div class=\"field is-horizontal is-mb-0\">\n <div class=\"field-label is-normal\">\n <label class=\"label has-text-secondary is-nowrap\" for=\"selectCycle\">Cycle</label>\n </div>\n <div class=\"field-body\">\n <div class=\"field\">\n <div class=\"control is-expanded\">\n <div class=\"select is-small is-fullwidth\">\n <select [value]=\"selectedIndex()\" (change)=\"selectIndex($event)\" if=\"selectCycle\">\n @for (entry of selectableCycles(); track nodeVersionKey(entry.cycle)) {\n <option [value]=\"entry.index\">{{ entry.index + 1 }}. {{ defaultLabel(entry.cycle) }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n</ng-template>\n\n<ng-template #details let-node=\"cycle\" let-data=\"data\" let-key=\"key\">\n <p>\n <b>{{ defaultLabel(node) }}</b>\n </p>\n <he-node-value-details\n class=\"is-overflow-visible\"\n [data]=\"data\"\n [dataState]=\"dataState()\"\n [nodeType]=\"node['@type']\"\n [dataKey]=\"key\"\n [aggregated]=\"node.aggregated\" />\n</ng-template>\n", styles: [":host{display:block}he-data-table ::ng-deep .table thead tr th:nth-child(2),he-data-table ::ng-deep .table tbody tr td:nth-child(2){max-width:106px;width:106px}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgbPopover, selector: "[ngbPopover]", inputs: ["animation", "autoClose", "ngbPopover", "popoverTitle", "placement", "popperOptions", "triggers", "positionTarget", "container", "disablePopover", "popoverClass", "popoverContext", "openDelay", "closeDelay"], outputs: ["shown", "hidden"], exportAs: ["ngbPopover"] }, { kind: "directive", type: NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "popperOptions", "triggers", "positionTarget", "container", "disableTooltip", "tooltipClass", "tooltipContext", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "component", type: SearchExtendComponent, selector: "he-search-extend", inputs: ["value", "disabled", "placeholder", "class", "collapsedClass"], outputs: ["valueChange", "searchText"] }, { kind: "component", type: DataTableComponent, selector: "he-data-table", inputs: ["minHeight", "maxHeight", "small"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "component", type: TermsUnitsDescriptionComponent, selector: "he-terms-units-description", inputs: ["term", "iconTemplate"] }, { kind: "component", type: CyclesFunctionalUnitMeasureComponent, selector: "he-cycles-functional-unit-measure", inputs: ["cycle"] }, { kind: "component", type: BlankNodeStateComponent, selector: "he-blank-node-state", inputs: ["dataState", "nodeType", "dataKey", "key", "node", "state", "linkClass"] }, { kind: "component", type: BlankNodeStateNoticeComponent, selector: "he-blank-node-state-notice", inputs: ["dataState", "showDeleted"] }, { kind: "component", type: CyclesEmissionsChartComponent, selector: "he-cycles-emissions-chart", inputs: ["cycles"] }, { kind: "component", type: CyclesNodesTimelineComponent, selector: "he-cycles-nodes-timeline", inputs: ["values", "maxDate", "minDate"] }, { kind: "component", type: NodeLogsModelsComponent, selector: "he-node-logs-models", inputs: ["node", "nodeKey", "originalValues", "recalculatedValues", "terms", "filterTermTypes", "filterTermTypesLabel", "logsKey", "noDataMessage", "cycle", "jlogParentKey", "jlogParentIndex"] }, { kind: "component", type: NodeAggregationLogsComponent, selector: "he-node-aggregation-logs", inputs: ["node", "nodeKey", "termType", "termId", "jlogParentKey", "jlogParentIndex"] }, { kind: "component", type: NodeValueDetailsComponent, selector: "he-node-value-details", inputs: ["data", "nodeType", "dataState", "dataKey", "aggregated"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: CompoundPipe, name: "compound" }, { kind: "pipe", type: DefaultPipe, name: "default" }, { kind: "pipe", type: EllipsisPipe, name: "ellipsis" }, { kind: "pipe", type: KeyToLabelPipe, name: "keyToLabel" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }, { kind: "pipe", type: PluralizePipe, name: "pluralize" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
11699
12403
  }
11700
12404
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: CyclesNodesComponent, decorators: [{
11701
12405
  type: Component$1,
@@ -11714,6 +12418,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
11714
12418
  CyclesEmissionsChartComponent,
11715
12419
  CyclesNodesTimelineComponent,
11716
12420
  NodeLogsModelsComponent,
12421
+ NodeAggregationLogsComponent,
11717
12422
  NodeValueDetailsComponent,
11718
12423
  FormsModule,
11719
12424
  CompoundPipe,
@@ -11722,7 +12427,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
11722
12427
  KeyToLabelPipe,
11723
12428
  PrecisionPipe,
11724
12429
  PluralizePipe
11725
- ], template: "@if (isGroupNode()) {\n <div class=\"tabs is-mb-1 | group-nodes-tabs\">\n <ul>\n @for (value of groupNodeValues(); track value) {\n <li\n [class.is-active]=\"selectedGroup() === value.value\"\n [ngbTooltip]=\"value.term.name\"\n placement=\"top\"\n container=\"body\">\n <a (click)=\"selectedGroup.set(value.value)\">\n <span class=\"is-capitalized is-pr-1\">{{ nodeKeyGroup() | pluralize: 1 }}:</span>\n <span>{{ value.value }}</span>\n </a>\n </li>\n }\n </ul>\n </div>\n}\n\n@if (isNodeKeyAllowed()) {\n @switch (selectedView()) {\n @case (View.table) {\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (hasData()) {\n <he-data-table class=\"is-mt-3 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n @if (dataKeys().length > 1) {\n <tr class=\"has-text-weight-bold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\"></th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @let blankNodesCount = countGroupVisibleNodes(blankNodes);\n @if (blankNodesCount > 0) {\n <th [attr.colspan]=\"blankNodesCount\" [class.has-border-right]=\"!dataKeyLast\">\n <span>{{ dataKey | keyToLabel }}</span>\n </th>\n }\n }\n </tr>\n }\n <tr class=\"has-text-weight-semibold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\"></th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n <th\n [attr.title]=\"node.value.term.name\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n <he-node-link [node]=\"node.value.term\">\n <span\n [innerHtml]=\"\n node.value.term.name | ellipsis: 30 | compound: node.value.term.termType\n \"></span>\n </he-node-link>\n </th>\n }\n }\n }\n </tr>\n <tr class=\"is-italic has-text-weight-semibold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\">\n <a [href]=\"schemaBaseUrl + '/Cycle#functionalUnit'\" target=\"_blank\">Functional unit</a>\n </th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n <th\n [attr.title]=\"node.value.term.units\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n <span [innerHtml]=\"node.value.term.units | compound\"></span>\n <he-terms-units-description class=\"is-inline-block is-ml-2\" [term]=\"node.value.term\" />\n </th>\n }\n }\n }\n </tr>\n </thead>\n <tbody>\n @for (cycle of cycles(); track nodeVersionKey(cycle); let cycleIndex = $index) {\n <tr [class.is-suggested]=\"$any(cycle).suggested\">\n <td class=\"width-auto has-border-right\" [attr.title]=\"defaultLabel(cycle)\">\n <he-node-link [node]=\"cycleNode(cycle)\">\n <span class=\"has-text-ellipsis is-ellipsis-3\">\n @if ($any(cycle).suggested) {\n <he-svg-icon name=\"compare\" />\n } @else {\n <span>{{ cycleIndex + 1 }}.</span>\n }\n <span class=\"is-pl-1\">{{ defaultLabel(cycle) }}</span>\n </span>\n </he-node-link>\n </td>\n <td class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\">\n <he-cycles-functional-unit-measure [cycle]=\"cycle\" />\n </td>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n @let cycleData = node.value.values[nodeVersionKey(cycle)];\n <td\n class=\"is-nowrap\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n @if (cycleData) {\n <span\n class=\"trigger-popover\"\n [ngbPopover]=\"details\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow is-overflow-visible\"\n placement=\"left bottom auto\"\n container=\"body\"\n [popoverContext]=\"{ data: cycleData, cycle, key: dataKey }\">\n <span pointer>\n {{ cycleData.propertyValue | precision: 3 | default: '-' }}\n </span>\n <he-blank-node-state\n class=\"ml-1\"\n [dataState]=\"dataState()\"\n [node]=\"cycleData.node\"\n key=\"value\" />\n </span>\n } @else {\n <span>-</span>\n }\n </td>\n }\n }\n }\n </tr>\n }\n </tbody>\n </table>\n </he-data-table>\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-justify-content-space-between is-mt-2\">\n <he-blank-node-state-notice\n [dataState]=\"dataState()\"\n [showDeleted]=\"firstNodeKey() === BlankNodesKey.emissions\" />\n <div class=\"is-flex is-flex-wrap-wrap is-gap-8\">\n @if (showHideZeroValues()) {\n <div class=\"field is-relative is-mb-0\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"componentId() + 'hideZeroValues'\"\n name=\"hideZeroValues\"\n [(ngModel)]=\"hideZeroValues\" />\n <label [for]=\"componentId() + 'hideZeroValues'\">\n <span>\n Hide\n <b>0</b>\n values\n </span>\n </label>\n </div>\n }\n @if (cycles().length > 1) {\n <div class=\"field is-relative is-mb-0\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"componentId() + 'hideIdenticalValues'\"\n name=\"hideIdenticalValues\"\n [(ngModel)]=\"hideIdenticalValues\" />\n <label [for]=\"componentId() + 'hideIdenticalValues'\">\n <span>Hide identical values</span>\n </label>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"is-pt-3 has-text-centered\">\n <span>No data available</span>\n @if (filterTerm()) {\n <span class=\"is-pl-1\">matching your search criteria</span>\n }\n <span>.</span>\n @if (showSwitchToRecalculated()) {\n <span>\n Switch to\n <code>recalculated</code>\n version.\n </span>\n }\n </div>\n }\n }\n @case (View.chart) {\n @switch (firstNodeKey()) {\n @case (BlankNodesKey.emissions) {\n <he-cycles-emissions-chart [cycles]=\"cycles()\">\n <ng-container *ngTemplateOutlet=\"selectView\" />\n </he-cycles-emissions-chart>\n }\n }\n }\n @case (View.timeline) {\n <he-cycles-nodes-timeline\n [values]=\"timelineValues()\"\n [minDate]=\"selectedCycle().startDate\"\n [maxDate]=\"selectedCycle().endDate\">\n <ng-container *ngTemplateOutlet=\"selectView\" />\n </he-cycles-nodes-timeline>\n }\n @case (View.logs) {\n <!-- keep the cycle selector visible even when the selected cycle has no grouped node (e.g. a cycle\n with no transformations under \"Transformation: Inputs\"), so the user can switch to one that has -->\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (selectedNode()) {\n <he-node-logs-models\n [node]=\"selectedNode()\"\n [cycle]=\"selectedNode()\"\n [nodeKey]=\"selectedNodeKey()\"\n [logsKey]=\"selectedLogsKey()\"\n [jlogParentKey]=\"nodeKeyGroup()\"\n [jlogParentIndex]=\"jlogParentIndex()\"\n [originalValues]=\"selectedOriginalValues()\"\n [recalculatedValues]=\"selectedRecalculatedValues()\"\n [filterTermTypes]=\"filterTermTypes()\">\n @if (nodeKeys().length > 1) {\n <div class=\"tabs is-m-0\">\n <ul>\n @for (nodeKey of nodeKeys(); track nodeKey) {\n <li [class.is-active]=\"selectedNodeKey() === nodeKey\">\n <a (click)=\"selectedNodeKey.set(nodeKey)\">{{ nodeKey | keyToLabel }}</a>\n </li>\n }\n </ul>\n </div>\n }\n </he-node-logs-models>\n } @else if (isGroupNode()) {\n <p class=\"is-p-3 has-text-grey\">\n No recalculation logs for the selected {{ nodeKeyGroup() | pluralize: 1 }} in this cycle \u2014 select another\n cycle above.\n </p>\n }\n }\n }\n}\n\n<ng-template #selectView>\n <div class=\"is-flex is-gap-8 is-align-items-center is-justify-content-space-between\">\n <div class=\"is-flex is-gap-8 is-align-items-center\">\n @if (selectedView() === View.table) {\n @if (hasData()) {\n <button class=\"button is-small is-ghost is-p-2\" (click)=\"showDownload()\">\n <he-svg-icon name=\"download\" />\n </button>\n }\n <he-search-extend\n class=\"is-secondary\"\n collapsedClass=\"is-p-2\"\n placeholder=\"Filter terms by name\"\n (searchText)=\"filterTerm.set($event)\" />\n } @else if (showSelectCycle()) {\n <ng-container *ngTemplateOutlet=\"selectCycle\" />\n }\n </div>\n\n @if (views()?.length > 1) {\n <div class=\"field has-addons button-segments\">\n @for (view of views(); track view) {\n <div class=\"control\">\n <button\n class=\"button is-small\"\n [class.is-selected]=\"selectedView() === view\"\n (click)=\"selectedView.set(view)\">\n <he-svg-icon\n name=\"checkmark\"\n aria-hidden=\"true\"\n class=\"is-hidden-mobile\"\n [class.is-hidden-tablet]=\"selectedView() !== view\" />\n <he-svg-icon\n [name]=\"viewIcon[view]\"\n aria-hidden=\"true\"\n [class.is-hidden-tablet]=\"selectedView() === view\" />\n <span class=\"is-hidden-mobile\">{{ view }}</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n</ng-template>\n\n<ng-template #selectCycle>\n @if (cycles().length > 1) {\n <div class=\"field is-horizontal is-mb-0\">\n <div class=\"field-label is-normal\">\n <label class=\"label has-text-secondary is-nowrap\" for=\"selectCycle\">Cycle</label>\n </div>\n <div class=\"field-body\">\n <div class=\"field\">\n <div class=\"control is-expanded\">\n <div class=\"select is-small is-fullwidth\">\n <select (change)=\"selectIndex($event)\" if=\"selectCycle\">\n @for (value of cycles(); track nodeVersionKey(value); let cycleIndex = $index) {\n <option [value]=\"cycleIndex\">{{ cycleIndex + 1 }}. {{ defaultLabel(value) }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n</ng-template>\n\n<ng-template #details let-node=\"cycle\" let-data=\"data\" let-key=\"key\">\n <p>\n <b>{{ defaultLabel(node) }}</b>\n </p>\n <he-node-value-details\n class=\"is-overflow-visible\"\n [data]=\"data\"\n [dataState]=\"dataState()\"\n [nodeType]=\"node['@type']\"\n [dataKey]=\"key\"\n [aggregated]=\"node.aggregated\" />\n</ng-template>\n", styles: [":host{display:block}he-data-table ::ng-deep .table thead tr th:nth-child(2),he-data-table ::ng-deep .table tbody tr td:nth-child(2){max-width:106px;width:106px}\n"] }]
12430
+ ], template: "@if (isGroupNode()) {\n <div class=\"tabs is-mb-1 | group-nodes-tabs\">\n <ul>\n @for (value of groupNodeValues(); track value) {\n <li\n [class.is-active]=\"selectedGroup() === value.value\"\n [ngbTooltip]=\"value.term.name\"\n placement=\"top\"\n container=\"body\">\n <a (click)=\"selectedGroup.set(value.value)\">\n <span class=\"is-capitalized is-pr-1\">{{ nodeKeyGroup() | pluralize: 1 }}:</span>\n <span>{{ value.value }}</span>\n </a>\n </li>\n }\n </ul>\n </div>\n}\n\n@if (isNodeKeyAllowed()) {\n @switch (selectedView()) {\n @case (View.table) {\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (hasData()) {\n <he-data-table class=\"is-mt-3 is-bordered\" [small]=\"true\" maxHeight=\"320\">\n <table class=\"table is-fullwidth is-narrow is-striped\">\n <thead>\n @if (dataKeys().length > 1) {\n <tr class=\"has-text-weight-bold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\"></th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @let blankNodesCount = countGroupVisibleNodes(blankNodes);\n @if (blankNodesCount > 0) {\n <th [attr.colspan]=\"blankNodesCount\" [class.has-border-right]=\"!dataKeyLast\">\n <span>{{ dataKey | keyToLabel }}</span>\n </th>\n }\n }\n </tr>\n }\n <tr class=\"has-text-weight-semibold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\"></th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n <th\n [attr.title]=\"node.value.term.name\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n <he-node-link [node]=\"node.value.term\">\n <span\n [innerHtml]=\"\n node.value.term.name | ellipsis: 30 | compound: node.value.term.termType\n \"></span>\n </he-node-link>\n </th>\n }\n }\n }\n </tr>\n <tr class=\"is-italic has-text-weight-semibold\">\n <th class=\"width-auto has-border-right\"></th>\n <th class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\">\n <a [href]=\"schemaBaseUrl + '/Cycle#functionalUnit'\" target=\"_blank\">Functional unit</a>\n </th>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n <th\n [attr.title]=\"node.value.term.units\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n <span [innerHtml]=\"node.value.term.units | compound\"></span>\n <he-terms-units-description class=\"is-inline-block is-ml-2\" [term]=\"node.value.term\" />\n </th>\n }\n }\n }\n </tr>\n </thead>\n <tbody>\n @for (cycle of cycles(); track nodeVersionKey(cycle); let cycleIndex = $index) {\n <tr [class.is-suggested]=\"$any(cycle).suggested\">\n <td class=\"width-auto has-border-right\" [attr.title]=\"defaultLabel(cycle)\">\n <he-node-link [node]=\"cycleNode(cycle)\">\n <span class=\"has-text-ellipsis is-ellipsis-3\">\n @if ($any(cycle).suggested) {\n <he-svg-icon name=\"compare\" />\n } @else {\n <span>{{ cycleIndex + 1 }}.</span>\n }\n <span class=\"is-pl-1\">{{ defaultLabel(cycle) }}</span>\n </span>\n </he-node-link>\n </td>\n <td class=\"has-border-right\" [class.is-hidden]=\"isGroupNode()\">\n <he-cycles-functional-unit-measure [cycle]=\"cycle\" />\n </td>\n @for (dataKey of dataKeys(); track dataKey; let dataKeyLast = $last) {\n @let blankNodes = data()[dataKey];\n @for (node of blankNodes; track node.value.term.name; let nodeLast = $last) {\n @if (node.value.visible) {\n @let cycleData = node.value.values[nodeVersionKey(cycle)];\n <td\n class=\"is-nowrap\"\n [class.has-border-right]=\"dataKeys().length > 1 && !dataKeyLast && nodeLast\">\n @if (cycleData) {\n <span\n class=\"trigger-popover\"\n [ngbPopover]=\"details\"\n autoClose=\"outside\"\n popoverClass=\"is-narrow is-overflow-visible\"\n placement=\"left bottom auto\"\n container=\"body\"\n [popoverContext]=\"{ data: cycleData, cycle, key: dataKey }\">\n <span pointer>\n {{ cycleData.propertyValue | precision: 3 | default: '-' }}\n </span>\n <he-blank-node-state\n class=\"ml-1\"\n [dataState]=\"dataState()\"\n [node]=\"cycleData.node\"\n key=\"value\" />\n </span>\n } @else {\n <span>-</span>\n }\n </td>\n }\n }\n }\n </tr>\n }\n </tbody>\n </table>\n </he-data-table>\n <div class=\"is-flex is-align-items-center is-flex-wrap-wrap is-justify-content-space-between is-mt-2\">\n <he-blank-node-state-notice\n [dataState]=\"dataState()\"\n [showDeleted]=\"firstNodeKey() === BlankNodesKey.emissions\" />\n <div class=\"is-flex is-flex-wrap-wrap is-gap-8\">\n @if (showHideZeroValues()) {\n <div class=\"field is-relative is-mb-0\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"componentId() + 'hideZeroValues'\"\n name=\"hideZeroValues\"\n [(ngModel)]=\"hideZeroValues\" />\n <label [for]=\"componentId() + 'hideZeroValues'\">\n <span>\n Hide\n <b>0</b>\n values\n </span>\n </label>\n </div>\n }\n @if (cycles().length > 1) {\n <div class=\"field is-relative is-mb-0\">\n <input\n type=\"checkbox\"\n class=\"switch is-small is-rounded\"\n [id]=\"componentId() + 'hideIdenticalValues'\"\n name=\"hideIdenticalValues\"\n [(ngModel)]=\"hideIdenticalValues\" />\n <label [for]=\"componentId() + 'hideIdenticalValues'\">\n <span>Hide identical values</span>\n </label>\n </div>\n }\n </div>\n </div>\n } @else {\n <div class=\"is-pt-3 has-text-centered\">\n <span>No data available</span>\n @if (filterTerm()) {\n <span class=\"is-pl-1\">matching your search criteria</span>\n }\n <span>.</span>\n @if (showSwitchToRecalculated()) {\n <span>\n Switch to\n <code>recalculated</code>\n version.\n </span>\n }\n </div>\n }\n }\n @case (View.chart) {\n @switch (firstNodeKey()) {\n @case (BlankNodesKey.emissions) {\n <he-cycles-emissions-chart [cycles]=\"cycles()\">\n <ng-container *ngTemplateOutlet=\"selectView\" />\n </he-cycles-emissions-chart>\n }\n }\n }\n @case (View.aggregationLogs) {\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (selectedNode()) {\n <he-node-aggregation-logs\n [node]=\"selectedNode()\"\n [nodeKey]=\"selectedNodeKey()\"\n [termType]=\"primaryProductTermType()\"\n [termId]=\"primaryProductTermId()\"\n [jlogParentKey]=\"nodeKeyGroup()\"\n [jlogParentIndex]=\"jlogParentIndex()\">\n @if (nodeKeys().length > 1) {\n <div class=\"tabs is-m-0\">\n <ul>\n @for (nodeKey of nodeKeys(); track nodeKey) {\n <li [class.is-active]=\"selectedNodeKey() === nodeKey\">\n <a (click)=\"selectedNodeKey.set(nodeKey)\">{{ nodeKey | keyToLabel }}</a>\n </li>\n }\n </ul>\n </div>\n }\n </he-node-aggregation-logs>\n }\n }\n @case (View.timeline) {\n <he-cycles-nodes-timeline\n [values]=\"timelineValues()\"\n [minDate]=\"selectedCycle().startDate\"\n [maxDate]=\"selectedCycle().endDate\">\n <ng-container *ngTemplateOutlet=\"selectView\" />\n </he-cycles-nodes-timeline>\n }\n @case (View.logs) {\n <!-- keep the cycle selector visible even when the selected cycle has no grouped node (e.g. a cycle\n with no transformations under \"Transformation: Inputs\"), so the user can switch to one that has -->\n <ng-container *ngTemplateOutlet=\"selectView\" />\n\n @if (selectedNode()) {\n <he-node-logs-models\n [node]=\"selectedNode()\"\n [cycle]=\"selectedNode()\"\n [nodeKey]=\"selectedNodeKey()\"\n [logsKey]=\"selectedLogsKey()\"\n [jlogParentKey]=\"nodeKeyGroup()\"\n [jlogParentIndex]=\"jlogParentIndex()\"\n [originalValues]=\"selectedOriginalValues()\"\n [recalculatedValues]=\"selectedRecalculatedValues()\"\n [filterTermTypes]=\"filterTermTypes()\">\n @if (nodeKeys().length > 1) {\n <div class=\"tabs is-m-0\">\n <ul>\n @for (nodeKey of nodeKeys(); track nodeKey) {\n <li [class.is-active]=\"selectedNodeKey() === nodeKey\">\n <a (click)=\"selectedNodeKey.set(nodeKey)\">{{ nodeKey | keyToLabel }}</a>\n </li>\n }\n </ul>\n </div>\n }\n </he-node-logs-models>\n } @else if (isGroupNode()) {\n <p class=\"is-p-3 has-text-grey\">\n No recalculation logs for the selected {{ nodeKeyGroup() | pluralize: 1 }} in this cycle \u2014 select another\n cycle above.\n </p>\n }\n }\n }\n}\n\n<ng-template #selectView>\n <div class=\"is-flex is-gap-8 is-align-items-center is-justify-content-space-between\">\n <div class=\"is-flex is-gap-8 is-align-items-center\">\n @if (selectedView() === View.table) {\n @if (hasData()) {\n <button class=\"button is-small is-ghost is-p-2\" (click)=\"showDownload()\">\n <he-svg-icon name=\"download\" />\n </button>\n }\n <he-search-extend\n class=\"is-secondary\"\n collapsedClass=\"is-p-2\"\n placeholder=\"Filter terms by name\"\n (searchText)=\"filterTerm.set($event)\" />\n } @else if (showSelectCycle()) {\n <ng-container *ngTemplateOutlet=\"selectCycle\" />\n }\n </div>\n\n @if (views()?.length > 1) {\n <div class=\"field has-addons button-segments\">\n @for (view of views(); track view) {\n <div class=\"control\">\n <button\n class=\"button is-small\"\n [class.is-selected]=\"selectedView() === view\"\n (click)=\"selectedView.set(view)\">\n <he-svg-icon\n name=\"checkmark\"\n aria-hidden=\"true\"\n class=\"is-hidden-mobile\"\n [class.is-hidden-tablet]=\"selectedView() !== view\" />\n <he-svg-icon\n [name]=\"viewIcon[view]\"\n aria-hidden=\"true\"\n [class.is-hidden-tablet]=\"selectedView() === view\" />\n <span class=\"is-hidden-mobile\">{{ view }}</span>\n </button>\n </div>\n }\n </div>\n }\n </div>\n</ng-template>\n\n<ng-template #selectCycle>\n @if (selectableCycles().length > 1) {\n <div class=\"field is-horizontal is-mb-0\">\n <div class=\"field-label is-normal\">\n <label class=\"label has-text-secondary is-nowrap\" for=\"selectCycle\">Cycle</label>\n </div>\n <div class=\"field-body\">\n <div class=\"field\">\n <div class=\"control is-expanded\">\n <div class=\"select is-small is-fullwidth\">\n <select [value]=\"selectedIndex()\" (change)=\"selectIndex($event)\" if=\"selectCycle\">\n @for (entry of selectableCycles(); track nodeVersionKey(entry.cycle)) {\n <option [value]=\"entry.index\">{{ entry.index + 1 }}. {{ defaultLabel(entry.cycle) }}</option>\n }\n </select>\n </div>\n </div>\n </div>\n </div>\n </div>\n }\n</ng-template>\n\n<ng-template #details let-node=\"cycle\" let-data=\"data\" let-key=\"key\">\n <p>\n <b>{{ defaultLabel(node) }}</b>\n </p>\n <he-node-value-details\n class=\"is-overflow-visible\"\n [data]=\"data\"\n [dataState]=\"dataState()\"\n [nodeType]=\"node['@type']\"\n [dataKey]=\"key\"\n [aggregated]=\"node.aggregated\" />\n</ng-template>\n", styles: [":host{display:block}he-data-table ::ng-deep .table thead tr th:nth-child(2),he-data-table ::ng-deep .table tbody tr td:nth-child(2){max-width:106px;width:106px}\n"] }]
11726
12431
  }], ctorParameters: () => [], propDecorators: { dataState: [{ type: i0.Input, args: [{ isSignal: true, alias: "dataState", required: false }] }], nodeKeys: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKeys", required: true }] }], nodeKeyGroup: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeKeyGroup", required: false }] }] } });
11727
12432
 
11728
12433
  class CyclesResultComponent {
@@ -11933,18 +12638,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
11933
12638
 
11934
12639
  // default for all product termType, can be overwritten
11935
12640
  const defaultMinNbObservations = 50;
12641
+ // the `.jlog` section the aggregation stores the quality score in, and the model tagging its entries
12642
+ const jlogKey = 'aggregatedQualityScore';
12643
+ const jlogModel = 'aggregation';
12644
+ const isTrue = (value) => value === true || value === 'True';
12645
+ /**
12646
+ * Whether a quantity was logged: one that was not found is logged as `None`, which the `.jlog`
12647
+ * carries as it is written.
12648
+ */
12649
+ const hasLogValue = (value) => !isUndefined(value) && !isNaN(+value);
11936
12650
  const isScoreValid = {
11937
- 0: ({ all_included }) => all_included === 'True',
12651
+ 0: ({ all_included }) => isTrue(all_included),
11938
12652
  1: ({ nb_observations, min_nb_observations }) => +nb_observations >= (+min_nb_observations || defaultMinNbObservations),
11939
12653
  2: ({ delta, delta_min, yield_delta, yield_delta_min }) => isUndefined(delta) ? +yield_delta <= +yield_delta_min : +delta <= +delta_min,
11940
- 3: ({ is_complete }) => is_complete === 'True',
12654
+ 3: ({ is_complete }) => isTrue(is_complete),
11941
12655
  4: ({ production_delta, production_delta_min }) => +production_delta >= +production_delta_min
11942
12656
  };
12657
+ /**
12658
+ * The quality score recorded in the `.jlog`, merged into a single set of values.
12659
+ * Empty for every aggregation that predates it, which only has the text logs.
12660
+ */
12661
+ const jlogScore = (jlog) => (jlog?.[jlogKey]?.logs ?? [])
12662
+ .filter(({ model }) => model === jlogModel)
12663
+ .reduce((values, { model: _model, ...fields }) => ({ ...values, ...fields }), {});
12664
+ const toValidScores = (logs) => Object.fromEntries(Object.entries(isScoreValid).map(([key, isValid]) => [key, isValid(logs)]));
12665
+
11943
12666
  const getCountryName = (node) => node?.site?.country?.name || node.name?.split(' - ')?.[1]?.trim();
11944
12667
  class NodeAggregatedQualityScoreComponent {
11945
12668
  constructor() {
11946
12669
  this.searchService = inject(HeSearchService);
11947
12670
  this.nodeService = inject(HeNodeService);
12671
+ this.nodeLogsModelsService = inject(NodeLogsModelsService);
11948
12672
  this.node = input.required(...(ngDevMode ? [{ debugName: "node" }] : []));
11949
12673
  this.country = input(...(ngDevMode ? [undefined, { debugName: "country" }] : []));
11950
12674
  /**
@@ -11984,22 +12708,34 @@ class NodeAggregatedQualityScoreComponent {
11984
12708
  : of(undefined)
11985
12709
  });
11986
12710
  this.countryId = computed(() => this.countryResource.value()?.['@id'], ...(ngDevMode ? [{ debugName: "countryId" }] : []));
11987
- this.logsResource = rxResource({
12711
+ // the score as recorded in the `.jlog`, which is where every new aggregation logs it
12712
+ this.jlogResource = rxResource({
11988
12713
  params: () => ({
11989
12714
  showInfo: this.showInfo(),
11990
12715
  node: this.node()
11991
12716
  }),
11992
- stream: ({ params: { showInfo, node } }) => showInfo
11993
- ? this.nodeService
12717
+ stream: ({ params: { showInfo, node } }) => showInfo ? this.nodeLogsModelsService.getJLog$(node) : of({})
12718
+ });
12719
+ this.jlogLogs = computed(() => jlogScore(this.jlogResource.value() ?? {}), ...(ngDevMode ? [{ debugName: "jlogLogs" }] : []));
12720
+ this.loadingJLog = computed(() => !this.jlogResource.hasValue() || this.jlogResource.isLoading(), ...(ngDevMode ? [{ debugName: "loadingJLog" }] : []));
12721
+ this.useJLog = computed(() => !isEmpty(this.jlogLogs()), ...(ngDevMode ? [{ debugName: "useJLog" }] : []));
12722
+ // only fetch the legacy text logs once we know the `.jlog` does not carry the score
12723
+ this.logsResource = rxResource({
12724
+ params: () => ({
12725
+ skip: !this.showInfo() || this.loadingJLog() || this.useJLog(),
12726
+ node: this.node()
12727
+ }),
12728
+ stream: ({ params: { skip, node } }) => skip
12729
+ ? of({})
12730
+ : this.nodeService
11994
12731
  .getLog$({
11995
12732
  '@type': node['@type'],
11996
12733
  '@id': node['@id']
11997
12734
  })
11998
12735
  .pipe(map(value => (value ? parseLines(value) : [])), mergeAll(), filter(({ data: { message } }) => !!message), map(({ data: { message } }) => parseMessage(message)), filter(({ id }) => id === this.node()['@id']), reduce((a, b) => ({ ...a, ...b }), {}))
11999
- : of({})
12000
12736
  });
12001
- this.logs = computed(() => this.logsResource.value() ?? {}, ...(ngDevMode ? [{ debugName: "logs" }] : []));
12002
- this.validScores = computed(() => Object.fromEntries(Object.entries(isScoreValid).map(([key, value]) => [key, value(this.logs())])), ...(ngDevMode ? [{ debugName: "validScores" }] : []));
12737
+ this.logs = computed(() => this.useJLog() ? this.jlogLogs() : (this.logsResource.value() ?? {}), ...(ngDevMode ? [{ debugName: "logs" }] : []));
12738
+ this.validScores = computed(() => toValidScores(this.logs()), ...(ngDevMode ? [{ debugName: "validScores" }] : []));
12003
12739
  this.minObservations = computed(() => +this.logs().min_nb_observations || defaultMinNbObservations, ...(ngDevMode ? [{ debugName: "minObservations" }] : []));
12004
12740
  this.observations = computed(() => +this.logs().nb_observations, ...(ngDevMode ? [{ debugName: "observations" }] : []));
12005
12741
  this.missingEmissionIds = computed(() => this.logs().missing_emissions?.split(';') ?? [], ...(ngDevMode ? [{ debugName: "missingEmissionIds" }] : []));
@@ -12045,10 +12781,16 @@ class NodeAggregatedQualityScoreComponent {
12045
12781
  this.isGlobal = computed(() => this.countryId()?.startsWith('region-'), ...(ngDevMode ? [{ debugName: "isGlobal" }] : []));
12046
12782
  this.schemaBaseUrl = computed(() => [schemaBaseUrl(), this.node()?.['@type']].join('/'), ...(ngDevMode ? [{ debugName: "schemaBaseUrl" }] : []));
12047
12783
  this.schemaUrl = computed(() => `${this.schemaBaseUrl()}#aggregatedQualityScore`, ...(ngDevMode ? [{ debugName: "schemaUrl" }] : []));
12048
- this.hasProductionQuantity = computed(() => this.logs()?.region_production_quantity !== 'None', ...(ngDevMode ? [{ debugName: "hasProductionQuantity" }] : []));
12784
+ this.hasFaostatYield = computed(() => hasLogValue(this.logs()?.faostat_yield), ...(ngDevMode ? [{ debugName: "hasFaostatYield" }] : []));
12785
+ this.hasProductionQuantity = computed(() => hasLogValue(this.logs()?.region_production_quantity), ...(ngDevMode ? [{ debugName: "hasProductionQuantity" }] : []));
12049
12786
  this.regionProductionQuantity = computed(() => +this.logs()?.region_production_quantity, ...(ngDevMode ? [{ debugName: "regionProductionQuantity" }] : []));
12050
12787
  this.countriesProductionQuantity = computed(() => +this.logs()?.countries_production_quantity, ...(ngDevMode ? [{ debugName: "countriesProductionQuantity" }] : []));
12051
- this.loading = computed(() => [this.logsResource.isLoading(), this.countryResource.isLoading(), this.missingEmissionsResource.isLoading()].some(Boolean), ...(ngDevMode ? [{ debugName: "loading" }] : []));
12788
+ this.loading = computed(() => [
12789
+ this.jlogResource.isLoading(),
12790
+ this.logsResource.isLoading(),
12791
+ this.countryResource.isLoading(),
12792
+ this.missingEmissionsResource.isLoading()
12793
+ ].some(Boolean), ...(ngDevMode ? [{ debugName: "loading" }] : []));
12052
12794
  }
12053
12795
  get hidden() {
12054
12796
  return isUndefined(this.score());
@@ -12060,7 +12802,7 @@ class NodeAggregatedQualityScoreComponent {
12060
12802
  return term['@id'];
12061
12803
  }
12062
12804
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregatedQualityScoreComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12063
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregatedQualityScoreComponent, isStandalone: true, selector: "he-node-aggregated-quality-score", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, country: { classPropertyName: "country", publicName: "country", isSignal: true, isRequired: false, transformFunction: null }, showInfo: { classPropertyName: "showInfo", publicName: "showInfo", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-none": "this.hidden" } }, ngImport: i0, template: "@if (score() >= 0) {\n @switch (mode()) {\n @case ('tag') {\n <div class=\"tags has-addons\">\n <a class=\"tag\" [href]=\"schemaUrl()\" target=\"_blank\">Data Quality</a>\n <ng-container *ngTemplateOutlet=\"quality\" />\n @if (showInfo()) {\n <span\n class=\"tag pointer\"\n [ngbTooltip]=\"logsTooltip\"\n [placement]=\"placement() + ' auto'\"\n triggers=\"click\"\n container=\"body\"\n autoClose=\"outside\"\n tooltipClass=\"quality-tooltip\">\n @if (loading()) {\n <he-svg-icon name=\"loading\" animation=\"spin\" />\n } @else if (logs()) {\n <he-svg-icon name=\"help-circle\" />\n }\n </span>\n }\n </div>\n }\n @default {\n <ng-container *ngTemplateOutlet=\"quality\" />\n }\n }\n}\n\n<ng-template #quality>\n <span\n [ngStyle]=\"{\n 'background-color': scoreColor()\n }\"\n class=\"tag-score | tag is-primary has-text-white has-text-weight-semibold\">\n <span>{{ scoreLevel() | uppercase }}</span>\n </span>\n</ng-template>\n\n<ng-template #logsTooltip>\n <div>\n <p class=\"mb-2\">\n Up to {{ scoreMax() }} points are awarded for the following criteria, where \"low\" is 0 to\n {{ scoreMax() - 3 }} points, \"medium\" is {{ scoreMax() - 2 }} to {{ scoreMax() - 1 }} points, and \"high\" is\n {{ scoreMax() }} points:\n </p>\n @if (validScores()) {\n <div class=\"table-container\">\n <table class=\"table is-fullwidth has-text-white has-background-secondary\">\n <thead>\n <tr>\n <th class=\"has-text-white\">Condition</th>\n <th class=\"has-text-white\">Met</th>\n @if (isCycle()) {\n <th class=\"has-text-white\">Details</th>\n }\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>No aggregated emissions included in the system boundary are missing</td>\n <td [class.has-text-success]=\"validScores()[0]\" [class.has-text-danger]=\"!validScores()[0]\">\n {{ validScores()[0] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingEmissions()?.length) {\n <span>{{ missingEmissions().length }}</span>\n <span class=\"is-pl-1\">{{ 'emission' | pluralize: missingEmissions().length }} are missing:</span>\n <div class=\"is-mt-2 | missing-emissions\">\n <ul class=\"is-list-style-disc is-pl-4\">\n @for (term of missingEmissions(); track trackByTerm($index, term); let l = $last) {\n <li>\n <he-node-link class=\"is-inline-block is-pl-1\" linkClass=\"is-dark\" [node]=\"term\">\n <span class=\"is-nowrap has-text-ellipsis\">{{ term.name }}</span>\n </he-node-link>\n </li>\n }\n </ul>\n </div>\n } @else if (logs()?.included_emissions) {\n <span>\n All {{ logs().included_emissions }} {{ 'emission' | pluralize: +logs().included_emissions }} for\n this product are included.\n </span>\n }\n </td>\n }\n </tr>\n <tr>\n <td>The aggregation is based on data from over {{ minObservations() }} Cycles</td>\n <td [class.has-text-success]=\"validScores()[1]\" [class.has-text-danger]=\"!validScores()[1]\">\n {{ validScores()[1] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n <span>Based on {{ observations() }} {{ 'Cycle' | pluralize: observations() }}.</span>\n </td>\n }\n </tr>\n @if (isCrop()) {\n <tr>\n <td>\n The difference between yield per hectare here and FAOSTAT is less than \u00B1{{\n logs().yield_delta_min || logs().delta_min | number\n }}%\n </td>\n <td [class.has-text-success]=\"validScores()[2]\" [class.has-text-danger]=\"!validScores()[2]\">\n {{ validScores()[2] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (logs()?.faostat_yield !== 'None') {\n <span>FAOSTAT yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.faostat_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha;</span>\n <span class=\"is-pl-1\">Aggregated data yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.product_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha.</span>\n } @else {\n No yield found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n <tr>\n <td>\n Data completeness is\n <code class=\"is-p-1\">true</code>\n for all priority areas\n </td>\n <td [class.has-text-success]=\"validScores()[3]\" [class.has-text-danger]=\"!validScores()[3]\">\n {{ validScores()[3] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingCompletenessFields()?.length) {\n <span>{{ missingCompletenessFields().length }}</span>\n <span class=\"is-pl-1\">completeness areas missing data:</span>\n @for (field of missingCompletenessFields(); track field; let l = $last) {\n <a class=\"is-inline-block is-pl-1 is-dark\" [href]=\"completenessUrl(field)\" target=\"_blank\">\n {{ field }}\n </a>\n <span>{{ l ? '.' : ',' }}</span>\n }\n }\n </td>\n }\n </tr>\n @if (isCrop() && isGlobal()) {\n <tr>\n <td>Aggregated countries represent at least 75% of {{ countryName() }} production</td>\n <td [class.has-text-success]=\"validScores()[4]\" [class.has-text-danger]=\"!validScores()[4]\">\n {{ validScores()[4] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasProductionQuantity()) {\n <span>{{ countryName() }} production quantity =</span>\n <span class=\"is-pl-1\">{{ regionProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes;</span>\n <span class=\"is-pl-1\">Production of countries included in the aggregation =</span>\n <span class=\"is-pl-1\">{{ countriesProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes.</span>\n } @else {\n No production quantity found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:inline-block}::ng-deep .quality-tooltip{min-width:500px}.missing-emissions{max-height:250px;overflow-y:auto}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: NgbTooltipModule }, { kind: "directive", type: i1$1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "popperOptions", "triggers", "positionTarget", "container", "disableTooltip", "tooltipClass", "tooltipContext", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "pipe", type: PluralizePipe, name: "pluralize" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }, { kind: "pipe", type: UpperCasePipe, name: "uppercase" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12805
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: NodeAggregatedQualityScoreComponent, isStandalone: true, selector: "he-node-aggregated-quality-score", inputs: { node: { classPropertyName: "node", publicName: "node", isSignal: true, isRequired: true, transformFunction: null }, country: { classPropertyName: "country", publicName: "country", isSignal: true, isRequired: false, transformFunction: null }, showInfo: { classPropertyName: "showInfo", publicName: "showInfo", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.is-none": "this.hidden" } }, ngImport: i0, template: "@if (score() >= 0) {\n @switch (mode()) {\n @case ('tag') {\n <div class=\"tags has-addons\">\n <a class=\"tag\" [href]=\"schemaUrl()\" target=\"_blank\">Data Quality</a>\n <ng-container *ngTemplateOutlet=\"quality\" />\n @if (showInfo()) {\n <span\n class=\"tag pointer\"\n [ngbTooltip]=\"logsTooltip\"\n [placement]=\"placement() + ' auto'\"\n triggers=\"click\"\n container=\"body\"\n autoClose=\"outside\"\n tooltipClass=\"quality-tooltip\">\n @if (loading()) {\n <he-svg-icon name=\"loading\" animation=\"spin\" />\n } @else if (logs()) {\n <he-svg-icon name=\"help-circle\" />\n }\n </span>\n }\n </div>\n }\n @default {\n <ng-container *ngTemplateOutlet=\"quality\" />\n }\n }\n}\n\n<ng-template #quality>\n <span\n [ngStyle]=\"{\n 'background-color': scoreColor()\n }\"\n class=\"tag-score | tag is-primary has-text-white has-text-weight-semibold\">\n <span>{{ scoreLevel() | uppercase }}</span>\n </span>\n</ng-template>\n\n<ng-template #logsTooltip>\n <div>\n <p class=\"mb-2\">\n Up to {{ scoreMax() }} points are awarded for the following criteria, where \"low\" is 0 to\n {{ scoreMax() - 3 }} points, \"medium\" is {{ scoreMax() - 2 }} to {{ scoreMax() - 1 }} points, and \"high\" is\n {{ scoreMax() }} points:\n </p>\n @if (validScores()) {\n <div class=\"table-container\">\n <table class=\"table is-fullwidth has-text-white has-background-secondary\">\n <thead>\n <tr>\n <th class=\"has-text-white\">Condition</th>\n <th class=\"has-text-white\">Met</th>\n @if (isCycle()) {\n <th class=\"has-text-white\">Details</th>\n }\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>No aggregated emissions included in the system boundary are missing</td>\n <td [class.has-text-success]=\"validScores()[0]\" [class.has-text-danger]=\"!validScores()[0]\">\n {{ validScores()[0] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingEmissions()?.length) {\n <span>{{ missingEmissions().length }}</span>\n <span class=\"is-pl-1\">{{ 'emission' | pluralize: missingEmissions().length }} are missing:</span>\n <div class=\"is-mt-2 | missing-emissions\">\n <ul class=\"is-list-style-disc is-pl-4\">\n @for (term of missingEmissions(); track trackByTerm($index, term); let l = $last) {\n <li>\n <he-node-link class=\"is-inline-block is-pl-1\" linkClass=\"is-dark\" [node]=\"term\">\n <span class=\"is-nowrap has-text-ellipsis\">{{ term.name }}</span>\n </he-node-link>\n </li>\n }\n </ul>\n </div>\n } @else if (logs()?.included_emissions) {\n <span>\n All {{ logs().included_emissions }} {{ 'emission' | pluralize: +logs().included_emissions }} for\n this product are included.\n </span>\n }\n </td>\n }\n </tr>\n <tr>\n <td>The aggregation is based on data from over {{ minObservations() }} Cycles</td>\n <td [class.has-text-success]=\"validScores()[1]\" [class.has-text-danger]=\"!validScores()[1]\">\n {{ validScores()[1] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n <span>Based on {{ observations() }} {{ 'Cycle' | pluralize: observations() }}.</span>\n </td>\n }\n </tr>\n @if (isCrop()) {\n <tr>\n <td>\n The difference between yield per hectare here and FAOSTAT is less than \u00B1{{\n logs().yield_delta_min || logs().delta_min | number\n }}%\n </td>\n <td [class.has-text-success]=\"validScores()[2]\" [class.has-text-danger]=\"!validScores()[2]\">\n {{ validScores()[2] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasFaostatYield()) {\n <span>FAOSTAT yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.faostat_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha;</span>\n <span class=\"is-pl-1\">Aggregated data yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.product_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha.</span>\n } @else {\n No yield found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n <tr>\n <td>\n Data completeness is\n <code class=\"is-p-1\">true</code>\n for all priority areas\n </td>\n <td [class.has-text-success]=\"validScores()[3]\" [class.has-text-danger]=\"!validScores()[3]\">\n {{ validScores()[3] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingCompletenessFields()?.length) {\n <span>{{ missingCompletenessFields().length }}</span>\n <span class=\"is-pl-1\">completeness areas missing data:</span>\n @for (field of missingCompletenessFields(); track field; let l = $last) {\n <a class=\"is-inline-block is-pl-1 is-dark\" [href]=\"completenessUrl(field)\" target=\"_blank\">\n {{ field }}\n </a>\n <span>{{ l ? '.' : ',' }}</span>\n }\n }\n </td>\n }\n </tr>\n @if (isCrop() && isGlobal()) {\n <tr>\n <td>Aggregated countries represent at least 75% of {{ countryName() }} production</td>\n <td [class.has-text-success]=\"validScores()[4]\" [class.has-text-danger]=\"!validScores()[4]\">\n {{ validScores()[4] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasProductionQuantity()) {\n <span>{{ countryName() }} production quantity =</span>\n <span class=\"is-pl-1\">{{ regionProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes;</span>\n <span class=\"is-pl-1\">Production of countries included in the aggregation =</span>\n <span class=\"is-pl-1\">{{ countriesProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes.</span>\n } @else {\n No production quantity found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:inline-block}::ng-deep .quality-tooltip{min-width:500px}.missing-emissions{max-height:250px;overflow-y:auto}\n"], dependencies: [{ kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: NodeLinkComponent, selector: "he-node-link", inputs: ["node", "dataState", "showExternalLink", "linkClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: NgbTooltipModule }, { kind: "directive", type: i1$1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "popperOptions", "triggers", "positionTarget", "container", "disableTooltip", "tooltipClass", "tooltipContext", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { kind: "component", type: HESvgIconComponent, selector: "he-svg-icon", inputs: ["name", "size", "animation"] }, { kind: "pipe", type: PluralizePipe, name: "pluralize" }, { kind: "pipe", type: DecimalPipe, name: "number" }, { kind: "pipe", type: PrecisionPipe, name: "precision" }, { kind: "pipe", type: UpperCasePipe, name: "uppercase" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12064
12806
  }
12065
12807
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NodeAggregatedQualityScoreComponent, decorators: [{
12066
12808
  type: Component$1,
@@ -12074,7 +12816,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
12074
12816
  NgTemplateOutlet,
12075
12817
  NgbTooltipModule,
12076
12818
  HESvgIconComponent
12077
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (score() >= 0) {\n @switch (mode()) {\n @case ('tag') {\n <div class=\"tags has-addons\">\n <a class=\"tag\" [href]=\"schemaUrl()\" target=\"_blank\">Data Quality</a>\n <ng-container *ngTemplateOutlet=\"quality\" />\n @if (showInfo()) {\n <span\n class=\"tag pointer\"\n [ngbTooltip]=\"logsTooltip\"\n [placement]=\"placement() + ' auto'\"\n triggers=\"click\"\n container=\"body\"\n autoClose=\"outside\"\n tooltipClass=\"quality-tooltip\">\n @if (loading()) {\n <he-svg-icon name=\"loading\" animation=\"spin\" />\n } @else if (logs()) {\n <he-svg-icon name=\"help-circle\" />\n }\n </span>\n }\n </div>\n }\n @default {\n <ng-container *ngTemplateOutlet=\"quality\" />\n }\n }\n}\n\n<ng-template #quality>\n <span\n [ngStyle]=\"{\n 'background-color': scoreColor()\n }\"\n class=\"tag-score | tag is-primary has-text-white has-text-weight-semibold\">\n <span>{{ scoreLevel() | uppercase }}</span>\n </span>\n</ng-template>\n\n<ng-template #logsTooltip>\n <div>\n <p class=\"mb-2\">\n Up to {{ scoreMax() }} points are awarded for the following criteria, where \"low\" is 0 to\n {{ scoreMax() - 3 }} points, \"medium\" is {{ scoreMax() - 2 }} to {{ scoreMax() - 1 }} points, and \"high\" is\n {{ scoreMax() }} points:\n </p>\n @if (validScores()) {\n <div class=\"table-container\">\n <table class=\"table is-fullwidth has-text-white has-background-secondary\">\n <thead>\n <tr>\n <th class=\"has-text-white\">Condition</th>\n <th class=\"has-text-white\">Met</th>\n @if (isCycle()) {\n <th class=\"has-text-white\">Details</th>\n }\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>No aggregated emissions included in the system boundary are missing</td>\n <td [class.has-text-success]=\"validScores()[0]\" [class.has-text-danger]=\"!validScores()[0]\">\n {{ validScores()[0] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingEmissions()?.length) {\n <span>{{ missingEmissions().length }}</span>\n <span class=\"is-pl-1\">{{ 'emission' | pluralize: missingEmissions().length }} are missing:</span>\n <div class=\"is-mt-2 | missing-emissions\">\n <ul class=\"is-list-style-disc is-pl-4\">\n @for (term of missingEmissions(); track trackByTerm($index, term); let l = $last) {\n <li>\n <he-node-link class=\"is-inline-block is-pl-1\" linkClass=\"is-dark\" [node]=\"term\">\n <span class=\"is-nowrap has-text-ellipsis\">{{ term.name }}</span>\n </he-node-link>\n </li>\n }\n </ul>\n </div>\n } @else if (logs()?.included_emissions) {\n <span>\n All {{ logs().included_emissions }} {{ 'emission' | pluralize: +logs().included_emissions }} for\n this product are included.\n </span>\n }\n </td>\n }\n </tr>\n <tr>\n <td>The aggregation is based on data from over {{ minObservations() }} Cycles</td>\n <td [class.has-text-success]=\"validScores()[1]\" [class.has-text-danger]=\"!validScores()[1]\">\n {{ validScores()[1] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n <span>Based on {{ observations() }} {{ 'Cycle' | pluralize: observations() }}.</span>\n </td>\n }\n </tr>\n @if (isCrop()) {\n <tr>\n <td>\n The difference between yield per hectare here and FAOSTAT is less than \u00B1{{\n logs().yield_delta_min || logs().delta_min | number\n }}%\n </td>\n <td [class.has-text-success]=\"validScores()[2]\" [class.has-text-danger]=\"!validScores()[2]\">\n {{ validScores()[2] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (logs()?.faostat_yield !== 'None') {\n <span>FAOSTAT yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.faostat_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha;</span>\n <span class=\"is-pl-1\">Aggregated data yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.product_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha.</span>\n } @else {\n No yield found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n <tr>\n <td>\n Data completeness is\n <code class=\"is-p-1\">true</code>\n for all priority areas\n </td>\n <td [class.has-text-success]=\"validScores()[3]\" [class.has-text-danger]=\"!validScores()[3]\">\n {{ validScores()[3] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingCompletenessFields()?.length) {\n <span>{{ missingCompletenessFields().length }}</span>\n <span class=\"is-pl-1\">completeness areas missing data:</span>\n @for (field of missingCompletenessFields(); track field; let l = $last) {\n <a class=\"is-inline-block is-pl-1 is-dark\" [href]=\"completenessUrl(field)\" target=\"_blank\">\n {{ field }}\n </a>\n <span>{{ l ? '.' : ',' }}</span>\n }\n }\n </td>\n }\n </tr>\n @if (isCrop() && isGlobal()) {\n <tr>\n <td>Aggregated countries represent at least 75% of {{ countryName() }} production</td>\n <td [class.has-text-success]=\"validScores()[4]\" [class.has-text-danger]=\"!validScores()[4]\">\n {{ validScores()[4] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasProductionQuantity()) {\n <span>{{ countryName() }} production quantity =</span>\n <span class=\"is-pl-1\">{{ regionProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes;</span>\n <span class=\"is-pl-1\">Production of countries included in the aggregation =</span>\n <span class=\"is-pl-1\">{{ countriesProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes.</span>\n } @else {\n No production quantity found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:inline-block}::ng-deep .quality-tooltip{min-width:500px}.missing-emissions{max-height:250px;overflow-y:auto}\n"] }]
12819
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (score() >= 0) {\n @switch (mode()) {\n @case ('tag') {\n <div class=\"tags has-addons\">\n <a class=\"tag\" [href]=\"schemaUrl()\" target=\"_blank\">Data Quality</a>\n <ng-container *ngTemplateOutlet=\"quality\" />\n @if (showInfo()) {\n <span\n class=\"tag pointer\"\n [ngbTooltip]=\"logsTooltip\"\n [placement]=\"placement() + ' auto'\"\n triggers=\"click\"\n container=\"body\"\n autoClose=\"outside\"\n tooltipClass=\"quality-tooltip\">\n @if (loading()) {\n <he-svg-icon name=\"loading\" animation=\"spin\" />\n } @else if (logs()) {\n <he-svg-icon name=\"help-circle\" />\n }\n </span>\n }\n </div>\n }\n @default {\n <ng-container *ngTemplateOutlet=\"quality\" />\n }\n }\n}\n\n<ng-template #quality>\n <span\n [ngStyle]=\"{\n 'background-color': scoreColor()\n }\"\n class=\"tag-score | tag is-primary has-text-white has-text-weight-semibold\">\n <span>{{ scoreLevel() | uppercase }}</span>\n </span>\n</ng-template>\n\n<ng-template #logsTooltip>\n <div>\n <p class=\"mb-2\">\n Up to {{ scoreMax() }} points are awarded for the following criteria, where \"low\" is 0 to\n {{ scoreMax() - 3 }} points, \"medium\" is {{ scoreMax() - 2 }} to {{ scoreMax() - 1 }} points, and \"high\" is\n {{ scoreMax() }} points:\n </p>\n @if (validScores()) {\n <div class=\"table-container\">\n <table class=\"table is-fullwidth has-text-white has-background-secondary\">\n <thead>\n <tr>\n <th class=\"has-text-white\">Condition</th>\n <th class=\"has-text-white\">Met</th>\n @if (isCycle()) {\n <th class=\"has-text-white\">Details</th>\n }\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>No aggregated emissions included in the system boundary are missing</td>\n <td [class.has-text-success]=\"validScores()[0]\" [class.has-text-danger]=\"!validScores()[0]\">\n {{ validScores()[0] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingEmissions()?.length) {\n <span>{{ missingEmissions().length }}</span>\n <span class=\"is-pl-1\">{{ 'emission' | pluralize: missingEmissions().length }} are missing:</span>\n <div class=\"is-mt-2 | missing-emissions\">\n <ul class=\"is-list-style-disc is-pl-4\">\n @for (term of missingEmissions(); track trackByTerm($index, term); let l = $last) {\n <li>\n <he-node-link class=\"is-inline-block is-pl-1\" linkClass=\"is-dark\" [node]=\"term\">\n <span class=\"is-nowrap has-text-ellipsis\">{{ term.name }}</span>\n </he-node-link>\n </li>\n }\n </ul>\n </div>\n } @else if (logs()?.included_emissions) {\n <span>\n All {{ logs().included_emissions }} {{ 'emission' | pluralize: +logs().included_emissions }} for\n this product are included.\n </span>\n }\n </td>\n }\n </tr>\n <tr>\n <td>The aggregation is based on data from over {{ minObservations() }} Cycles</td>\n <td [class.has-text-success]=\"validScores()[1]\" [class.has-text-danger]=\"!validScores()[1]\">\n {{ validScores()[1] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n <span>Based on {{ observations() }} {{ 'Cycle' | pluralize: observations() }}.</span>\n </td>\n }\n </tr>\n @if (isCrop()) {\n <tr>\n <td>\n The difference between yield per hectare here and FAOSTAT is less than \u00B1{{\n logs().yield_delta_min || logs().delta_min | number\n }}%\n </td>\n <td [class.has-text-success]=\"validScores()[2]\" [class.has-text-danger]=\"!validScores()[2]\">\n {{ validScores()[2] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasFaostatYield()) {\n <span>FAOSTAT yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.faostat_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha;</span>\n <span class=\"is-pl-1\">Aggregated data yield =</span>\n <span class=\"is-pl-1\">{{ logs()?.product_yield | precision: 3 }}</span>\n <span class=\"is-pl-1\">kg/ha.</span>\n } @else {\n No yield found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n <tr>\n <td>\n Data completeness is\n <code class=\"is-p-1\">true</code>\n for all priority areas\n </td>\n <td [class.has-text-success]=\"validScores()[3]\" [class.has-text-danger]=\"!validScores()[3]\">\n {{ validScores()[3] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (missingCompletenessFields()?.length) {\n <span>{{ missingCompletenessFields().length }}</span>\n <span class=\"is-pl-1\">completeness areas missing data:</span>\n @for (field of missingCompletenessFields(); track field; let l = $last) {\n <a class=\"is-inline-block is-pl-1 is-dark\" [href]=\"completenessUrl(field)\" target=\"_blank\">\n {{ field }}\n </a>\n <span>{{ l ? '.' : ',' }}</span>\n }\n }\n </td>\n }\n </tr>\n @if (isCrop() && isGlobal()) {\n <tr>\n <td>Aggregated countries represent at least 75% of {{ countryName() }} production</td>\n <td [class.has-text-success]=\"validScores()[4]\" [class.has-text-danger]=\"!validScores()[4]\">\n {{ validScores()[4] ? 'Yes' : 'No' }}\n </td>\n @if (isCycle()) {\n <td>\n @if (hasProductionQuantity()) {\n <span>{{ countryName() }} production quantity =</span>\n <span class=\"is-pl-1\">{{ regionProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes;</span>\n <span class=\"is-pl-1\">Production of countries included in the aggregation =</span>\n <span class=\"is-pl-1\">{{ countriesProductionQuantity() / 1000000 | number: '1.0-0' }}</span>\n <span class=\"is-pl-1\">million tonnes.</span>\n } @else {\n No production quantity found on FAOSTAT for this product.\n }\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </div>\n</ng-template>\n", styles: [":host{display:inline-block}::ng-deep .quality-tooltip{min-width:500px}.missing-emissions{max-height:250px;overflow-y:auto}\n"] }]
12078
12820
  }], propDecorators: { hidden: [{
12079
12821
  type: HostBinding,
12080
12822
  args: ['class.is-none']
@@ -15273,7 +16015,8 @@ class HierarchyChartComponent {
15273
16015
  handleNodeClick(_event, d) {
15274
16016
  d._open = !d._open;
15275
16017
  const switchingSelection = '_greyed' in d && d._greyed;
15276
- d.parent.children.forEach(child => {
16018
+ // the node can still be clicked while it animates out, by which time its parent is collapsed
16019
+ childrenOf(d.parent).forEach(child => {
15277
16020
  child._greyed = false;
15278
16021
  if (child !== d) {
15279
16022
  child._open = false;
@@ -16679,5 +17422,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
16679
17422
  * Generated bundle index. Do not edit.
16680
17423
  */
16681
17424
 
16682
- export { ARRAY_DELIMITER, ApplyPurePipe, BarChartComponent, BibliographiesSearchConfirmComponent, BlankNodeStateComponent, BlankNodeStateNoticeComponent, BlankNodeValueDeltaComponent, CapitalizePipe, ChartComponent, ChartConfigurationDirective, ChartExportButtonComponent, ChartTooltipComponent, ClickOutsideDirective, ClipboardComponent, CollapsibleBoxComponent, CollapsibleBoxStyle, ColorPalette, CompoundDirective, CompoundPipe, ContributionChartComponent, ControlValueAccessor, CycleNodesKeyGroup, CyclesCompletenessComponent, CyclesEmissionsCategoryService, CyclesEmissionsChartComponent, CyclesFunctionalUnitMeasureComponent, CyclesMetadataComponent, CyclesNodesComponent, CyclesNodesTimelineComponent, CyclesResultComponent, DataTableComponent, DefaultPipe, DeltaColour, DistributionChartComponent, DrawerContainerComponent, DurationPipe, EllipsisPipe, EngineModelsLinkComponent, EngineModelsLookupInfoComponent, EngineModelsStageComponent, EngineModelsStageDeepComponent, EngineModelsStageDeepService, EngineModelsVersionInfoComponent, EngineModelsVersionLinkComponent, EngineOrchestratorEditComponent, EngineRequirementsFormComponent, FileSizePipe, FileUploadErrorKeys, FilesDragDropDirective, FilesDropZoneComponent, FilesErrorSummaryComponent, FilesFormComponent, FilesFormEditableComponent, FilesUploadErrorsComponent, FilterAccordionComponent, GUIDE_ENABLED, GetPipe, GlossaryMigrationFormat, GuideOverlayComponent, HE_API_BASE_URL, HE_CALCULATIONS_BASE_URL, HE_MAP_LOADED, HeAuthService, HeCommonService, HeEngineService, HeGlossaryService, HeMendeleyService, HeNodeCsvService, HeNodeService, HeNodeStoreService, HeSchemaService, HeSearchService, HeToastService, HorizontalBarChartComponent, HorizontalButtonsGroupComponent, ImpactAssessmentsGraphComponent, ImpactAssessmentsIndicatorBreakdownChartComponent, ImpactAssessmentsIndicatorsChartComponent, ImpactAssessmentsProductsComponent, IsArrayPipe, IsObjectPipe, IssueConfirmComponent, KeyToLabelPipe, Level, LineChartComponent, LinkKeyValueComponent, LogStatus, LongPressDirective, MAX_RESULTS, MapsDrawingComponent, MapsDrawingConfirmComponent, MaxPipe, MeanPipe, MedianPipe, MendeleySearchResult, MinPipe, MobileShellComponent, NavigationMenuComponent, NoExtPipe, NodeAggregatedComponent, NodeAggregatedInfoComponent, NodeAggregatedQualityScoreComponent, NodeCsvExportConfirmComponent, NodeCsvPreviewComponent, NodeCsvSelectHeadersComponent, NodeIconComponent, NodeJLogModelsComponent, NodeJsonldComponent, NodeJsonldSchemaComponent, NodeKeyState, NodeLinkComponent, NodeLogsFileComponent, NodeLogsModelsComponent, NodeLogsTimeComponent, NodeMissingLookupFactorsComponent, NodeQualityScore, NodeRecommendationsComponent, NodeSelectComponent, NodeValueDetailsComponent, PipelineStagesProgressComponent, PluralizePipe, PopoverComponent, PopoverConfirmComponent, PrecisionPipe, RelatedNodeResult, RemoveMarkdownPipe, RepeatPipe, Repository, ResizedDirective, ResizedEvent, ResponsiveService, SchemaInfoComponent, SchemaVersionLinkComponent, SearchExtendComponent, ShelfDialogComponent, ShellComponent, SiteNodesKeyGroup, SitesManagementChartComponent, SitesMapsComponent, SitesNodesComponent, SkeletonTextComponent, SocialTagsComponent, SortByPipe, SortSelectComponent, SumPipe, TagsInputDirective, Template, TermsPropertyContentComponent, TermsSubClassOfContentComponent, TermsUnitsDescriptionComponent, ThousandSuffixesPipe, ThousandsPipe, TimesPipe, ToastComponent, UncapitalizePipe, addPolygonToFeature, afterBarDrawPlugin, allCountriesQuery, allGroups, allOptions, availableProperties, axisHoverPlugin, backgroundHoverPlugin, baseApiUrl, baseUrl, bottom, buildSummary, bytesSize, calculateCycleDuration, calculateCycleDurationEnabled, calculateCycleStartDate, calculateCycleStartDateEnabled, capitalize, changelogUrl, clustererImage, code, colorToRgba, compoundToHtml, computeKeys, computeTerms, contactUsEmail, contactUsLink, convertToSvg, coordinatesToPoint, copyObject, countGroupVisibleNodes, countriesQuery, createMarker, cropsQuery, d3ellipse, d3wrap, dataPathLabel, dataPathToKey, dataVersionHeader, dataVersionHeaderKey, defaultFeature, defaultLabel, defaultSuggestionType, defaultTicksFont, definitionToSchemaType, distinctUntilChangedDeep, downloadFile, downloadPng, downloadSvg, ellipsis, engineGitBaseUrl, engineGitUrl, errorText, evaluateSuccess, exportAsSVG, exportFormats, externalLink, externalNodeLink, fillColor, fillStyle, filterBlankNode$1 as filterBlankNode, filterParams, findConfigModels, findMatchingModel, findModels, findNodeModel, findOrchestratorModel, findProperty, findPropertyById, flatFilterData, flatFilterNode, formatCustomErrorMessage, formatDate, formatError, formatPropertyError, formatter, getColor, getDatesBetween, gitBranch, gitHome, gitlabRawUrl, glossaryBaseUrl, glossaryLink, groupChanged, groupDataByCategory, groupJLogByField, groupJLogByTerm, groupLogsByTerm, groupNodesByTerm, groupdLogsByKey, grouppedKeys, grouppedValueKeys, groupsLogsByFields, guideModelUrl, guideNamespace, handleAPIError, handleGuideEvent, hasError, hasValidationError, hasWarning, hexToRgba, ignoreKeys$2 as ignoreKeys, increaseScaleLimits, initialFilterState, injectResizeEvent$, inputGroupsTermTypes, isAddPropertyEnabled, isChrome, isDateBetween, isEqual, isExternal, isKeyClosedVisible, isKeyHidden, isMaxStage, isMethodModelAllowed, isNonNodeModelKey, isSchemaIri, isScrolledBelow, isState, isTermTypeAllowed, isValidKey, jLogModelCount, keyToDataPath, levels, listColor, listColorContinuous, listColorWithAlpha, loadMapApi, locationQuery, logToCsv$2 as logToCsv, logValueArray, logsKey, lollipopChartPlugin, lookupUrl, mapFilterData, mapsUrl, markerIcon, markerPie, matchAggregatedQuery, matchAggregatedValidatedQuery, matchBoolPrefixQuery, matchCountry, matchExactQuery, matchGlobalRegion, matchId, matchNameNormalized, matchNestedKey, matchPhrasePrefixQuery, matchPhraseQuery, matchPrimaryProductQuery, matchQuery, matchRegex, matchRegion, matchTermType, matchType, maxAreaSize, measurementValue, mergeDataWithHeaders, methodTierOrder, migrationErrorMessage, migrationsUrl, modelCount, modelKeyParams, modelParams, models, multiMatchQuery, nestedProperty, nestingEnabled, nestingTypeEnabled, noValue, nodeAvailableProperties, nodeById, nodeColours$1 as nodeColours, nodeDataState, nodeDataStates, nodeDataVersion, nodeId, nodeIdWithoutDataVersion, nodeIds, nodeLink, nodeLinkEnabled, nodeLinkTypeEnabled, nodeLogsUrl, nodeQualityScoreColor, nodeQualityScoreLevel, nodeQualityScoreMaxDefault, nodeQualityScoreOrder, nodeRequestId, nodeSecondaryColours, nodeToAggregationFilename, nodeType, nodeTypeDataState, nodeTypeIcon, nodeTypeIconSchema, nodeUrl, nodeUrlParams, nodeVersion, nodeVersionKey, nodesByState, nodesByType, numberGte, optionsFromGroup, parentKey, parentProperty, parseColor, parseData, parseDataPath, parseLines, parseMessage, parseNewValue, pluralize, pointToCoordinates, polygonBounds, polygonToCoordinates, polygonToMap, polygonsFromFeature, populateWithTrackIdsFilterData, postGuideEvent, primaryProduct, productsQuery, propertyError, propertyId, recursiveProperties, refToSchemaType, refreshPropertyKeys, regionsQuery, registerChart, repeat, reportIssueLink, reportIssueUrl, safeJSONParse, safeJSONStringify, schemaBaseUrl, schemaDataBaseUrl, schemaLink, schemaRequiredProperties, schemaTypeToDefaultValue, scrollToEl, scrollTop, searchFilterData, searchableTypes, siblingProperty, simplifyContributions, singleProperty, siteTooBig, siteTypeToColor, siteTypeToIcon, sortProperties, sortedDates, strokeColor, strokeStyle, subValueKeys, suggestMatchQuery, suggestQuery, sumValues, takeAfterViewInit, termLocation, termLocationName, termProperties, termTypeLabel, toSnakeCase, toThousands, typeToNewProperty, typeaheadFocus, uncapitalize, uniqueDatesBetween, updateProperties, valueLink, valueToString, valueTypeToDefault, valueValue, waitFor, wildcardQuery };
17425
+ export { ARRAY_DELIMITER, ApplyPurePipe, BarChartComponent, BibliographiesSearchConfirmComponent, BlankNodeStateComponent, BlankNodeStateNoticeComponent, BlankNodeValueDeltaComponent, CapitalizePipe, ChartComponent, ChartConfigurationDirective, ChartExportButtonComponent, ChartTooltipComponent, ClickOutsideDirective, ClipboardComponent, CollapsibleBoxComponent, CollapsibleBoxStyle, ColorPalette, CompoundDirective, CompoundPipe, ContributionChartComponent, ControlValueAccessor, CycleNodesKeyGroup, CyclesCompletenessComponent, CyclesEmissionsCategoryService, CyclesEmissionsChartComponent, CyclesFunctionalUnitMeasureComponent, CyclesMetadataComponent, CyclesNodesComponent, CyclesNodesTimelineComponent, CyclesResultComponent, DataTableComponent, DefaultPipe, DeltaColour, DistributionChartComponent, DrawerContainerComponent, DurationPipe, EllipsisPipe, EngineModelsLinkComponent, EngineModelsLookupInfoComponent, EngineModelsStageComponent, EngineModelsStageDeepComponent, EngineModelsStageDeepService, EngineModelsVersionInfoComponent, EngineModelsVersionLinkComponent, EngineOrchestratorEditComponent, EngineRequirementsFormComponent, FileSizePipe, FileUploadErrorKeys, FilesDragDropDirective, FilesDropZoneComponent, FilesErrorSummaryComponent, FilesFormComponent, FilesFormEditableComponent, FilesUploadErrorsComponent, FilterAccordionComponent, FormulaBlockComponent, GUIDE_ENABLED, GetPipe, GlossaryMigrationFormat, GuideOverlayComponent, HE_API_BASE_URL, HE_CALCULATIONS_BASE_URL, HE_MAP_LOADED, HeAuthService, HeCommonService, HeEngineService, HeGlossaryService, HeMendeleyService, HeNodeCsvService, HeNodeService, HeNodeStoreService, HeSchemaService, HeSearchService, HeToastService, HorizontalBarChartComponent, HorizontalButtonsGroupComponent, ImpactAssessmentsGraphComponent, ImpactAssessmentsIndicatorBreakdownChartComponent, ImpactAssessmentsIndicatorsChartComponent, ImpactAssessmentsProductsComponent, IsArrayPipe, IsObjectPipe, IssueConfirmComponent, KeyToLabelPipe, Level, LineChartComponent, LinkKeyValueComponent, LogStatus, LongPressDirective, MAX_RESULTS, MapsDrawingComponent, MapsDrawingConfirmComponent, MaxPipe, MeanPipe, MedianPipe, MendeleySearchResult, MinPipe, MobileShellComponent, NavigationMenuComponent, NoExtPipe, NodeAggregatedComponent, NodeAggregatedFormulasComponent, NodeAggregatedInfoComponent, NodeAggregatedQualityScoreComponent, NodeAggregationLogsComponent, NodeCsvExportConfirmComponent, NodeCsvPreviewComponent, NodeCsvSelectHeadersComponent, NodeIconComponent, NodeJLogModelsComponent, NodeJsonldComponent, NodeJsonldSchemaComponent, NodeKeyState, NodeLinkComponent, NodeLogsFileComponent, NodeLogsModelsComponent, NodeLogsTimeComponent, NodeMissingLookupFactorsComponent, NodeQualityScore, NodeRecommendationsComponent, NodeSelectComponent, NodeValueDetailsComponent, PipelineStagesProgressComponent, PluralizePipe, PopoverComponent, PopoverConfirmComponent, PrecisionPipe, RelatedNodeResult, RemoveMarkdownPipe, RepeatPipe, Repository, ResizedDirective, ResizedEvent, ResponsiveService, SchemaInfoComponent, SchemaVersionLinkComponent, SearchExtendComponent, ShelfDialogComponent, ShellComponent, SiteNodesKeyGroup, SitesManagementChartComponent, SitesMapsComponent, SitesNodesComponent, SkeletonTextComponent, SocialTagsComponent, SortByPipe, SortSelectComponent, SumPipe, TagsInputDirective, Template, TermsPropertyContentComponent, TermsSubClassOfContentComponent, TermsUnitsDescriptionComponent, ThousandSuffixesPipe, ThousandsPipe, TimesPipe, ToastComponent, UncapitalizePipe, addPolygonToFeature, afterBarDrawPlugin, allCountriesQuery, allGroups, allOptions, availableProperties, axisHoverPlugin, backgroundHoverPlugin, baseApiUrl, baseUrl, bottom, buildSummary, bytesSize, calculateCycleDuration, calculateCycleDurationEnabled, calculateCycleStartDate, calculateCycleStartDateEnabled, capitalize, changelogUrl, clustererImage, code, colorToRgba, compoundToHtml, computeKeys, computeTerms, contactUsEmail, contactUsLink, convertToSvg, coordinatesToPoint, copyObject, countGroupVisibleNodes, countriesQuery, createMarker, cropsQuery, d3ellipse, d3wrap, dataPathLabel, dataPathToKey, dataVersionHeader, dataVersionHeaderKey, defaultFeature, defaultLabel, defaultSuggestionType, defaultTicksFont, definitionToSchemaType, distinctUntilChangedDeep, downloadFile, downloadPng, downloadSvg, ellipsis, engineGitBaseUrl, engineGitUrl, errorText, evaluateSuccess, exportAsSVG, exportFormats, externalLink, externalNodeLink, fillColor, fillStyle, filterBlankNode$1 as filterBlankNode, filterParams, findConfigModels, findMatchingModel, findModels, findNodeModel, findOrchestratorModel, findProperty, findPropertyById, flatFilterData, flatFilterNode, formatCustomErrorMessage, formatDate, formatError, formatPropertyError, formatter, getColor, getDatesBetween, gitBranch, gitHome, gitlabRawUrl, glossaryBaseUrl, glossaryLink, groupBlankNodesByTermIdentity, groupChanged, groupDataByCategory, groupJLogByField, groupJLogByTerm, groupLogsByTerm, groupNodesByTerm, groupdLogsByKey, grouppedKeys, grouppedValueKeys, groupsLogsByFields, guideModelUrl, guideNamespace, handleAPIError, handleGuideEvent, hasError, hasValidationError, hasWarning, hexToRgba, ignoreKeys$2 as ignoreKeys, increaseScaleLimits, initialFilterState, injectResizeEvent$, inputGroupsTermTypes, isAddPropertyEnabled, isChrome, isDateBetween, isEqual, isExternal, isKeyClosedVisible, isKeyHidden, isMaxStage, isMethodModelAllowed, isNonNodeModelKey, isSchemaIri, isScrolledBelow, isState, isTermTypeAllowed, isValidKey, jLogModelCount, keyToDataPath, levels, listColor, listColorContinuous, listColorWithAlpha, loadMapApi, locationQuery, logToCsv$2 as logToCsv, logValueArray, logsKey, lollipopChartPlugin, lookupUrl, mapFilterData, mapsUrl, markerIcon, markerPie, matchAggregatedQuery, matchAggregatedValidatedQuery, matchBoolPrefixQuery, matchCountry, matchExactQuery, matchGlobalRegion, matchId, matchNameNormalized, matchNestedKey, matchPhrasePrefixQuery, matchPhraseQuery, matchPrimaryProductQuery, matchQuery, matchRegex, matchRegion, matchTermType, matchType, maxAreaSize, measurementValue, mergeDataWithHeaders, methodTierOrder, migrationErrorMessage, migrationsUrl, modelCount, modelKeyParams, modelParams, models, multiMatchQuery, nestedProperty, nestingEnabled, nestingTypeEnabled, noValue, nodeAvailableProperties, nodeById, nodeColours$1 as nodeColours, nodeDataState, nodeDataStates, nodeDataVersion, nodeId, nodeIdWithoutDataVersion, nodeIds, nodeLink, nodeLinkEnabled, nodeLinkTypeEnabled, nodeLogsUrl, nodeQualityScoreColor, nodeQualityScoreLevel, nodeQualityScoreMaxDefault, nodeQualityScoreOrder, nodeRequestId, nodeSecondaryColours, nodeToAggregationFilename, nodeType, nodeTypeDataState, nodeTypeIcon, nodeTypeIconSchema, nodeUrl, nodeUrlParams, nodeVersion, nodeVersionKey, nodesByState, nodesByType, numberGte, optionsFromGroup, parentKey, parentProperty, parseColor, parseData, parseDataPath, parseLines, parseMessage, parseNewValue, pluralize, pointToCoordinates, polygonBounds, polygonToCoordinates, polygonToMap, polygonsFromFeature, populateWithTrackIdsFilterData, postGuideEvent, primaryProduct, productsQuery, propertyError, propertyId, recursiveProperties, refToSchemaType, refreshPropertyKeys, regionsQuery, registerChart, repeat, reportIssueLink, reportIssueUrl, safeJSONParse, safeJSONStringify, schemaBaseUrl, schemaDataBaseUrl, schemaLink, schemaRequiredProperties, schemaTypeToDefaultValue, scrollToEl, scrollTop, searchFilterData, searchableTypes, siblingProperty, simplifyContributions, singleProperty, siteTooBig, siteTypeToColor, siteTypeToIcon, sortProperties, sortedDates, strokeColor, strokeStyle, subValueKeys, suggestMatchQuery, suggestQuery, sumValues, takeAfterViewInit, termLocation, termLocationName, termProperties, termTypeLabel, toSnakeCase, toTextParts, toThousands, typeToNewProperty, typeaheadFocus, uncapitalize, uniqueDatesBetween, updateProperties, valueLink, valueToString, valueTypeToDefault, valueValue, waitFor, wildcardQuery };
16683
17426
  //# sourceMappingURL=hestia-earth-ui-components.mjs.map