@hestia-earth/engine-models 0.73.1 → 0.73.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/config.ts CHANGED
@@ -19,13 +19,17 @@ export interface IOrchestratorConfig {
19
19
  models: (IOrchestratorModelConfig | IOrchestratorModelConfig[])[];
20
20
  }
21
21
 
22
- const allowedTypes = [NodeType.Cycle, NodeType.Site, NodeType.ImpactAssessment];
23
-
24
22
  export type allowedType =
25
23
  | NodeType.Cycle
26
24
  | NodeType.Site
27
25
  | NodeType.ImpactAssessment;
28
26
 
27
+ export const allowedTypes: allowedType[] = [
28
+ NodeType.Cycle,
29
+ NodeType.Site,
30
+ NodeType.ImpactAssessment
31
+ ];
32
+
29
33
  const validateType = (nodeType: any) =>
30
34
  allowedTypes.includes(nodeType) ||
31
35
  (() => {
package/src/index.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './config';
2
2
  export * from './models';
3
+ export * from './utils';
4
+ export * from './validate-config';
3
5
  export * from './version';
@@ -3,7 +3,7 @@ import { describe, expect, test } from '@jest/globals';
3
3
  import { models } from './models';
4
4
 
5
5
  describe('models', () => {
6
- describe('loadModels', () => {
6
+ describe('models', () => {
7
7
  test('has links', () => {
8
8
  expect(models.links?.length > 0).toBeTruthy();
9
9
  });
package/src/models.ts CHANGED
@@ -26,6 +26,10 @@ export interface IModel {
26
26
  * A key in the model if term does not exist.
27
27
  */
28
28
  modelKey?: string;
29
+ /**
30
+ * List of other models this model needs to run.
31
+ */
32
+ dependencies?: string[];
29
33
  }
30
34
 
31
35
  export type modelLookup = Record<
@@ -0,0 +1,118 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+
3
+ import { IModel } from './models';
4
+ import { findMatchingModel, findMatchingModelFromConfig } from './utils';
5
+
6
+ describe('models', () => {
7
+ describe('findMatchingModel', () => {
8
+ const model = 'stehfestBouwman2006';
9
+ const term = 'noxToAirInorganicFertiliser';
10
+ const path =
11
+ 'hestia_earth/models/stehfestBouwman2006/noxToAirInorganicFertiliser.py';
12
+
13
+ test('should find by model', () => {
14
+ const params = { model, term } as Partial<IModel>;
15
+
16
+ expect(findMatchingModel(params)).toEqual(
17
+ expect.objectContaining({ model, term, path })
18
+ );
19
+ });
20
+
21
+ test('should handle values with dot', () => {
22
+ expect(
23
+ findMatchingModel(
24
+ {
25
+ model: 'ipcc2019',
26
+ term: 'animal.pregnancyRateTotal'
27
+ },
28
+ { useDotValues: true }
29
+ )
30
+ ).toEqual(
31
+ expect.objectContaining({
32
+ model: 'ipcc2019',
33
+ term: 'pregnancyRateTotal'
34
+ })
35
+ );
36
+ });
37
+ });
38
+
39
+ describe('findMatchingModelFromConfig', () => {
40
+ test('should handle completeness', () => {
41
+ const model = 'cycle';
42
+
43
+ expect(
44
+ findMatchingModelFromConfig({
45
+ model,
46
+ value: 'completeness'
47
+ })
48
+ ).toEqual(
49
+ expect.objectContaining({
50
+ model: 'cycle',
51
+ modelKey: 'completeness.animalFeed'
52
+ })
53
+ );
54
+ });
55
+
56
+ test('should handle feedConversionRatio', () => {
57
+ const model = 'hestia';
58
+
59
+ expect(
60
+ findMatchingModelFromConfig({
61
+ model,
62
+ value: 'feedConversionRatio'
63
+ })
64
+ ).toEqual(
65
+ expect.objectContaining({
66
+ model: 'hestia',
67
+ term: 'feedConversionRatioCarbon'
68
+ })
69
+ );
70
+ });
71
+
72
+ test('should handle ecoinventV3AndEmberClimate', () => {
73
+ const model = 'ecoinventV3AndEmberClimate';
74
+
75
+ expect(
76
+ findMatchingModelFromConfig({
77
+ model,
78
+ value: 'all'
79
+ })
80
+ ).toEqual(
81
+ expect.objectContaining({
82
+ model
83
+ })
84
+ );
85
+ });
86
+
87
+ test('should handle ecoinventV3AndEmberClimate', () => {
88
+ const model = 'ecoinventV3AndEmberClimate';
89
+
90
+ expect(
91
+ findMatchingModelFromConfig({
92
+ model,
93
+ value: 'all'
94
+ })
95
+ ).toEqual(
96
+ expect.objectContaining({
97
+ model
98
+ })
99
+ );
100
+ });
101
+
102
+ test('should handle seed_emissions', () => {
103
+ const model = 'hestia';
104
+
105
+ expect(
106
+ findMatchingModelFromConfig({
107
+ model,
108
+ value: 'seed_emissions'
109
+ })
110
+ ).toEqual(
111
+ expect.objectContaining({
112
+ model,
113
+ modelKey: 'seed_emissions'
114
+ })
115
+ );
116
+ });
117
+ });
118
+ });
package/src/utils.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { IOrchestratorModelConfig } from './config';
2
+ import { IModel, models } from './models';
3
+
4
+ type findMatchFunc = (
5
+ model: IModel,
6
+ key: string,
7
+ value: string | string[]
8
+ ) => boolean;
9
+
10
+ const matchWithDot: findMatchFunc = (model, key, value) =>
11
+ typeof value === 'string' && value.split('.').pop() === model[key];
12
+
13
+ const matchPath: findMatchFunc = (model, key, value) =>
14
+ key === 'path' && typeof value === 'string' && model[key].startsWith(value);
15
+
16
+ export const findMatchingModel = (
17
+ model: Partial<IModel>,
18
+ config: {
19
+ useDotValues?: boolean;
20
+ } = {
21
+ useDotValues: false
22
+ }
23
+ ): IModel =>
24
+ Object.keys(model).length > 0
25
+ ? models.links.find((m) =>
26
+ Object.entries(model).every(
27
+ ([key, value]) =>
28
+ value === m[key] ||
29
+ (config.useDotValues && matchWithDot(m, key, value)) ||
30
+ matchPath(m, key, value)
31
+ )
32
+ )
33
+ : null;
34
+
35
+ const modelMatchOrder = ({
36
+ model,
37
+ value
38
+ }: Pick<IOrchestratorModelConfig, 'model' | 'value'>) =>
39
+ [
40
+ { model, term: value },
41
+ { model, modelKey: value },
42
+ {
43
+ model,
44
+ modelKey: value.split('.').pop()
45
+ },
46
+ {
47
+ model,
48
+ term: value.split('.').pop()
49
+ },
50
+ {
51
+ model,
52
+ path: ['hestia_earth', 'models', model, value].join('/')
53
+ },
54
+ !value || value === 'all' ? { model } : null
55
+ ].filter(Boolean) as Partial<IModel>[];
56
+
57
+ export const findMatchingModelFromConfig = (
58
+ model: Pick<IOrchestratorModelConfig, 'model' | 'value'>
59
+ ) =>
60
+ modelMatchOrder(model)
61
+ .map(
62
+ (params) =>
63
+ findMatchingModel(params) ||
64
+ findMatchingModel(params, { useDotValues: true })
65
+ )
66
+ ?.filter(Boolean)
67
+ ?.shift();
@@ -0,0 +1,29 @@
1
+ import { describe, expect, test } from '@jest/globals';
2
+ import { NodeType } from '@hestia-earth/schema';
3
+
4
+ import { validateConfig } from './validate-config';
5
+
6
+ describe('validate-config', () => {
7
+ describe('validateConfig', () => {
8
+ describe(NodeType.Cycle, () => {
9
+ test('should be valid', () => {
10
+ const results = validateConfig(NodeType.Cycle);
11
+ expect(results).toEqual([]);
12
+ });
13
+ });
14
+
15
+ describe(NodeType.ImpactAssessment, () => {
16
+ test('should be valid', () => {
17
+ const results = validateConfig(NodeType.ImpactAssessment);
18
+ expect(results).toEqual([]);
19
+ });
20
+ });
21
+
22
+ describe(NodeType.Site, () => {
23
+ test('should be valid', () => {
24
+ const results = validateConfig(NodeType.Site);
25
+ expect(results).toEqual([]);
26
+ });
27
+ });
28
+ });
29
+ });
@@ -0,0 +1,67 @@
1
+ import { allowedType, IOrchestratorModelConfig, loadConfig } from './config';
2
+ import { findMatchingModelFromConfig } from './utils';
3
+
4
+ // models not included in the links
5
+ const skipModels = ['emissions.deleted', 'transformations'];
6
+
7
+ const isHestiaResidueCyclicModel = ({
8
+ model,
9
+ value
10
+ }: IOrchestratorModelConfig) =>
11
+ model === 'hestia' && value?.startsWith('residue');
12
+
13
+ const isOrganicCarbonCyclicModel = ({
14
+ model,
15
+ value
16
+ }: IOrchestratorModelConfig) =>
17
+ model === 'hestia' && value?.startsWith('organicCarbonPer');
18
+
19
+ const includeModel = (model: IOrchestratorModelConfig) =>
20
+ [
21
+ !skipModels.includes(model.model),
22
+ // cyclic dependencies
23
+ !isHestiaResidueCyclicModel(model),
24
+ !isOrganicCarbonCyclicModel(model)
25
+ ].every(Boolean);
26
+
27
+ // dependencies that cannot be validated
28
+ const skipDependencies = [
29
+ // completeness gap-fills after other models run
30
+ 'completeness'
31
+ ];
32
+
33
+ const matchDependencies =
34
+ (models: IOrchestratorModelConfig[]) => (model: IOrchestratorModelConfig) => {
35
+ const { dependencies } = findMatchingModelFromConfig(model);
36
+ const previousModels = models.slice(0, models.indexOf(model));
37
+ const existing =
38
+ dependencies?.filter((value) =>
39
+ previousModels.some(
40
+ (config) => config.value?.split('.').pop() === value
41
+ )
42
+ ) ?? [];
43
+ return {
44
+ model,
45
+ existing,
46
+ missing:
47
+ dependencies
48
+ // skip dependencies that dont exist at all
49
+ ?.filter((value) => models.some((config) => config.value === value))
50
+ ?.filter(
51
+ (v) =>
52
+ !existing.includes(v) &&
53
+ !skipDependencies.includes(v) &&
54
+ v !== model.value
55
+ ) ?? []
56
+ };
57
+ };
58
+
59
+ export const validateConfig = (nodeType: allowedType) => {
60
+ const config = loadConfig(nodeType);
61
+ // flatten all models to process one by one, starting with the last one
62
+ const models = config.models.flat();
63
+
64
+ const results = models.filter(includeModel).map(matchDependencies(models));
65
+
66
+ return results.filter((r) => r.missing.length > 0);
67
+ };
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const ENGINE_VERSION = '0.73.1';
1
+ export const ENGINE_VERSION = '0.73.3';