diff --git a/manifests/examples/nesting.yml b/manifests/examples/nesting.yml index d3e6408f4..e56e8bb18 100644 --- a/manifests/examples/nesting.yml +++ b/manifests/examples/nesting.yml @@ -11,7 +11,7 @@ aggregation: type: "both" params: initialize: - #outputs: ['yaml'] + outputs: ['yaml'] plugins: teads-curve: path: "@grnsft/if-unofficial-plugins" @@ -37,7 +37,14 @@ initialize: method: Sci global-config: functional-unit: "requests" - functional-unit-time: "1 minute" + "sum-carbon": + path: "builtin" + method: Sum + global-config: + input-parameters: + - carbon-operational + - carbon-embodied + output-parameter: carbon time-sync: method: TimeSync path: "builtin" @@ -62,6 +69,7 @@ tree: - sum - sci-embodied - sci-o + - sum-carbon - time-sync - sci inputs: @@ -71,24 +79,28 @@ tree: duration: 1 cpu/utilization: 50 network/energy: 0.000001 + requests: 50 - timestamp: "2023-12-12T00:00:01.000Z" duration: 5 cpu/utilization: 20 cloud/instance-type: A1 cloud/region: uk-west network/energy: 0.000001 + requests: 60 - timestamp: "2023-12-12T00:00:06.000Z" duration: 7 cpu/utilization: 15 cloud/instance-type: A1 cloud/region: uk-west network/energy: 0.000001 + requests: 70 - timestamp: "2023-12-12T00:00:13.000Z" duration: 30 cloud/instance-type: A1 cloud/region: uk-west cpu/utilization: 15 network/energy: 0.000001 + requests: 55 child-1: defaults: cpu/thermal-design-power: 100 @@ -103,6 +115,7 @@ tree: - sum - sci-embodied - sci-o + - sum-carbon - time-sync - sci inputs: @@ -112,24 +125,28 @@ tree: duration: 1 cpu/utilization: 50 network/energy: 0.000001 + requests: 10 - timestamp: "2023-12-12T00:00:01.000Z" duration: 5 cpu/utilization: 20 cloud/instance-type: A1 cloud/region: uk-west network/energy: 0.000001 + requests: 90 - timestamp: "2023-12-12T00:00:06.000Z" duration: 7 cpu/utilization: 15 cloud/instance-type: A1 cloud/region: uk-west network/energy: 0.000001 + requests: 30 - timestamp: "2023-12-12T00:00:13.000Z" duration: 30 cloud/instance-type: A1 cloud/region: uk-west cpu/utilization: 15 network/energy: 0.000001 + requests: 22 child-2: children: child-2-0: @@ -146,6 +163,7 @@ tree: - sum - sci-embodied - sci-o + - sum-carbon - time-sync - sci inputs: @@ -155,24 +173,28 @@ tree: duration: 1 cpu/utilization: 50 network/energy: 0.000001 + requests: 50 - timestamp: "2023-12-12T00:00:01.000Z" duration: 5 cpu/utilization: 20 cloud/instance-type: A1 cloud/region: uk-west network/energy: 0.000001 + requests: 65 - timestamp: "2023-12-12T00:00:06.000Z" duration: 7 cpu/utilization: 15 cloud/instance-type: A1 cloud/region: uk-west network/energy: 0.000001 + requests: 80 - timestamp: "2023-12-12T00:00:13.000Z" duration: 30 cloud/instance-type: A1 cloud/region: uk-west cpu/utilization: 15 network/energy: 0.000001 + requests: 40 child-2-1: defaults: cpu/thermal-design-power: 100 @@ -187,6 +209,7 @@ tree: - sum - sci-embodied - sci-o + - sum-carbon - time-sync - sci inputs: @@ -196,21 +219,25 @@ tree: duration: 1 cpu/utilization: 50 network/energy: 0.000001 + requests: 50 - timestamp: "2023-12-12T00:00:01.000Z" duration: 5 cpu/utilization: 20 cloud/instance-type: A1 cloud/region: uk-west network/energy: 0.000001 + requests: 50 - timestamp: "2023-12-12T00:00:06.000Z" duration: 7 cpu/utilization: 15 cloud/instance-type: A1 cloud/region: uk-west network/energy: 0.000001 + requests: 60 - timestamp: "2023-12-12T00:00:13.000Z" duration: 30 cloud/instance-type: A1 cloud/region: uk-west cpu/utilization: 15 network/energy: 0.000001 + requests: 40 diff --git a/manifests/examples/pipeline-teads-sci.yml b/manifests/examples/pipeline-teads-sci.yml index 8981de3e6..01c491cdc 100644 --- a/manifests/examples/pipeline-teads-sci.yml +++ b/manifests/examples/pipeline-teads-sci.yml @@ -26,8 +26,15 @@ initialize: path: "builtin" method: Sci global-config: - functional-unit: "" - functional-unit-time: "1-day" + functional-unit: "component" + "sum-carbon": + path: "builtin" + method: Sum + global-config: + input-parameters: + - carbon-operational + - carbon-embodied + output-parameter: carbon "time-sync": method: TimeSync path: "builtin" @@ -44,7 +51,7 @@ tree: - sum - sci-embodied - sci-o - - time-sync + - sum-carbon - sci config: defaults: @@ -55,6 +62,7 @@ tree: device/expected-lifespan: 94608000 # 3 years in seconds resources-reserved: 1 resources-total: 8 + component: 1 inputs: - timestamp: "2023-12-12T00:00:00.000Z" cloud/instance-type: A1 diff --git a/manifests/examples/pipeline-with-aggregate.yml b/manifests/examples/pipeline-with-aggregate.yml index 5fb74daee..bd3e637ac 100644 --- a/manifests/examples/pipeline-with-aggregate.yml +++ b/manifests/examples/pipeline-with-aggregate.yml @@ -26,7 +26,14 @@ initialize: method: Sci global-config: functional-unit: "requests" - functional-unit-time: "1 minute" + "sum-carbon": + path: "builtin" + method: Sum + global-config: + input-parameters: + - carbon-operational + - carbon-embodied + output-parameter: carbon "time-sync": method: TimeSync path: "builtin" @@ -46,6 +53,7 @@ tree: - sci-e - sci-embodied - sci-o + - sum-carbon - time-sync - sci config: @@ -61,34 +69,38 @@ tree: device/expected-lifespan: 94608000 # 3 years in seconds resources-reserved: 1 resources-total: 8 - functional-unit-time: "1 min" inputs: - timestamp: "2023-12-12T00:00:00.000Z" cloud/instance-type: A1 cloud/region: uk-west duration: 1 cpu/utilization: 10 + requests: 10 - timestamp: "2023-12-12T00:00:01.000Z" duration: 5 cpu/utilization: 20 cloud/instance-type: A1 cloud/region: uk-west + requests: 5 - timestamp: "2023-12-12T00:00:06.000Z" duration: 7 cpu/utilization: 15 cloud/instance-type: A1 cloud/region: uk-west + requests: 15 - timestamp: "2023-12-12T00:00:13.000Z" duration: 30 cloud/instance-type: A1 cloud/region: uk-west cpu/utilization: 15 + requests: 30 child-2: pipeline: - teads-curve - sci-e - sci-embodied - sci-o + - sum-carbon - time-sync - sci config: @@ -104,25 +116,28 @@ tree: device/expected-lifespan: 94608000 # 3 years in seconds resources-reserved: 1 resources-total: 8 - functional-unit-time: "1 min" inputs: - timestamp: "2023-12-12T00:00:00.000Z" duration: 1 cpu/utilization: 30 cloud/instance-type: A1 cloud/region: uk-west + requests: 100 - timestamp: "2023-12-12T00:00:01.000Z" duration: 5 cpu/utilization: 28 cloud/instance-type: A1 cloud/region: uk-west + requests: 150 - timestamp: "2023-12-12T00:00:06.000Z" duration: 7 cpu/utilization: 40 cloud/instance-type: A1 cloud/region: uk-west + requests: 110 - timestamp: "2023-12-12T00:00:13.000Z" duration: 30 cpu/utilization: 33 cloud/instance-type: A1 cloud/region: uk-west + requests: 180 diff --git a/manifests/examples/pipeline-with-mocks.yml b/manifests/examples/pipeline-with-mocks.yml index 45da4d52f..008e319b1 100644 --- a/manifests/examples/pipeline-with-mocks.yml +++ b/manifests/examples/pipeline-with-mocks.yml @@ -39,12 +39,19 @@ initialize: "sci-o": path: "@grnsft/if-plugins" method: SciO + "sum-carbon": + path: "builtin" + method: Sum + global-config: + input-parameters: + - carbon-operational + - carbon-embodied + output-parameter: carbon "sci": path: "builtin" method: Sci global-config: functional-unit: "requests" - functional-unit-time: "1 minute" "time-sync": method: TimeSync path: "builtin" @@ -56,7 +63,7 @@ initialize: "group-by": path: builtin method: GroupBy - outputs: ['yaml'] + # outputs: ['yaml'] tree: children: child-1: @@ -65,6 +72,7 @@ tree: - sci-e - sci-embodied - sci-o + - sum-carbon - time-sync - sci config: @@ -87,27 +95,32 @@ tree: cloud/region: uk-west duration: 1 cpu/utilization: 10 + requests: 30 - timestamp: "2023-12-12T00:00:01.000Z" duration: 5 cpu/utilization: 20 cloud/instance-type: A1 cloud/region: uk-west + requests: 40 - timestamp: "2023-12-12T00:00:06.000Z" duration: 7 cpu/utilization: 15 cloud/instance-type: A1 cloud/region: uk-west + requests: 30 - timestamp: "2023-12-12T00:00:13.000Z" duration: 30 cloud/instance-type: A1 cloud/region: uk-west cpu/utilization: 15 + requests: 50 child-2: pipeline: - teads-curve - sci-e - sci-embodied - sci-o + - sum-carbon - time-sync - sci config: @@ -130,18 +143,22 @@ tree: cpu/utilization: 30 cloud/instance-type: A1 cloud/region: uk-west + requests: 30 - timestamp: "2023-12-12T00:00:01.000Z" duration: 5 cpu/utilization: 28 cloud/instance-type: A1 cloud/region: uk-west + requests: 40 - timestamp: "2023-12-12T00:00:06.000Z" duration: 7 cpu/utilization: 40 cloud/instance-type: A1 cloud/region: uk-west + requests: 50 - timestamp: "2023-12-12T00:00:13.000Z" duration: 30 cpu/utilization: 33 cloud/instance-type: A1 cloud/region: uk-west + requests: 60 diff --git a/manifests/plugins/sci/failure-invalid-config-value.yml b/manifests/plugins/sci/failure-invalid-config-value.yml index a9357f3b2..f5a4def52 100644 --- a/manifests/plugins/sci/failure-invalid-config-value.yml +++ b/manifests/plugins/sci/failure-invalid-config-value.yml @@ -9,7 +9,7 @@ initialize: method: Sci path: "builtin" # global-config: - # functional-unit-time: 1 minute + # functional-unit: 1 minute tree: children: child: @@ -17,7 +17,6 @@ tree: - sci config: sci: - functional-unit-time: 1 sec functional-unit: 999 # factor to convert per time to per f.unit inputs: - timestamp: 2023-07-06T00:00 diff --git a/manifests/plugins/sci/failure-missing-input-param.yml b/manifests/plugins/sci/failure-missing-input-param.yml index 7b8e9b4bd..44ced7374 100644 --- a/manifests/plugins/sci/failure-missing-input-param.yml +++ b/manifests/plugins/sci/failure-missing-input-param.yml @@ -9,7 +9,7 @@ initialize: method: Sci path: "builtin" # global-config: - # functional-unit-time: 1 minute + # functional-unit: requests tree: children: child: diff --git a/manifests/plugins/sci/success.yml b/manifests/plugins/sci/success.yml index 15a224911..c7e6dbde6 100644 --- a/manifests/plugins/sci/success.yml +++ b/manifests/plugins/sci/success.yml @@ -2,14 +2,14 @@ name: sci description: successful path tags: initialize: - outputs: ['yaml'] + # outputs: ['yaml'] plugins: sci: kind: plugin method: Sci path: "builtin" - # global-config: - # functional-unit-time: 1 minute + global-config: + functional-unit: requests tree: children: child: @@ -25,4 +25,5 @@ tree: energy: 5 carbon-operational: 5 carbon-embodied: 0.02 + carbon: 5.02 requests: 100 diff --git a/src/__tests__/unit/builtins/sci.test.ts b/src/__tests__/unit/builtins/sci.test.ts index 365a71c3d..2b6261cda 100644 --- a/src/__tests__/unit/builtins/sci.test.ts +++ b/src/__tests__/unit/builtins/sci.test.ts @@ -6,7 +6,7 @@ const {InputValidationError} = ERRORS; describe('lib/sci:', () => { describe('Sci: ', () => { - const sci = Sci(); + const sci = Sci({'functional-unit': 'users'}); describe('init: ', () => { it('successfully initalized.', () => { @@ -19,13 +19,13 @@ describe('lib/sci:', () => { it('returns a result with valid inputs.', async () => { const sci = Sci({ 'functional-unit': 'users', - 'functional-unit-time': '1 min', }); const inputs = [ { timestamp: '2021-01-01T00:00:00Z', 'carbon-operational': 0.02, 'carbon-embodied': 5, + carbon: 5.02, users: 100, duration: 1, }, @@ -42,70 +42,31 @@ describe('lib/sci:', () => { carbon: 5.02, users: 100, duration: 1, - sci: 3.012, + sci: 0.050199999999999995, }, ]); }); - it('returns a result with vary input duration.', async () => { + it('returns the same result regardless of input duration.', async () => { const sci = Sci({ 'functional-unit': 'requests', - 'functional-unit-time': '1 day', }); const inputs = [ { timestamp: '2021-01-01T00:00:00Z', 'carbon-operational': 0.2, 'carbon-embodied': 0.05, - duration: 100, - }, - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - duration: 2, - }, - ]; - const result = await sci.execute(inputs); - - expect.assertions(1); - - expect(result).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.2, - 'carbon-embodied': 0.05, - carbon: 0.0025, - duration: 100, - sci: 216, - }, - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - carbon: 0.00125, - duration: 2, - sci: 108, + carbon: 0.205, + duration: 1, + requests: 10, }, - ]); - }); - - it('returns a result when `functional-unit-time` is not provided.', async () => { - const sci = Sci({ - 'functional-unit': 'requests', - }); - const inputs = [ { - timestamp: '2021-01-01T00:00:00Z', + timestamp: '2021-01-01T00:01:00Z', 'carbon-operational': 0.2, 'carbon-embodied': 0.05, + carbon: 0.205, duration: 100, - }, - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - duration: 2, + requests: 10, }, ]; const result = await sci.execute(inputs); @@ -117,59 +78,19 @@ describe('lib/sci:', () => { timestamp: '2021-01-01T00:00:00Z', 'carbon-operational': 0.2, 'carbon-embodied': 0.05, - carbon: 0.0025, - duration: 100, - sci: 0.0025, + carbon: 0.205, + duration: 1, + requests: 10, + sci: 0.020499999999999997, }, { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - carbon: 0.00125, - duration: 2, - sci: 0.00125, - }, - ]); - }); - - it('returns a result when `functional-unit` is not provided.', async () => { - const sci = Sci({ - 'functional-unit-time': '1 day', - }); - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.2, + timestamp: '2021-01-01T00:01:00Z', + carbon: 0.205, 'carbon-embodied': 0.05, - duration: 100, - }, - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - duration: 2, - }, - ]; - const result = await sci.execute(inputs); - - expect.assertions(1); - - expect(result).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', 'carbon-operational': 0.2, - 'carbon-embodied': 0.05, - carbon: 0.0025, duration: 100, - sci: 216, - }, - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - carbon: 0.00125, - duration: 2, - sci: 108, + requests: 10, + sci: 0.020499999999999997, }, ]); }); @@ -177,7 +98,6 @@ describe('lib/sci:', () => { it('throws exception on invalid functional unit data.', async () => { const sci = Sci({ 'functional-unit': 'requests', - 'functional-unit-time': 'bad-data', }); const inputs = [ { @@ -197,10 +117,9 @@ describe('lib/sci:', () => { } }); - it('throws an exception on negative time value.', async () => { + it('throws exception if functional unit value is not positive integer.', async () => { const sci = Sci({ 'functional-unit': 'requests', - 'functional-unit-time': '-1 hour', }); const inputs = [ { @@ -208,6 +127,7 @@ describe('lib/sci:', () => { 'carbon-operational': 0.002, 'carbon-embodied': 0.0005, duration: 1, + requests: -5, }, ]; @@ -219,211 +139,6 @@ describe('lib/sci:', () => { expect(error).toBeInstanceOf(InputValidationError); } }); - - it('throws exception on invalid time unit.', async () => { - const sci = Sci({ - 'functional-unit': 'requests', - 'functional-unit-time': '1 badData', - }); - - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - duration: 1, - }, - ]; - expect.assertions(1); - - try { - await sci.execute(inputs); - } catch (error) { - expect(error).toBeInstanceOf(InputValidationError); - } - }); - - it('returns a result when the value of `functional-unit-time` is separated by underscore.', async () => { - const sci = Sci({ - 'functional-unit': 'requests', - 'functional-unit-time': '2_d', - }); - - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - duration: 1, - }, - ]; - const result = await sci.execute(inputs); - - expect.assertions(1); - expect(result).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - carbon: 0.0025, - duration: 1, - sci: 432, - }, - ]); - }); - - it('returns a result when the value of `functional-unit-time` is separated by hyphen.', async () => { - const sci = Sci({ - 'functional-unit': 'requests', - 'functional-unit-time': '2-d', - }); - - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - duration: 1, - }, - ]; - const result = await sci.execute(inputs); - - expect.assertions(1); - expect(result).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - carbon: 0.0025, - duration: 1, - sci: 432, - }, - ]); - }); - - it('returns a result with the value of `functional-unit-time` from `node-config` and overwritting the value in the `global-config`.', async () => { - const sci = Sci({ - 'functional-unit': 'users', - 'functional-unit-time': '2-d', - }); - - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - duration: 1, - }, - ]; - const result = await sci.execute(inputs, { - sci: {'functional-unit': 'requests'}, - }); - - expect.assertions(1); - expect(result).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - carbon: 0.0025, - duration: 1, - sci: 432, - }, - ]); - }); - - it('throws an exception on bad string formatting (bad separator) in `functional-unit-time`.', async () => { - const sci = Sci({ - 'functional-unit': 'requests', - 'functional-unit-time': '1/d', - }); - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - duration: 1, - }, - ]; - - expect.assertions(1); - - try { - await sci.execute(inputs); - } catch (error) { - expect(error).toBeInstanceOf(InputValidationError); - } - }); - - it('throws an exception on bad string formatting (no separator) in `functional-unit-time`.', async () => { - const sci = Sci({ - 'functional-unit': 'requests', - 'functional-unit-time': '1hour', - }); - const inputs = [ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - - duration: 1, - }, - ]; - expect.assertions(1); - - try { - await sci.execute(inputs); - } catch (error) { - expect(error).toBeInstanceOf(InputValidationError); - } - }); - - it('returns a result either `carbon` or both of `carbon-operational` and `carbon-embodied` are in the input.', async () => { - const sci = Sci({ - 'functional-unit': 'requests', - 'functional-unit-time': '2-d', - }); - expect.assertions(2); - - const inputsWithCarbon = [ - { - timestamp: '2021-01-01T00:00:00Z', - carbon: 0.0025, - duration: 1, - }, - ]; - const resultWithCarbon = await sci.execute(inputsWithCarbon); - - expect(resultWithCarbon).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', - carbon: 0.0025, - duration: 1, - sci: 432, - }, - ]); - - const inputsWithoutCarbon = [ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - duration: 1, - }, - ]; - const resultWithoutCarbon = await sci.execute(inputsWithoutCarbon); - - expect(resultWithoutCarbon).toStrictEqual([ - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - carbon: 0.0025, - duration: 1, - sci: 432, - }, - ]); - }); }); }); }); diff --git a/src/builtins/sci/README.md b/src/builtins/sci/README.md index ee7608eb1..477f74dc0 100644 --- a/src/builtins/sci/README.md +++ b/src/builtins/sci/README.md @@ -6,34 +6,17 @@ ### Plugin global config -- `functional-unit`: the functional unit in which to express the carbon impact -- `functional-unit-time`: the time to be used for functional unit conversions, as a string composed of a value and a unit separated with a space, hyphen or underscore, e.g. `2 mins`, `5-days`, `3_years` +- `functional-unit`: the name of the functional unit in which to express the carbon impact (required) -### Plugin node config - -- `functional-unit`: the functional unit in which to express the carbon impact. If the property is also declared in the global config, it will be overridden. ### Inputs -either: - -- `carbon`: total carbon, i.e. sum of embodied and operational, in gCO2eq - -or both of - -- `carbon-operational`: carbon emitted during an application's operation in gCO2eq -- `carbon-embodied`: carbon emitted in a component's manufacture - and disposal in gCO2eq - -and: -- `timestamp`: a timestamp for the input -- `duration`: the amount of time, in seconds, that the input covers. +- `carbon`: total carbon in gCO2eq (required) +- `functional-unit`: whatever `functional-unit` you define in global config also has to be present in each input, for example if you provide `functional-unit: requests` in global config, `requests` must be present in your input data. ## Returns -- `carbon`: the total carbon, calculated as the sum of `carbon-operational` - and `carbon-embodied`, in gCO2eq - `sci`: carbon expressed in terms of the given functional unit ## Calculation @@ -41,64 +24,12 @@ and: SCI is calculated as: ```pseudocode -sci = carbon-operational + carbon-embodied / functional unit +sci = carbon / functional unit ``` -where `carbon-operational` is the product of `energy` and `grid-intensity`. -The SCI-guide represents this as - -```pseudocode -SCI = (E * I) + M per R -``` - -where -`E` = energy used in kWh, -`I` is grid intensity in gCO2e/kWh, -`M` is embodied carbon, and -`R` is the functional unit. - -SCI is the sum of the `carbon-operational` (calculated using the `sci-o` plugin) -and the `carbon-embodied` (calculated using the `sci-m` plugin). -It is then converted to some functional unit, for example for an API the -functional unit might be per request, or for a website -it might be per 1000 visits. ## IF Implementation -`sci` takes `carbon-operational` and `carbon-embodied` as inputs along -with two parameters related to the functional unit: - -- `functional-unit`: a string describing the functional unit to normalize - the SCI to. This must match a field provided in the `inputs` with - an associated value. - For example, if `functional-unit` is `requests` then there should be - a `requests` field in `inputs` with an associated value for - the number of requests per `functional-unit`. -- `functional-unit-time`: a time value and a unit as a single string. - E.g. `2 s`, `10 seconds`, `3 days`, `2 months`, `0.5 y`. - -In a plugin pipeline, time is always denominated in `seconds`. It is only in -`sci` that other units of time are considered. Therefore, if `functional-unit-time` -is `1 month`, then the sum of `carbon-operational` and `carbon-embodied` is -multiplied by the number of seconds in one month. - -Example: - -```yaml -carbon-operational: 0.02 // carbon-operational per s -carbon-embodied: 5 // carbon-embodied per s -functional-unit: requests // indicate the functional unit is requests -functional-unit-time: 1 minute // time unit is minutes -requests: 100 // requests per minute -``` - -```pseduocode -sci-per-s = carbon-operational + carbon-embodied / duration // (= 5.02) -sci-per-minute = sci-per-s * 60 // (= 301.2) -sci-per-functional-unit-time = sci-per-minute * number of minutes -sci-per-f-unit = sci-per-functional-unit-time / 100 // (= 3.012 gC/request) -``` - To run the plugin, you must first create an instance of `Sci`. Then, you can call `execute()` to return `sci`. ```typescript @@ -108,23 +39,18 @@ const sci = Sci({'functional-unit': 'requests'}); const results = await sci.execute( [ { - 'carbon-operational': 0.02, - 'carbon-embodied': 5, + 'carbon': 5' duration: 1, requests: 100, }, - ], - { - 'functional-unit-time': '1 day', - } + ] ); ``` ## Example manifest -IF users will typically call the plugin as part of a pipeline defined in a `manifest` -file. In this case, instantiating the plugin is handled by -`ie` and does not have to be done explicitly by the user. +IF users will typically call the plugin as part of a pipeline defined in a `manifest` file. In this case, instantiating the plugin is handled by `ie` and does not have to be done explicitly by the user. + The following is an example `manifest` that calls `sci`: ```yaml @@ -139,19 +65,16 @@ initialize: method: Sci path: 'builtin' global-config: - functional-unit-time: '5 minutes' + functional-unit: 'requests' tree: children: child: pipeline: - sci config: - sci: - functional-unit: requests # factor to convert per time to per f.unit inputs: - timestamp: 2023-07-06T00:00 - carbon-operational: 0.02 - carbon-embodied: 5 + carbon: 5 duration: 1 requests: 100 ``` @@ -160,7 +83,7 @@ You can run this example `manifest` by saving it as `./manifests/plugins/sci.yml ```sh npm i -g @grnsft/if -ie --manifest ./examples/manifests/test/sci.yml --output ./examples/outputs/sci.yml +ie --manifest manifests/plugins/sci.yml --output manifests/outputs/sci.yml ``` -The results will be saved to a new `yaml` file in `./examples/outputs`. +The results will be saved to a new `yaml` file. diff --git a/src/builtins/sci/index.ts b/src/builtins/sci/index.ts index 5c486d483..7a60db439 100644 --- a/src/builtins/sci/index.ts +++ b/src/builtins/sci/index.ts @@ -7,176 +7,81 @@ import {validate, allDefined} from '../../util/validations'; import {buildErrorMessage} from '../../util/helpers'; import {ERRORS} from '../../util/errors'; -import {TIME_UNITS_IN_SECONDS} from './config'; - const {InputValidationError} = ERRORS; -export const Sci = (globalConfig?: ConfigParams): ExecutePlugin => { +export const Sci = (globalConfig: ConfigParams): ExecutePlugin => { const errorBuilder = buildErrorMessage(Sci.name); const metadata = { kind: 'execute', }; /** - * Calculate the total emissions for a list of inputs. + * Validates node and gloabl configs. */ - const execute = ( - inputs: PluginParams[], - config?: ConfigParams - ): PluginParams[] => { - const mergedConfig = Object.assign({}, globalConfig, config); - const validatedConfigs = validateConfig(mergedConfig); + const validateConfig = (config?: ConfigParams) => { + const errorMessage = + '`functional-unit` should be provided in your global config'; - return inputs.map(input => { - const safeInput = validateInput(input); - const inputWithConfigs = Object.assign( - {}, - input, - safeInput, - validatedConfigs - ); + const schema = z + .object({ + 'functional-unit': z.string(), + }) + .refine(data => data['functional-unit'], { + message: errorMessage, + }); - return { - ...input, - ...tuneInput(inputWithConfigs), - }; - }); + return validate>(schema, config); }; /** - * Given an input, tunes it and returns the tuned input. + * Calculate the total emissions for a list of inputs. */ - const tuneInput = (input: PluginParams) => { - const sciPerSecond = calculateSciSeconds(input); - const factor = getFunctionalUnitConversionFactor(input); - - if (!input['functional-unit-time']) { + const execute = (inputs: PluginParams[]): PluginParams[] => { + return inputs.map(input => { + const safeInput = validateInput(input); + const sci = + safeInput['carbon'] > 0 + ? safeInput['carbon'] / input[globalConfig['functional-unit']] + : 0; return { - carbon: input['carbon'] ?? sciPerSecond, - sci: sciPerSecond / factor, + ...input, + sci, }; - } - - const functionalUnitTime = parseTime(input); - const sciTimed = convertSciToTimeUnit(sciPerSecond, functionalUnitTime); - const sciTimedDuration = sciTimed * functionalUnitTime.value; - - return { - carbon: input['carbon'] ?? sciPerSecond, - sci: sciTimedDuration / factor, - }; + }); }; /** - * Gets the conversion factor based on the functional unit specified in the input. - * If the 'functional-unit' exists in the input and is not 'none' or an empty string, - * returns the value; otherwise, defaults to 1. + * Checks for fields in input. */ - const getFunctionalUnitConversionFactor = (input: PluginParams): number => { - const functionalUnit = input['functional-unit']; - - return functionalUnit in input && - input[functionalUnit] !== 'none' && - input[functionalUnit] !== '' - ? input[functionalUnit] - : 1; - }; + const validateInput = (input: PluginParams) => { + const message = `'carbon' and ${globalConfig['functional-unit']} should be present in your input data.`; - /** - * Converts the given sci value from seconds to the specified time unit. - */ - const convertSciToTimeUnit = ( - sciPerSecond: number, - functionalUnitTime: {unit: string; value: number} - ): number => { - const conversionFactor = TIME_UNITS_IN_SECONDS[functionalUnitTime.unit]; + const validatedConfig = validateConfig(globalConfig); - if (!conversionFactor) { + if ( + !( + validatedConfig['functional-unit'] in input && + input[validatedConfig['functional-unit']] > 0 + ) + ) { throw new InputValidationError( errorBuilder({ - message: 'functional-unit-time is not in recognized unit of time', + message: + 'functional-unit value is missing from input data or it is not a positive integer', }) ); } - return sciPerSecond * conversionFactor; - }; - - /** - * Calculates sci in seconds for a given input. - */ - const calculateSciSeconds = (input: PluginParams): number => { - const operational = parseFloat(input['carbon-operational']); - const embodied = parseFloat(input['carbon-embodied']); - const sciPerSecond = (operational + embodied) / input['duration']; - - return 'carbon' in input - ? input['carbon'] / input['duration'] - : sciPerSecond; - }; - - /** - * Validates node and gloabl configs. - */ - const validateConfig = (config: ConfigParams) => { - const unitWarnMessage = - 'Please ensure you have provided one value and one unit and they are either space, underscore, or hyphen separated.'; - const errorMessage = - 'Either or both `functional-unit-time` and `functional-unit` should be provided'; - const schema = z .object({ - 'functional-unit-time': z - .string() - .regex(new RegExp('^[0-9][ _-][a-zA-Z]+$')) - .min(3, unitWarnMessage) - .optional(), - 'functional-unit': z.string().optional(), + carbon: z.number().gte(0), + duration: z.number().gte(1), }) - .refine(data => data['functional-unit'] || data['functional-unit-time'], { - message: errorMessage, - }); - - return validate>(schema, config); - }; - - /** - * Checks for fields in input. - */ - const validateInput = (input: PluginParams) => { - const message = - 'Either carbon or both of carbon-operational and carbon-embodied should be present.'; - - const schemaWithCarbon = z.object({ - carbon: z.number().gte(0), - duration: z.number().gte(1), - }); - - const schemaWithoutCarbon = z.object({ - 'carbon-operational': z.number().gte(0), - 'carbon-embodied': z.number().gte(0), - duration: z.number().gte(1), - }); - - const schema = schemaWithCarbon - .or(schemaWithoutCarbon) .refine(allDefined, {message}); return validate>(schema, input); }; - /** - * Parses the 'functional-unit-time' from the input and extracts the time value and unit. - * Updates the functionalUnitTime's unit and value properties accordingly. - */ - const parseTime = (input: PluginParams) => { - const splits = input['functional-unit-time'].split(/[-_ ]/); - return { - unit: splits[1], - value: parseFloat(splits[0]), - }; - }; - return { metadata, execute,