@hestia-earth/data-validation 0.41.11 → 0.41.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -57,7 +57,7 @@ declare const urlConfig: (baseUrl?: string) => IUrlConfig;
57
57
  * primary entry point). They are kept structurally compatible on purpose.
58
58
  */
59
59
  type validationErrorLevel = 'error' | 'warning';
60
- type validationErrorParam = 'missingProperty' | 'type' | 'term' | 'termType' | 'termIds' | 'model' | 'product' | 'range' | 'node' | 'allowedValues' | 'allowedValue' | 'additionalProperty' | 'expected' | 'default' | 'current' | 'percentage' | 'keys' | 'threshold' | 'outliers' | 'country' | 'min' | 'max' | 'mu' | 'sd' | 'defaultSource' | 'source' | 'group' | 'message' | 'distance' | 'siteType' | 'products' | 'units' | 'duplicatedIndexes' | 'invalidCoordinates' | 'ids' | 'limit' | 'delta';
60
+ type validationErrorParam = 'missingProperty' | 'type' | 'term' | 'termType' | 'termIds' | 'model' | 'product' | 'range' | 'node' | 'allowedValues' | 'allowedValue' | 'additionalProperty' | 'expected' | 'default' | 'current' | 'percentage' | 'keys' | 'threshold' | 'outliers' | 'country' | 'min' | 'max' | 'mu' | 'sd' | 'defaultSource' | 'source' | 'group' | 'message' | 'distance' | 'siteType' | 'products' | 'units' | 'duplicatedIndexes' | 'invalidCoordinates' | 'ids' | 'limit' | 'delta' | 'newlyCovered' | 'recommended' | 'missing';
61
61
  type validationErrorKeyword = 'required' | 'type' | 'if' | 'then' | 'not';
62
62
  interface ICustomValidationRules {
63
63
  const?: any;
@@ -57,7 +57,7 @@ declare const urlConfig: (baseUrl?: string) => IUrlConfig;
57
57
  * primary entry point). They are kept structurally compatible on purpose.
58
58
  */
59
59
  type validationErrorLevel = 'error' | 'warning';
60
- type validationErrorParam = 'missingProperty' | 'type' | 'term' | 'termType' | 'termIds' | 'model' | 'product' | 'range' | 'node' | 'allowedValues' | 'allowedValue' | 'additionalProperty' | 'expected' | 'default' | 'current' | 'percentage' | 'keys' | 'threshold' | 'outliers' | 'country' | 'min' | 'max' | 'mu' | 'sd' | 'defaultSource' | 'source' | 'group' | 'message' | 'distance' | 'siteType' | 'products' | 'units' | 'duplicatedIndexes' | 'invalidCoordinates' | 'ids' | 'limit' | 'delta';
60
+ type validationErrorParam = 'missingProperty' | 'type' | 'term' | 'termType' | 'termIds' | 'model' | 'product' | 'range' | 'node' | 'allowedValues' | 'allowedValue' | 'additionalProperty' | 'expected' | 'default' | 'current' | 'percentage' | 'keys' | 'threshold' | 'outliers' | 'country' | 'min' | 'max' | 'mu' | 'sd' | 'defaultSource' | 'source' | 'group' | 'message' | 'distance' | 'siteType' | 'products' | 'units' | 'duplicatedIndexes' | 'invalidCoordinates' | 'ids' | 'limit' | 'delta' | 'newlyCovered' | 'recommended' | 'missing';
61
61
  type validationErrorKeyword = 'required' | 'type' | 'if' | 'then' | 'not';
62
62
  interface ICustomValidationRules {
63
63
  const?: any;
@@ -712,6 +712,11 @@ var validations = [
712
712
  level: "error",
713
713
  message: "must have a value or a distance if complete"
714
714
  },
715
+ {
716
+ function: "validate_coverage",
717
+ level: "warning",
718
+ message: "can not calculate all emissions"
719
+ },
715
720
  {
716
721
  function: "validate_has_termType",
717
722
  level: "warning",
@@ -1204,6 +1209,30 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1204
1209
  const listAsCode = (values) => (values != null ? values : []).map((value) => r.code(value)).join(", ");
1205
1210
  const joinWithAnd = (values) => values.length > 1 ? `${values.slice(0, -1).join(", ")} and ${values[values.length - 1]}` : values.join("");
1206
1211
  const listAsCodeWithAnd = (values) => joinWithAnd((values != null ? values : []).map((value) => r.code(value)));
1212
+ const joinWithOr = (values) => values.length > 1 ? `${values.slice(0, -1).join(", ")} or ${values[values.length - 1]}` : values.join("");
1213
+ const pluralizeCount = (count, word) => `${count} ${pluralize(word, count)}`;
1214
+ const termNames = (terms = [], max = 3) => [
1215
+ ...terms.slice(0, max).map(({ "@id": id, name }) => termLink({ id, name })),
1216
+ ...terms.length > max ? [`${terms.length - max} more`] : []
1217
+ ].reduce((text, name, index, names) => index === 0 ? name : `${text}; ${index === names.length - 1 ? "and " : ""}${name}`, "");
1218
+ const coverageItems = {
1219
+ properties: ({ expected, term, blankNodeType }) => `add ${listAsCodeWithAnd(expected)} to the ${code(term == null ? void 0 : term["@id"])} ${blankNodeType}`,
1220
+ cropResidue: () => `add the fate of the crop residue: a ${code(schema.TermTermType.cropResidueManagement)} Practice, or the crop residue Products`,
1221
+ completeness: ({ paths }) => `set ${listAsCodeWithAnd(paths)} to ${code(true)}, if all the ${joinWithAnd(
1222
+ paths.map((path) => path.split(".")[1])
1223
+ )} data is recorded`,
1224
+ pastureGrass: () => `add a ${code("pastureGrass")} Practice, with the type of grass as its key`,
1225
+ system: ({ expected }) => `add an animal production system Practice${(expected == null ? void 0 : expected.length) ? `: ${joinWithOr(expected.map(code))}` : ""}`,
1226
+ milkYield: ({ expected }) => `add a milk yield Practice to the Animals: ${joinWithOr(expected.map(code))}`,
1227
+ animals: () => "add the herd composition, as Animals",
1228
+ data: ({ path, expected }) => path === "or" ? `add one of ${joinWithOr(expected.map((fields) => listAsCodeWithAnd(fields)))}` : (expected == null ? void 0 : expected.length) && expected.join() !== path.split(".").pop() ? `add ${joinWithOr(expected.map(code))} to ${code(path)}` : `add ${code(path)}`
1229
+ };
1230
+ const coverageItem = (item) => {
1231
+ var _a, _b;
1232
+ return `\u2022 ${(_b = (_a = coverageItems[item.type]) == null ? void 0 : _a.call(coverageItems, item)) != null ? _b : code(item.path)}${item.nodeType && item.nodeType !== schema.SchemaType.Cycle ? ` (on the ${item.nodeType})` : ""}`;
1233
+ };
1234
+ const coverageRequirement = ({ path, expected, current, nodeType: nodeType2 }) => `${code(path)} is ${joinWithOr(expected.map(code))}${current ? ` (this ${nodeType2} has ${code(current)})` : ""}`;
1235
+ const coverageMissing = ({ requirements, terms }) => `\u2022 HESTIA only calculates them when ${joinWithAnd(requirements.map(coverageRequirement))}: ${termNames(terms)}.`;
1207
1236
  const listAsTermLinksWithAnd = (termIds) => joinWithAnd((termIds != null ? termIds : []).map((id) => termLink({ id })));
1208
1237
  const glossaryQueryLink = (query, type) => r.link(`${urls.glossaryBaseUrl()}?query=${encodeURIComponent(query)}&termType=${type}`, query);
1209
1238
  const joinValues = (values) => (values || []).filter(Boolean).map((value) => r.code(value)).join(r.inlineSeparator);
@@ -1258,6 +1287,15 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1258
1287
  const sentences = utils.unique(errors.map(({ params }) => params == null ? void 0 : params.source)).map((source) => distributionSourceMessage[source]).filter(Boolean);
1259
1288
  return sentences.length ? `${sentences.join(r.lineBreak)}${r.lineBreak}` : "";
1260
1289
  };
1290
+ const outlierSubject = ({ dataPath, params }, value, errorCount) => {
1291
+ var _a;
1292
+ const shownValue = errorCount === 1 ? ` ${code(value)}` : "";
1293
+ return dataPath.includes("product") ? `The product value${shownValue}` : ((_a = params == null ? void 0 : params.termIds) == null ? void 0 : _a.length) ? `The total of ${joinWithAnd(params.termIds.map((id) => termLink({ id })))}, ${code(value)},` : (params == null ? void 0 : params.group) ? `The total of all ${code(params.group)} inputs of this Cycle${shownValue}` : `The input value${shownValue}`;
1294
+ };
1295
+ const outlierGroupNote = (errors) => errors.some(({ dataPath, params }) => {
1296
+ var _a;
1297
+ return !dataPath.includes("product") && (((_a = params == null ? void 0 : params.termIds) == null ? void 0 : _a.length) || (params == null ? void 0 : params.group));
1298
+ }) ? `This total adds up every input checked together, including the nutrient content of fertilisers recorded in kg, so it can be higher than the value of a single input.${r.lineBreak}` : "";
1261
1299
  const customErrorMessage = {
1262
1300
  "this field is required": ({ dataPath }, _errorCount, allErrors) => {
1263
1301
  const parts = parseDataPath(dataPath);
@@ -1667,22 +1705,24 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1667
1705
  Please, use a term with termType ${glossaryTypeLink(schema.TermTermType.landCover)} instead.
1668
1706
  This will help us gap-fill the amount of grass consumed by the herd.`,
1669
1707
  "the sum of all pastureGrass values must be 100": ({ params }) => `The sum of all ${termLink(pastureGrass)} values must be equal to ${params.expected}%.`,
1670
- "is outside confidence interval": (error, errorCount, allErrors) => `${utils.unique(
1671
- (allErrors.length ? allErrors : [error]).flatMap(
1672
- ({ dataPath, params }) => {
1673
- var _a;
1674
- return (_a = params == null ? void 0 : params.outliers) == null ? void 0 : _a.map(
1675
- (value) => {
1708
+ "is outside confidence interval": (error, errorCount, allErrors) => {
1709
+ const errors = allErrors.length ? allErrors : [error];
1710
+ return `${utils.unique(
1711
+ errors.flatMap(
1712
+ (current) => {
1713
+ var _a, _b;
1714
+ return (_b = (_a = current.params) == null ? void 0 : _a.outliers) == null ? void 0 : _b.map((value) => {
1676
1715
  var _a2;
1677
- return `The ${dataPath.includes("product") ? "product" : "input"} value ${errorCount === 1 ? `${code(value)} ` : ""}is ${value > (params == null ? void 0 : params.min) ? "above" : "below"} ${(params == null ? void 0 : params.threshold) ? `${code(`${(Number(params.threshold) + (1 - Number(params.threshold)) / 2) * 100}%`)} of` : ""} all ${(params == null ? void 0 : params.group) ? code(params.group) : code((_a2 = params == null ? void 0 : params.term) == null ? void 0 : _a2.name)} data in ${code(
1716
+ const { params } = current;
1717
+ return `${outlierSubject(current, value, errorCount)} is ${value > (params == null ? void 0 : params.min) ? "above" : "below"} ${(params == null ? void 0 : params.threshold) ? `${code(`${(Number(params.threshold) + (1 - Number(params.threshold)) / 2) * 100}%`)} of` : ""} all ${(params == null ? void 0 : params.group) ? code(params.group) : code((_a2 = params == null ? void 0 : params.term) == null ? void 0 : _a2.name)} data in ${code(
1678
1718
  params == null ? void 0 : params.country.name
1679
1719
  )} on the HESTIA platform.`;
1680
- }
1681
- );
1682
- }
1683
- )
1684
- ).join(r.lineBreak)}${r.lineBreak}${distributionSources(allErrors.length ? allErrors : [error])}
1685
- Please double-check these values before submitting.`,
1720
+ });
1721
+ }
1722
+ )
1723
+ ).join(r.lineBreak)}${r.lineBreak}${outlierGroupNote(errors)}${distributionSources(errors)}
1724
+ Please double-check these values before submitting.`;
1725
+ },
1686
1726
  "should be equal to boundary": ({ params }) => `The calculated ${schemaLink("Site#area", "area")} from ${code("boundary")} is ${code(params == null ? void 0 : params.expected)} ha.
1687
1727
  This may be an error if the boundary represents the area under cultivation.`,
1688
1728
  "must specify the fate of cropResidue": ({ params }) => `For sites of type ${params.siteType.map(code).join(" or ")},
@@ -1690,7 +1730,7 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1690
1730
  true
1691
1731
  )} for crop residue, the fates and quantities of the residue must be specified.
1692
1732
  ${r.lineBreak}
1693
- The quantity must include both above and below ground crop residue, where the sum of above ground crop residue and below ground crop residue must be greater than zero.
1733
+ The quantity must include both above and below ground crop residue, where the above ground crop residue must be greater than zero, unless the crop produces no crop residue (e.g. mushrooms).
1694
1734
  This can be added using the terms:
1695
1735
  ${formatList([
1696
1736
  `${termLink({ id: "belowGroundCropResidue", name: "Below ground crop residue" })}; and`,
@@ -1813,7 +1853,7 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1813
1853
  schema.CycleFunctionalUnit.relative
1814
1854
  )} or change the ${schemaLink("Site#siteType", "siteType")}.`,
1815
1855
  "should specify a value when HESTIA has a default one": ({ params }) => `As you have not specified a ${code("value")}, we will assume the value is ${code(params == null ? void 0 : params.expected)}.
1816
- If this is incorrect, pelase add a value.`,
1856
+ If this is incorrect, please add a value.`,
1817
1857
  "should contain at least one management node": ({ params }) => `${(params == null ? void 0 : params.termType) ? `Does not contain information about ${code(params.termType)}.
1818
1858
  Please consider adding a history of ${code(
1819
1859
  params.termType
@@ -2038,12 +2078,36 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
2038
2078
  but the calculated duration from the end date and start date provided is ${params.current}.`,
2039
2079
  "should add the term pastureGrass": () => `For Cycles with ${code("siteType=permanent pasture")} we recommend specifying the type(s) of ${code("Pasture grass")} grown.
2040
2080
  This is particularly important for ruminants and will allow for a more accurate estimate of the amount of grass grazed.`,
2041
- "should add the fields for a relative cycle": ({ params }) => {
2042
- var _a;
2043
- return `We strongly recommend setting ${(_a = params == null ? void 0 : params.expected) == null ? void 0 : _a.map((param) => code(param)).join("and")} on all other sites for any uploads with a ${code(
2044
- "relative"
2045
- )} functional unit. This will allow calculation of land occupation and land transformation.`;
2081
+ "can not calculate all emissions": ({ params }) => {
2082
+ var _a, _b, _c;
2083
+ const indicators = "GWP100, acidification and other similar indicators";
2084
+ const recommended = (_a = params == null ? void 0 : params.recommended) != null ? _a : [];
2085
+ const missing = (_b = params == null ? void 0 : params.missing) != null ? _b : [];
2086
+ const missingCount = missing.reduce(
2087
+ (count, { terms }) => count + terms.length,
2088
+ 0
2089
+ );
2090
+ const unlocked = (_c = params == null ? void 0 : params.newlyCovered) != null ? _c : [];
2091
+ const these = recommended.length === 1 ? "this" : "all of this";
2092
+ return (recommended.length ? [
2093
+ r.bold(`HESTIA cannot yet calculate all the emissions used by ${indicators} for this Cycle.`),
2094
+ "To calculate more of them:",
2095
+ ...recommended.map(coverageItem),
2096
+ unlocked.length ? `With ${these}, HESTIA could calculate ${unlocked.length} more ${pluralize("emission", unlocked.length)}, such as: ${termNames(unlocked)}.` : `With ${these}, HESTIA could calculate more of these emissions.`,
2097
+ ...missing.length ? [
2098
+ `Even then, ${pluralizeCount(missingCount, "emission")} will be missing, so the indicators using ${missingCount === 1 ? "it" : "them"} will be incomplete:`,
2099
+ ...missing.map(coverageMissing)
2100
+ ] : []
2101
+ ] : [
2102
+ r.bold(
2103
+ `With the data provided, ${pluralizeCount(missingCount, "emission")} used by ${indicators} cannot be calculated, so those indicators will be incomplete:`
2104
+ ),
2105
+ ...missing.map(coverageMissing)
2106
+ ]).join(r.lineBreak);
2046
2107
  },
2108
+ "should add the fields for a relative cycle": ({ params }) => `We strongly recommend setting ${listAsCodeWithAnd(params == null ? void 0 : params.expected)} on all other sites for any uploads with a ${code(
2109
+ "relative"
2110
+ )} functional unit. This will allow calculation of land occupation and land transformation.`,
2047
2111
  "must not be blank if complete": ({ params }) => `If completeness is set to true for ${schemaLink(
2048
2112
  `Completeness#${params.expected}`,
2049
2113
  params.expected
@@ -710,6 +710,11 @@ var validations = [
710
710
  level: "error",
711
711
  message: "must have a value or a distance if complete"
712
712
  },
713
+ {
714
+ function: "validate_coverage",
715
+ level: "warning",
716
+ message: "can not calculate all emissions"
717
+ },
713
718
  {
714
719
  function: "validate_has_termType",
715
720
  level: "warning",
@@ -1202,6 +1207,30 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1202
1207
  const listAsCode = (values) => (values != null ? values : []).map((value) => r.code(value)).join(", ");
1203
1208
  const joinWithAnd = (values) => values.length > 1 ? `${values.slice(0, -1).join(", ")} and ${values[values.length - 1]}` : values.join("");
1204
1209
  const listAsCodeWithAnd = (values) => joinWithAnd((values != null ? values : []).map((value) => r.code(value)));
1210
+ const joinWithOr = (values) => values.length > 1 ? `${values.slice(0, -1).join(", ")} or ${values[values.length - 1]}` : values.join("");
1211
+ const pluralizeCount = (count, word) => `${count} ${pluralize(word, count)}`;
1212
+ const termNames = (terms = [], max = 3) => [
1213
+ ...terms.slice(0, max).map(({ "@id": id, name }) => termLink({ id, name })),
1214
+ ...terms.length > max ? [`${terms.length - max} more`] : []
1215
+ ].reduce((text, name, index, names) => index === 0 ? name : `${text}; ${index === names.length - 1 ? "and " : ""}${name}`, "");
1216
+ const coverageItems = {
1217
+ properties: ({ expected, term, blankNodeType }) => `add ${listAsCodeWithAnd(expected)} to the ${code(term == null ? void 0 : term["@id"])} ${blankNodeType}`,
1218
+ cropResidue: () => `add the fate of the crop residue: a ${code(TermTermType.cropResidueManagement)} Practice, or the crop residue Products`,
1219
+ completeness: ({ paths }) => `set ${listAsCodeWithAnd(paths)} to ${code(true)}, if all the ${joinWithAnd(
1220
+ paths.map((path) => path.split(".")[1])
1221
+ )} data is recorded`,
1222
+ pastureGrass: () => `add a ${code("pastureGrass")} Practice, with the type of grass as its key`,
1223
+ system: ({ expected }) => `add an animal production system Practice${(expected == null ? void 0 : expected.length) ? `: ${joinWithOr(expected.map(code))}` : ""}`,
1224
+ milkYield: ({ expected }) => `add a milk yield Practice to the Animals: ${joinWithOr(expected.map(code))}`,
1225
+ animals: () => "add the herd composition, as Animals",
1226
+ data: ({ path, expected }) => path === "or" ? `add one of ${joinWithOr(expected.map((fields) => listAsCodeWithAnd(fields)))}` : (expected == null ? void 0 : expected.length) && expected.join() !== path.split(".").pop() ? `add ${joinWithOr(expected.map(code))} to ${code(path)}` : `add ${code(path)}`
1227
+ };
1228
+ const coverageItem = (item) => {
1229
+ var _a, _b;
1230
+ return `\u2022 ${(_b = (_a = coverageItems[item.type]) == null ? void 0 : _a.call(coverageItems, item)) != null ? _b : code(item.path)}${item.nodeType && item.nodeType !== SchemaType.Cycle ? ` (on the ${item.nodeType})` : ""}`;
1231
+ };
1232
+ const coverageRequirement = ({ path, expected, current, nodeType: nodeType2 }) => `${code(path)} is ${joinWithOr(expected.map(code))}${current ? ` (this ${nodeType2} has ${code(current)})` : ""}`;
1233
+ const coverageMissing = ({ requirements, terms }) => `\u2022 HESTIA only calculates them when ${joinWithAnd(requirements.map(coverageRequirement))}: ${termNames(terms)}.`;
1205
1234
  const listAsTermLinksWithAnd = (termIds) => joinWithAnd((termIds != null ? termIds : []).map((id) => termLink({ id })));
1206
1235
  const glossaryQueryLink = (query, type) => r.link(`${urls.glossaryBaseUrl()}?query=${encodeURIComponent(query)}&termType=${type}`, query);
1207
1236
  const joinValues = (values) => (values || []).filter(Boolean).map((value) => r.code(value)).join(r.inlineSeparator);
@@ -1256,6 +1285,15 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1256
1285
  const sentences = unique(errors.map(({ params }) => params == null ? void 0 : params.source)).map((source) => distributionSourceMessage[source]).filter(Boolean);
1257
1286
  return sentences.length ? `${sentences.join(r.lineBreak)}${r.lineBreak}` : "";
1258
1287
  };
1288
+ const outlierSubject = ({ dataPath, params }, value, errorCount) => {
1289
+ var _a;
1290
+ const shownValue = errorCount === 1 ? ` ${code(value)}` : "";
1291
+ return dataPath.includes("product") ? `The product value${shownValue}` : ((_a = params == null ? void 0 : params.termIds) == null ? void 0 : _a.length) ? `The total of ${joinWithAnd(params.termIds.map((id) => termLink({ id })))}, ${code(value)},` : (params == null ? void 0 : params.group) ? `The total of all ${code(params.group)} inputs of this Cycle${shownValue}` : `The input value${shownValue}`;
1292
+ };
1293
+ const outlierGroupNote = (errors) => errors.some(({ dataPath, params }) => {
1294
+ var _a;
1295
+ return !dataPath.includes("product") && (((_a = params == null ? void 0 : params.termIds) == null ? void 0 : _a.length) || (params == null ? void 0 : params.group));
1296
+ }) ? `This total adds up every input checked together, including the nutrient content of fertilisers recorded in kg, so it can be higher than the value of a single input.${r.lineBreak}` : "";
1259
1297
  const customErrorMessage = {
1260
1298
  "this field is required": ({ dataPath }, _errorCount, allErrors) => {
1261
1299
  const parts = parseDataPath(dataPath);
@@ -1665,22 +1703,24 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1665
1703
  Please, use a term with termType ${glossaryTypeLink(TermTermType.landCover)} instead.
1666
1704
  This will help us gap-fill the amount of grass consumed by the herd.`,
1667
1705
  "the sum of all pastureGrass values must be 100": ({ params }) => `The sum of all ${termLink(pastureGrass)} values must be equal to ${params.expected}%.`,
1668
- "is outside confidence interval": (error, errorCount, allErrors) => `${unique(
1669
- (allErrors.length ? allErrors : [error]).flatMap(
1670
- ({ dataPath, params }) => {
1671
- var _a;
1672
- return (_a = params == null ? void 0 : params.outliers) == null ? void 0 : _a.map(
1673
- (value) => {
1706
+ "is outside confidence interval": (error, errorCount, allErrors) => {
1707
+ const errors = allErrors.length ? allErrors : [error];
1708
+ return `${unique(
1709
+ errors.flatMap(
1710
+ (current) => {
1711
+ var _a, _b;
1712
+ return (_b = (_a = current.params) == null ? void 0 : _a.outliers) == null ? void 0 : _b.map((value) => {
1674
1713
  var _a2;
1675
- return `The ${dataPath.includes("product") ? "product" : "input"} value ${errorCount === 1 ? `${code(value)} ` : ""}is ${value > (params == null ? void 0 : params.min) ? "above" : "below"} ${(params == null ? void 0 : params.threshold) ? `${code(`${(Number(params.threshold) + (1 - Number(params.threshold)) / 2) * 100}%`)} of` : ""} all ${(params == null ? void 0 : params.group) ? code(params.group) : code((_a2 = params == null ? void 0 : params.term) == null ? void 0 : _a2.name)} data in ${code(
1714
+ const { params } = current;
1715
+ return `${outlierSubject(current, value, errorCount)} is ${value > (params == null ? void 0 : params.min) ? "above" : "below"} ${(params == null ? void 0 : params.threshold) ? `${code(`${(Number(params.threshold) + (1 - Number(params.threshold)) / 2) * 100}%`)} of` : ""} all ${(params == null ? void 0 : params.group) ? code(params.group) : code((_a2 = params == null ? void 0 : params.term) == null ? void 0 : _a2.name)} data in ${code(
1676
1716
  params == null ? void 0 : params.country.name
1677
1717
  )} on the HESTIA platform.`;
1678
- }
1679
- );
1680
- }
1681
- )
1682
- ).join(r.lineBreak)}${r.lineBreak}${distributionSources(allErrors.length ? allErrors : [error])}
1683
- Please double-check these values before submitting.`,
1718
+ });
1719
+ }
1720
+ )
1721
+ ).join(r.lineBreak)}${r.lineBreak}${outlierGroupNote(errors)}${distributionSources(errors)}
1722
+ Please double-check these values before submitting.`;
1723
+ },
1684
1724
  "should be equal to boundary": ({ params }) => `The calculated ${schemaLink("Site#area", "area")} from ${code("boundary")} is ${code(params == null ? void 0 : params.expected)} ha.
1685
1725
  This may be an error if the boundary represents the area under cultivation.`,
1686
1726
  "must specify the fate of cropResidue": ({ params }) => `For sites of type ${params.siteType.map(code).join(" or ")},
@@ -1688,7 +1728,7 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1688
1728
  true
1689
1729
  )} for crop residue, the fates and quantities of the residue must be specified.
1690
1730
  ${r.lineBreak}
1691
- The quantity must include both above and below ground crop residue, where the sum of above ground crop residue and below ground crop residue must be greater than zero.
1731
+ The quantity must include both above and below ground crop residue, where the above ground crop residue must be greater than zero, unless the crop produces no crop residue (e.g. mushrooms).
1692
1732
  This can be added using the terms:
1693
1733
  ${formatList([
1694
1734
  `${termLink({ id: "belowGroundCropResidue", name: "Below ground crop residue" })}; and`,
@@ -1811,7 +1851,7 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
1811
1851
  CycleFunctionalUnit.relative
1812
1852
  )} or change the ${schemaLink("Site#siteType", "siteType")}.`,
1813
1853
  "should specify a value when HESTIA has a default one": ({ params }) => `As you have not specified a ${code("value")}, we will assume the value is ${code(params == null ? void 0 : params.expected)}.
1814
- If this is incorrect, pelase add a value.`,
1854
+ If this is incorrect, please add a value.`,
1815
1855
  "should contain at least one management node": ({ params }) => `${(params == null ? void 0 : params.termType) ? `Does not contain information about ${code(params.termType)}.
1816
1856
  Please consider adding a history of ${code(
1817
1857
  params.termType
@@ -2036,12 +2076,36 @@ var createErrorFormatter = ({ renderer: r, urls }) => {
2036
2076
  but the calculated duration from the end date and start date provided is ${params.current}.`,
2037
2077
  "should add the term pastureGrass": () => `For Cycles with ${code("siteType=permanent pasture")} we recommend specifying the type(s) of ${code("Pasture grass")} grown.
2038
2078
  This is particularly important for ruminants and will allow for a more accurate estimate of the amount of grass grazed.`,
2039
- "should add the fields for a relative cycle": ({ params }) => {
2040
- var _a;
2041
- return `We strongly recommend setting ${(_a = params == null ? void 0 : params.expected) == null ? void 0 : _a.map((param) => code(param)).join("and")} on all other sites for any uploads with a ${code(
2042
- "relative"
2043
- )} functional unit. This will allow calculation of land occupation and land transformation.`;
2079
+ "can not calculate all emissions": ({ params }) => {
2080
+ var _a, _b, _c;
2081
+ const indicators = "GWP100, acidification and other similar indicators";
2082
+ const recommended = (_a = params == null ? void 0 : params.recommended) != null ? _a : [];
2083
+ const missing = (_b = params == null ? void 0 : params.missing) != null ? _b : [];
2084
+ const missingCount = missing.reduce(
2085
+ (count, { terms }) => count + terms.length,
2086
+ 0
2087
+ );
2088
+ const unlocked = (_c = params == null ? void 0 : params.newlyCovered) != null ? _c : [];
2089
+ const these = recommended.length === 1 ? "this" : "all of this";
2090
+ return (recommended.length ? [
2091
+ r.bold(`HESTIA cannot yet calculate all the emissions used by ${indicators} for this Cycle.`),
2092
+ "To calculate more of them:",
2093
+ ...recommended.map(coverageItem),
2094
+ unlocked.length ? `With ${these}, HESTIA could calculate ${unlocked.length} more ${pluralize("emission", unlocked.length)}, such as: ${termNames(unlocked)}.` : `With ${these}, HESTIA could calculate more of these emissions.`,
2095
+ ...missing.length ? [
2096
+ `Even then, ${pluralizeCount(missingCount, "emission")} will be missing, so the indicators using ${missingCount === 1 ? "it" : "them"} will be incomplete:`,
2097
+ ...missing.map(coverageMissing)
2098
+ ] : []
2099
+ ] : [
2100
+ r.bold(
2101
+ `With the data provided, ${pluralizeCount(missingCount, "emission")} used by ${indicators} cannot be calculated, so those indicators will be incomplete:`
2102
+ ),
2103
+ ...missing.map(coverageMissing)
2104
+ ]).join(r.lineBreak);
2044
2105
  },
2106
+ "should add the fields for a relative cycle": ({ params }) => `We strongly recommend setting ${listAsCodeWithAnd(params == null ? void 0 : params.expected)} on all other sites for any uploads with a ${code(
2107
+ "relative"
2108
+ )} functional unit. This will allow calculation of land occupation and land transformation.`,
2045
2109
  "must not be blank if complete": ({ params }) => `If completeness is set to true for ${schemaLink(
2046
2110
  `Completeness#${params.expected}`,
2047
2111
  params.expected
package/index.js CHANGED
@@ -718,6 +718,11 @@ var validations = [
718
718
  level: "error",
719
719
  message: "must have a value or a distance if complete"
720
720
  },
721
+ {
722
+ function: "validate_coverage",
723
+ level: "warning",
724
+ message: "can not calculate all emissions"
725
+ },
721
726
  {
722
727
  function: "validate_has_termType",
723
728
  level: "warning",
package/index.mjs CHANGED
@@ -716,6 +716,11 @@ var validations = [
716
716
  level: "error",
717
717
  message: "must have a value or a distance if complete"
718
718
  },
719
+ {
720
+ function: "validate_coverage",
721
+ level: "warning",
722
+ message: "can not calculate all emissions"
723
+ },
719
724
  {
720
725
  function: "validate_has_termType",
721
726
  level: "warning",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hestia-earth/data-validation",
3
- "version": "0.41.11",
3
+ "version": "0.41.13",
4
4
  "description": "HESTIA Data Validation library",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
package/validation.json CHANGED
@@ -705,6 +705,11 @@
705
705
  "level": "error",
706
706
  "message": "must have a value or a distance if complete"
707
707
  },
708
+ {
709
+ "function": "validate_coverage",
710
+ "level": "warning",
711
+ "message": "can not calculate all emissions"
712
+ },
708
713
  {
709
714
  "function": "validate_has_termType",
710
715
  "level": "warning",