@hestia-earth/engine-models 0.82.0 → 0.82.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/cjs/formulas.d.ts +16 -3
  2. package/cjs/formulas.js +58 -29
  3. package/cjs/version.d.ts +1 -1
  4. package/cjs/version.js +1 -1
  5. package/esm/formulas.d.ts +16 -3
  6. package/esm/formulas.js +52 -26
  7. package/esm/version.d.ts +1 -1
  8. package/esm/version.js +1 -1
  9. package/formulas/README.md +38 -9
  10. package/formulas/index.json +3349 -393
  11. package/model-links.json +30 -0
  12. package/package.json +1 -1
  13. package/formulas/deRuijterEtAl2010/nh3ToAirCropResidueDecomposition.json +0 -30
  14. package/formulas/emepEea2019/co2ToAirFuelCombustion.json +0 -23
  15. package/formulas/emepEea2019/nh3ToAirExcreta.json +0 -27
  16. package/formulas/emepEea2019/pm10ToAirAnimalHousing.json +0 -28
  17. package/formulas/emepEea2019/pm25ToAirAnimalHousing.json +0 -28
  18. package/formulas/emepEea2019/tspToAirAnimalHousing.json +0 -28
  19. package/formulas/hestia/feedConversionRatio/feedConversionRatioCarbon.json +0 -23
  20. package/formulas/ipcc2019/belowGroundCropResidue.json +0 -31
  21. package/formulas/ipcc2019/ch4ToAirAquacultureSystems.json +0 -29
  22. package/formulas/ipcc2019/ch4ToAirEntericFermentation.json +0 -29
  23. package/formulas/ipcc2019/ch4ToAirExcreta.json +0 -36
  24. package/formulas/ipcc2019/ch4ToAirFloodedRice.json +0 -31
  25. package/formulas/ipcc2019/ch4ToAirOrganicSoilCultivation.json +0 -40
  26. package/formulas/ipcc2019/co2ToAirUreaHydrolysis.json +0 -27
  27. package/formulas/ipcc2019/emissionsToAirOrganicSoilBurning.json +0 -49
  28. package/formulas/ipcc2019/n2OToAirAquacultureSystemsIndirect.json +0 -31
  29. package/formulas/ipcc2019/n2OToAirCropResidueBurningDirect.json +0 -25
  30. package/formulas/ipcc2019/n2OToAirCropResidueDecompositionDirect.json +0 -28
  31. package/formulas/ipcc2019/n2OToAirCropResidueDecompositionIndirect.json +0 -35
  32. package/formulas/ipcc2019/n2OToAirExcretaDirect.json +0 -25
  33. package/formulas/ipcc2019/n2OToAirExcretaIndirect.json +0 -39
  34. package/formulas/ipcc2019/n2OToAirFuelCombustionIndirect.json +0 -31
  35. package/formulas/ipcc2019/n2OToAirInorganicFertiliserDirect.json +0 -28
  36. package/formulas/ipcc2019/n2OToAirInorganicFertiliserIndirect.json +0 -39
  37. package/formulas/ipcc2019/n2OToAirNaturalVegetationBurningIndirect.json +0 -31
  38. package/formulas/ipcc2019/n2OToAirOrganicFertiliserDirect.json +0 -28
  39. package/formulas/ipcc2019/n2OToAirOrganicFertiliserIndirect.json +0 -39
  40. package/formulas/ipcc2019/n2OToAirOrganicSoilBurningIndirect.json +0 -31
  41. package/formulas/ipcc2019/n2OToAirOrganicSoilCultivationIndirect.json +0 -31
  42. package/formulas/ipcc2019/nh3ToAirInorganicFertiliser.json +0 -31
  43. package/formulas/ipcc2019/nh3ToAirOrganicFertiliser.json +0 -31
  44. package/formulas/ipcc2019/no3ToGroundwaterCropResidueDecomposition.json +0 -28
  45. package/formulas/ipcc2019/no3ToGroundwaterExcreta.json +0 -28
  46. package/formulas/ipcc2019/no3ToGroundwaterInorganicFertiliser.json +0 -28
  47. package/formulas/ipcc2019/no3ToGroundwaterOrganicFertiliser.json +0 -28
  48. package/formulas/ipcc2019/nonCo2EmissionsToAirNaturalVegetationBurning.json +0 -13
  49. package/formulas/ipcc2019/noxToAirInorganicFertiliser.json +0 -31
  50. package/formulas/ipcc2019/noxToAirOrganicFertiliser.json +0 -31
  51. package/formulas/ipcc2019/pastureGrass.json +0 -556
  52. package/formulas/pooreNemecek2018/ch4ToAirAquacultureSystems.json +0 -88
  53. package/formulas/pooreNemecek2018/n2OToAirAquacultureSystemsDirect.json +0 -32
  54. package/formulas/pooreNemecek2018/n2ToAirAquacultureSystems.json +0 -32
  55. package/formulas/pooreNemecek2018/nh3ToAirAquacultureSystems.json +0 -40
  56. package/formulas/pooreNemecek2018/no3ToGroundwaterCropResidueDecomposition.json +0 -68
  57. package/formulas/pooreNemecek2018/no3ToGroundwaterExcreta.json +0 -68
  58. package/formulas/pooreNemecek2018/no3ToGroundwaterInorganicFertiliser.json +0 -68
  59. package/formulas/pooreNemecek2018/no3ToGroundwaterOrganicFertiliser.json +0 -68
  60. package/formulas/pooreNemecek2018/noxToAirAquacultureSystems.json +0 -32
  61. package/formulas/schererPfister2015/nErosionSoilFlux.json +0 -34
  62. package/formulas/schererPfister2015/pErosionSoilFlux.json +0 -109
  63. package/formulas/stehfestBouwman2006/n2OToAirCropResidueDecompositionDirect.json +0 -37
  64. package/formulas/stehfestBouwman2006/n2OToAirExcretaDirect.json +0 -37
  65. package/formulas/stehfestBouwman2006/n2OToAirInorganicFertiliserDirect.json +0 -37
  66. package/formulas/stehfestBouwman2006/n2OToAirOrganicFertiliserDirect.json +0 -37
  67. package/formulas/stehfestBouwman2006/noxToAirCropResidueDecomposition.json +0 -36
  68. package/formulas/stehfestBouwman2006/noxToAirExcreta.json +0 -36
  69. package/formulas/stehfestBouwman2006/noxToAirInorganicFertiliser.json +0 -36
  70. package/formulas/stehfestBouwman2006/noxToAirOrganicFertiliser.json +0 -36
package/cjs/formulas.d.ts CHANGED
@@ -19,11 +19,24 @@ export interface IFormulaBinding {
19
19
  */
20
20
  key?: string;
21
21
  /**
22
- * For a per-row value inside a packed `log_as_table` string: the column to
23
- * read from each row. When set, the symbol sits under a `\sum` and is expanded
24
- * once per row (e.g. `\sum_i M_i \times EF_i` → `(32 × 0.166) + (13 × 0.495)`).
22
+ * A value inside a packed `log_as_table` string: the column to read.
23
+ * - with no `match`, the symbol sits under a `\sum` and is expanded once per
24
+ * row (e.g. `\sum_i M_i \times EF_i` → `(32 × 0.166) + (13 × 0.495)`);
25
+ * - with `match`, it resolves to a single row (row selection, see `match`).
25
26
  */
26
27
  column?: string;
28
+ /**
29
+ * Row selection: pick the one row of the `key` table whose id (first column)
30
+ * starts with this value, then read `column` from it. Resolves to a single
31
+ * value, e.g. `NH_3\text{-}N` → the `emission-value` of the row `nh3…`.
32
+ */
33
+ match?: string;
34
+ /**
35
+ * A fixed constant (not logged): the symbol always substitutes to this literal
36
+ * (e.g. `ER` → `2`) and is excluded from coverage. Empty string marks a
37
+ * constant with no display value.
38
+ */
39
+ constant?: string;
27
40
  }
28
41
  /**
29
42
  * A KaTeX formula extracted from a model's documentation, with the bindings
package/cjs/formulas.js CHANGED
@@ -92,8 +92,9 @@ var formatValue = function (value) {
92
92
  return round4(value);
93
93
  return formatNumericString(String(value));
94
94
  };
95
- // `\sum` with an optional index subscript (`_i`, `_{i}`, ...) and trailing space.
96
- var SUM_RE = /\\sum(?:_\{[^}]*\}|_[^\s{])?\s*/;
95
+ // `\sum` with an optional index subscript (`_i`, `_{s=1}`, ...) and upper bound
96
+ // superscript (`^S`, `^{S}`), and trailing space.
97
+ var SUM_RE = /\\sum(?:_\{[^}]*\}|_[^\s{])?(?:\^\{[^}]*\}|\^[^\s{])?\s*/;
97
98
  // Parse a packed `log_as_table` string into rows: rows split on `;`, columns on
98
99
  // `_`, each column a `key:value` pair (values never contain `_` or `:`).
99
100
  var parseTable = function (packed) {
@@ -122,35 +123,63 @@ var parseTable = function (packed) {
122
123
  * (or an array of row objects). Without the table it stays symbolic.
123
124
  */
124
125
  var renderFormula = function (formula, values) {
125
- var _a;
126
- var columnBindings = formula.bindings.filter(function (b) { return b.key && b.column; });
127
- var scalarBindings = formula.bindings.filter(function (b) { return b.key && !b.column; });
128
- var sumMatch = columnBindings.length ? SUM_RE.exec(formula.formula) : null;
129
- var tableKey = (_a = columnBindings[0]) === null || _a === void 0 ? void 0 : _a.key;
130
- var raw = values[tableKey];
131
- var rows = !sumMatch
132
- ? null
133
- : Array.isArray(raw)
134
- ? raw
135
- : typeof raw === 'string'
136
- ? parseTable(raw)
137
- : null;
126
+ // sum: a per-row column under a \sum. point: a single value — a scalar, a row
127
+ // selection (`match` + `column`), or a fixed constant.
128
+ var sumBindings = formula.bindings.filter(function (b) { return b.key && b.column && !b.match; });
129
+ var pointBindings = formula.bindings.filter(function (b) { return (b.key && !(b.column && !b.match)) || b.constant; });
130
+ var toRows = function (source) {
131
+ return Array.isArray(source)
132
+ ? source
133
+ : typeof source === 'string'
134
+ ? parseTable(source)
135
+ : [];
136
+ };
137
+ var sumMatch = sumBindings.length ? SUM_RE.exec(formula.formula) : null;
138
+ // The summand may draw from several parallel tables (e.g. one for values, one
139
+ // for factors); parse each once and align them by row index.
140
+ var tableRows = {};
141
+ if (sumMatch) {
142
+ sumBindings.forEach(function (b) {
143
+ var key = b.key;
144
+ if (!(key in tableRows))
145
+ tableRows[key] = toRows(values[key]);
146
+ });
147
+ }
148
+ var rowCount = sumMatch
149
+ ? Math.max.apply(Math, __spreadArray([0], __read(sumBindings.map(function (b) { return tableRows[b.key].length; })), false)) : 0;
150
+ // Resolve a point binding to a single value: a fixed constant, a scalar lookup,
151
+ // or the selected row's column for a row selection (`match` = id prefix).
152
+ var pointValue = function (b) {
153
+ if (b.constant !== undefined)
154
+ return b.constant || undefined;
155
+ if (!b.match)
156
+ return values[b.key];
157
+ var row = toRows(values[b.key]).find(function (r) { var _a; return String((_a = Object.values(r)[0]) !== null && _a !== void 0 ? _a : '').startsWith(b.match); });
158
+ return row ? row[b.column] : undefined;
159
+ };
160
+ // How a binding is referenced in the `data-key` attribute.
161
+ var ref = function (b) {
162
+ return b.constant !== undefined
163
+ ? 'const'
164
+ : "".concat(b.key).concat(b.match ? '@' + b.match : '').concat(b.column ? ':' + b.column : '');
165
+ };
138
166
  // Expand the `\sum` inline, wrapping each row value with `wrapRow`. Returns the
139
167
  // original formula when there is no table to expand over.
140
168
  var expand = function (wrapRow) {
141
- if (!sumMatch || !rows || !rows.length)
169
+ if (!sumMatch || !rowCount)
142
170
  return formula.formula;
143
171
  var before = formula.formula.slice(0, sumMatch.index);
144
172
  var summand = formula.formula.slice(sumMatch.index + sumMatch[0].length);
145
- var symbols = columnBindings.map(function (b) { return b.symbol; });
146
- var terms = rows.map(function (row) {
173
+ var symbols = sumBindings.map(function (b) { return b.symbol; });
174
+ var terms = Array.from({ length: rowCount }, function (_, i) {
147
175
  return '(' +
148
176
  splitOnSymbols(summand, symbols)
149
177
  .map(function (seg) {
150
178
  if (seg.text !== undefined)
151
179
  return seg.text;
152
- var b = columnBindings.find(function (x) { return x.symbol === seg.symbol; });
153
- var value = formatValue(row[b.column]);
180
+ var b = sumBindings.find(function (x) { return x.symbol === seg.symbol; });
181
+ var row = tableRows[b.key][i];
182
+ var value = row ? formatValue(row[b.column]) : null;
154
183
  return value === null ? seg.symbol : wrapRow(b, value);
155
184
  })
156
185
  .join('') +
@@ -158,22 +187,22 @@ var renderFormula = function (formula, values) {
158
187
  });
159
188
  return before + terms.join(' + ');
160
189
  };
161
- // Substitute the scalar (non-column) symbols over a working string.
162
- var substituteScalars = function (working, wrap) {
163
- return splitOnSymbols(working, scalarBindings.map(function (b) { return b.symbol; }))
190
+ // Substitute the point (single-value) symbols over a working string.
191
+ var substitutePoints = function (working, wrap) {
192
+ return splitOnSymbols(working, pointBindings.map(function (b) { return b.symbol; }))
164
193
  .map(function (seg) {
165
194
  if (seg.text !== undefined)
166
195
  return seg.text;
167
- var b = scalarBindings.find(function (x) { return x.symbol === seg.symbol; });
168
- return wrap(b, seg.symbol, formatValue(values[b.key]));
196
+ var b = pointBindings.find(function (x) { return x.symbol === seg.symbol; });
197
+ return wrap(b, seg.symbol, formatValue(pointValue(b)));
169
198
  })
170
199
  .join('');
171
200
  };
172
- var substituted = substituteScalars(expand(function (b, v) { return "\\htmlData{key=".concat(b.key, ":").concat(b.column, "}{").concat(v, "}"); }), function (b, symbol, value) {
173
- return value === null ? symbol : "\\htmlData{key=".concat(b.key, "}{").concat(value, "}");
201
+ var substituted = substitutePoints(expand(function (b, v) { return "\\htmlData{key=".concat(ref(b), "}{").concat(v, "}"); }), function (b, symbol, value) {
202
+ return value === null ? symbol : "\\htmlData{key=".concat(ref(b), "}{").concat(value, "}");
174
203
  });
175
- var annotated = substituteScalars(expand(function (b, v) { return "\\htmlData{key=".concat(b.key, ":").concat(b.column, ", value=").concat(v, "}{").concat(v, "}"); }), function (b, symbol, value) {
176
- return "\\htmlData{key=".concat(b.key, ", value=").concat(value === null ? 'na' : value, "}{").concat(symbol, "}");
204
+ var annotated = substitutePoints(expand(function (b, v) { return "\\htmlData{key=".concat(ref(b), ", value=").concat(v, "}{").concat(v, "}"); }), function (b, symbol, value) {
205
+ return "\\htmlData{key=".concat(ref(b), ", value=").concat(value === null ? 'na' : value, "}{").concat(symbol, "}");
177
206
  });
178
207
  return { symbolic: formula.formula, substituted: substituted, annotated: annotated };
179
208
  };
package/cjs/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const ENGINE_VERSION = "0.82.0";
1
+ export declare const ENGINE_VERSION = "0.82.2";
package/cjs/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ENGINE_VERSION = void 0;
4
- exports.ENGINE_VERSION = '0.82.0';
4
+ exports.ENGINE_VERSION = '0.82.2';
package/esm/formulas.d.ts CHANGED
@@ -19,11 +19,24 @@ export interface IFormulaBinding {
19
19
  */
20
20
  key?: string;
21
21
  /**
22
- * For a per-row value inside a packed `log_as_table` string: the column to
23
- * read from each row. When set, the symbol sits under a `\sum` and is expanded
24
- * once per row (e.g. `\sum_i M_i \times EF_i` → `(32 × 0.166) + (13 × 0.495)`).
22
+ * A value inside a packed `log_as_table` string: the column to read.
23
+ * - with no `match`, the symbol sits under a `\sum` and is expanded once per
24
+ * row (e.g. `\sum_i M_i \times EF_i` → `(32 × 0.166) + (13 × 0.495)`);
25
+ * - with `match`, it resolves to a single row (row selection, see `match`).
25
26
  */
26
27
  column?: string;
28
+ /**
29
+ * Row selection: pick the one row of the `key` table whose id (first column)
30
+ * starts with this value, then read `column` from it. Resolves to a single
31
+ * value, e.g. `NH_3\text{-}N` → the `emission-value` of the row `nh3…`.
32
+ */
33
+ match?: string;
34
+ /**
35
+ * A fixed constant (not logged): the symbol always substitutes to this literal
36
+ * (e.g. `ER` → `2`) and is excluded from coverage. Empty string marks a
37
+ * constant with no display value.
38
+ */
39
+ constant?: string;
27
40
  }
28
41
  /**
29
42
  * A KaTeX formula extracted from a model's documentation, with the bindings
package/esm/formulas.js CHANGED
@@ -55,8 +55,9 @@ const formatValue = (value) => {
55
55
  return round4(value);
56
56
  return formatNumericString(String(value));
57
57
  };
58
- // `\sum` with an optional index subscript (`_i`, `_{i}`, ...) and trailing space.
59
- const SUM_RE = /\\sum(?:_\{[^}]*\}|_[^\s{])?\s*/;
58
+ // `\sum` with an optional index subscript (`_i`, `_{s=1}`, ...) and upper bound
59
+ // superscript (`^S`, `^{S}`), and trailing space.
60
+ const SUM_RE = /\\sum(?:_\{[^}]*\}|_[^\s{])?(?:\^\{[^}]*\}|\^[^\s{])?\s*/;
60
61
  // Parse a packed `log_as_table` string into rows: rows split on `;`, columns on
61
62
  // `_`, each column a `key:value` pair (values never contain `_` or `:`).
62
63
  const parseTable = (packed) => packed
@@ -81,50 +82,75 @@ const parseTable = (packed) => packed
81
82
  * (or an array of row objects). Without the table it stays symbolic.
82
83
  */
83
84
  export const renderFormula = (formula, values) => {
84
- var _a;
85
- const columnBindings = formula.bindings.filter((b) => b.key && b.column);
86
- const scalarBindings = formula.bindings.filter((b) => b.key && !b.column);
87
- const sumMatch = columnBindings.length ? SUM_RE.exec(formula.formula) : null;
88
- const tableKey = (_a = columnBindings[0]) === null || _a === void 0 ? void 0 : _a.key;
89
- const raw = values[tableKey];
90
- const rows = !sumMatch
91
- ? null
92
- : Array.isArray(raw)
93
- ? raw
94
- : typeof raw === 'string'
95
- ? parseTable(raw)
96
- : null;
85
+ // sum: a per-row column under a \sum. point: a single value — a scalar, a row
86
+ // selection (`match` + `column`), or a fixed constant.
87
+ const sumBindings = formula.bindings.filter((b) => b.key && b.column && !b.match);
88
+ const pointBindings = formula.bindings.filter((b) => (b.key && !(b.column && !b.match)) || b.constant);
89
+ const toRows = (source) => Array.isArray(source)
90
+ ? source
91
+ : typeof source === 'string'
92
+ ? parseTable(source)
93
+ : [];
94
+ const sumMatch = sumBindings.length ? SUM_RE.exec(formula.formula) : null;
95
+ // The summand may draw from several parallel tables (e.g. one for values, one
96
+ // for factors); parse each once and align them by row index.
97
+ const tableRows = {};
98
+ if (sumMatch) {
99
+ sumBindings.forEach((b) => {
100
+ const key = b.key;
101
+ if (!(key in tableRows))
102
+ tableRows[key] = toRows(values[key]);
103
+ });
104
+ }
105
+ const rowCount = sumMatch
106
+ ? Math.max(0, ...sumBindings.map((b) => tableRows[b.key].length))
107
+ : 0;
108
+ // Resolve a point binding to a single value: a fixed constant, a scalar lookup,
109
+ // or the selected row's column for a row selection (`match` = id prefix).
110
+ const pointValue = (b) => {
111
+ if (b.constant !== undefined)
112
+ return b.constant || undefined;
113
+ if (!b.match)
114
+ return values[b.key];
115
+ const row = toRows(values[b.key]).find((r) => { var _a; return String((_a = Object.values(r)[0]) !== null && _a !== void 0 ? _a : '').startsWith(b.match); });
116
+ return row ? row[b.column] : undefined;
117
+ };
118
+ // How a binding is referenced in the `data-key` attribute.
119
+ const ref = (b) => b.constant !== undefined
120
+ ? 'const'
121
+ : `${b.key}${b.match ? '@' + b.match : ''}${b.column ? ':' + b.column : ''}`;
97
122
  // Expand the `\sum` inline, wrapping each row value with `wrapRow`. Returns the
98
123
  // original formula when there is no table to expand over.
99
124
  const expand = (wrapRow) => {
100
- if (!sumMatch || !rows || !rows.length)
125
+ if (!sumMatch || !rowCount)
101
126
  return formula.formula;
102
127
  const before = formula.formula.slice(0, sumMatch.index);
103
128
  const summand = formula.formula.slice(sumMatch.index + sumMatch[0].length);
104
- const symbols = columnBindings.map((b) => b.symbol);
105
- const terms = rows.map((row) => '(' +
129
+ const symbols = sumBindings.map((b) => b.symbol);
130
+ const terms = Array.from({ length: rowCount }, (_, i) => '(' +
106
131
  splitOnSymbols(summand, symbols)
107
132
  .map((seg) => {
108
133
  if (seg.text !== undefined)
109
134
  return seg.text;
110
- const b = columnBindings.find((x) => x.symbol === seg.symbol);
111
- const value = formatValue(row[b.column]);
135
+ const b = sumBindings.find((x) => x.symbol === seg.symbol);
136
+ const row = tableRows[b.key][i];
137
+ const value = row ? formatValue(row[b.column]) : null;
112
138
  return value === null ? seg.symbol : wrapRow(b, value);
113
139
  })
114
140
  .join('') +
115
141
  ')');
116
142
  return before + terms.join(' + ');
117
143
  };
118
- // Substitute the scalar (non-column) symbols over a working string.
119
- const substituteScalars = (working, wrap) => splitOnSymbols(working, scalarBindings.map((b) => b.symbol))
144
+ // Substitute the point (single-value) symbols over a working string.
145
+ const substitutePoints = (working, wrap) => splitOnSymbols(working, pointBindings.map((b) => b.symbol))
120
146
  .map((seg) => {
121
147
  if (seg.text !== undefined)
122
148
  return seg.text;
123
- const b = scalarBindings.find((x) => x.symbol === seg.symbol);
124
- return wrap(b, seg.symbol, formatValue(values[b.key]));
149
+ const b = pointBindings.find((x) => x.symbol === seg.symbol);
150
+ return wrap(b, seg.symbol, formatValue(pointValue(b)));
125
151
  })
126
152
  .join('');
127
- const substituted = substituteScalars(expand((b, v) => `\\htmlData{key=${b.key}:${b.column}}{${v}}`), (b, symbol, value) => value === null ? symbol : `\\htmlData{key=${b.key}}{${value}}`);
128
- const annotated = substituteScalars(expand((b, v) => `\\htmlData{key=${b.key}:${b.column}, value=${v}}{${v}}`), (b, symbol, value) => `\\htmlData{key=${b.key}, value=${value === null ? 'na' : value}}{${symbol}}`);
153
+ const substituted = substitutePoints(expand((b, v) => `\\htmlData{key=${ref(b)}}{${v}}`), (b, symbol, value) => value === null ? symbol : `\\htmlData{key=${ref(b)}}{${value}}`);
154
+ const annotated = substitutePoints(expand((b, v) => `\\htmlData{key=${ref(b)}, value=${v}}{${v}}`), (b, symbol, value) => `\\htmlData{key=${ref(b)}, value=${value === null ? 'na' : value}}{${symbol}}`);
129
155
  return { symbolic: formula.formula, substituted, annotated };
130
156
  };
package/esm/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const ENGINE_VERSION = "0.82.0";
1
+ export declare const ENGINE_VERSION = "0.82.2";
package/esm/version.js CHANGED
@@ -1 +1 @@
1
- export const ENGINE_VERSION = '0.82.0';
1
+ export const ENGINE_VERSION = '0.82.2';
@@ -6,16 +6,15 @@ to a value logged at runtime (the "jlog"). With these you can render a model's
6
6
  formula and *substitute* its symbols with the actual values from a given
7
7
  execution — so a user can see exactly where a result came from.
8
8
 
9
- The files here are generated by `scripts/generate-formulas.js` (run as part of
10
- `npm run build:models`). Do not edit them by hand — edit the model's `.md`
9
+ `index.json` is generated by `scripts/generate-formulas.js` (run as part of
10
+ `npm run build:models`). Do not edit it by hand — edit the model's `.md`
11
11
  documentation instead (see [Authoring bindings](#authoring-bindings)).
12
12
 
13
13
  ## Contents
14
14
 
15
- | File | Description |
16
- | --- | --- |
17
- | `<model>/<term>.json` | One file per documentation page that contains at least one formula. |
18
- | `index.json` | Aggregate of every formula, keyed by `docPath`. This is what the package imports. |
15
+ `index.json` holds every formula, keyed by the same `docPath` used in
16
+ `model-links.json`: `{ [docPath]: IFormula[] }`. It is the single file the
17
+ package imports.
19
18
 
20
19
  ## Data shape
21
20
 
@@ -24,7 +23,9 @@ interface IFormulaBinding {
24
23
  symbol: string; // the KaTeX symbol as written, e.g. "E_{CH4}", "GE"
25
24
  description?: string; // the "Where:" list description text
26
25
  key?: string; // the jlog field this symbol resolves to (absent for constants)
27
- column?: string; // for a \sum: the column to read per row of a packed table
26
+ column?: string; // a column of a packed table (per-row under a \sum, or with `match`)
27
+ match?: string; // row selection: the row whose id starts with this, read `column`
28
+ constant?: string; // a fixed constant (not logged), substituted as-is
28
29
  }
29
30
 
30
31
  interface IFormula {
@@ -162,13 +163,26 @@ Where:
162
163
  - $E_{CH4}$ = Methane emissions (in kg $CH_4$) `[value]`
163
164
  - $GE$ = Gross Energy intake (in MJ). `[total_feed_in_MJ]`
164
165
  - $Y_m$ = Methane conversion factor. `[enteric_factor]`
165
- - $55.65$ = The energy content of methane. <!-- constant, no binding -->
166
+ - $55.65$ = The energy content of methane. `[const]`
166
167
  ```
167
168
 
168
169
  - Bind the result (left-hand side) to `` `[value]` ``.
169
170
  - Bind every other symbol to the jlog field it corresponds to — the `key` passed
170
171
  to `debugValues` / `logRequirements` in the model's `.py` (e.g. `enteric_factor`).
171
- - Leave constants unbound; they render as-is.
172
+ - **Every symbol in the `Where:` list must be annotated.** Tag display-only
173
+ constants (conversion factors, physical constants, e.g. `55.65`, `\frac{17}{14}`)
174
+ with `` `[const]` ``: they render as-is and are excluded from coverage. An
175
+ untagged, unbound symbol is treated as a *genuine missing binding*, not a
176
+ constant — this is what lets the coverage report tell the two apart.
177
+ - Tag a **named** constant with `` `[const:<value>]` `` (e.g. `ER` → `` `[const:2]` ``):
178
+ it substitutes to the literal value (rather than rendering as-is) without being
179
+ logged, and is excluded from coverage.
180
+ - Tag a **documentation-only** symbol with `` `[display]` `` when it genuinely
181
+ can't be substituted and is *not* a constant — e.g. a per-event Monte-Carlo term
182
+ that never reduces to a single logged value. It renders symbolically and is
183
+ excluded from coverage. **Do not** use `` `[const]` `` for a real variable to
184
+ reach coverage: `validate-formula-bindings.js` errors on an empty `` `[const]` ``
185
+ whose symbol isn't a numeric literal.
172
186
 
173
187
  For a `\sum` over inputs, bind each summand symbol to a **table column** with
174
188
  `` `[<table key>:<column>]` `` — the log key of the packed `log_as_table` value
@@ -184,6 +198,21 @@ Where:
184
198
  - $EF_i$ = product-specific CO<sub>2</sub> emission factor `[urea_values:factor]`
185
199
  ```
186
200
 
201
+ When a symbol is a single value living in a specific row of a packed table (not a
202
+ `\sum`), select the row with `` `[<table key>@<id prefix>:<column>]` `` — the row
203
+ whose id (first column) starts with `<id prefix>`, reading `<column>`:
204
+
205
+ ```markdown
206
+ $$N_2O_{indirect} = [(NH_3\text{-}N + NO_x\text{-}N) \times EF_4] \times \frac{44}{28}$$
207
+
208
+ Where:
209
+
210
+ - $N_2O_{indirect}$ = indirect N<sub>2</sub>O emission `[value]`
211
+ - $NH_3\text{-}N$ = nitrogen volatilised as ammonia (kg N) `[values@nh3:emission-value]`
212
+ - $NO_x\text{-}N$ = nitrogen volatilised as nitrogen oxides (kg N) `[values@nox:emission-value]`
213
+ - $EF_4$ = emission factor for deposition of volatilised N `[values@nh3:ef4-factor]`
214
+ ```
215
+
187
216
  `scripts/validate-documentation.js` validates these on every build: it errors if a
188
217
  bound symbol is not in the formula or a symbol is bound twice. It warns (without
189
218
  failing) when there is no `` `[value]` `` result, several results, or a key is not