From 62dacb7dea604279c1abc528c2a11ec60ac225d7 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Wed, 29 May 2024 11:37:33 +0100 Subject: [PATCH 01/11] fix(lib): update readme for sci --- src/builtins/sci/README.md | 101 +++++-------------------------------- 1 file changed, 12 insertions(+), 89 deletions(-) diff --git a/src/builtins/sci/README.md b/src/builtins/sci/README.md index ee7608eb1..66582ece6 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. From 92f8b4bb962020aab40feb94c7723a4b908e1d03 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Wed, 29 May 2024 11:38:13 +0100 Subject: [PATCH 02/11] fix(lib): simplify source code for sci --- src/builtins/sci/index.ts | 170 +++++++++----------------------------- 1 file changed, 39 insertions(+), 131 deletions(-) diff --git a/src/builtins/sci/index.ts b/src/builtins/sci/index.ts index 5c486d483..8abaa708a 100644 --- a/src/builtins/sci/index.ts +++ b/src/builtins/sci/index.ts @@ -7,176 +7,84 @@ 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); + let sci = 0; + if (safeInput['carbon'] > 0) { + sci = safeInput['carbon'] / input[globalConfig['functional-unit']]; + } 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. + * Converts the given sci value from seconds to the specified time unit. */ - const getFunctionalUnitConversionFactor = (input: PluginParams): number => { - const functionalUnit = input['functional-unit']; - - return functionalUnit in input && - input[functionalUnit] !== 'none' && - input[functionalUnit] !== '' - ? input[functionalUnit] - : 1; - }; + // const convertSciToTimeUnit = ( + // sciPerSecond: number, + // functionalUnitTime: { unit: string; value: number } + // ): number => { + // const conversionFactor = TIME_UNITS_IN_SECONDS[functionalUnitTime.unit]; /** - * Converts the given sci value from seconds to the specified time unit. + * Checks for fields in input. */ - const convertSciToTimeUnit = ( - sciPerSecond: number, - functionalUnitTime: {unit: string; value: number} - ): number => { - const conversionFactor = TIME_UNITS_IN_SECONDS[functionalUnitTime.unit]; + const validateInput = (input: PluginParams) => { + const message = `'carbon' and ${globalConfig['functional-unit']} should be present in your input data.`; - if (!conversionFactor) { + const validatedConfig = validateConfig(globalConfig); + + if (!(validatedConfig['functional-unit'] in input)) { throw new InputValidationError( errorBuilder({ - message: 'functional-unit-time is not in recognized unit of time', + message: 'functional-unit value is missing from input data', }) ); } - 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, From e45733124ee26df1a9ba6f937913f5ec1efd2418 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Wed, 29 May 2024 11:38:40 +0100 Subject: [PATCH 03/11] fix(lib): update unit tetss for sci --- src/__tests__/unit/builtins/sci.test.ts | 344 ++---------------------- 1 file changed, 18 insertions(+), 326 deletions(-) diff --git a/src/__tests__/unit/builtins/sci.test.ts b/src/__tests__/unit/builtins/sci.test.ts index 365a71c3d..66f7b5b6d 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, + carbon: 0.205, + duration: 1, + requests: 10, }, { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - carbon: 0.00125, - duration: 2, - sci: 108, - }, - ]); - }); - - 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, - }, - { - timestamp: '2021-01-01T00:00:00Z', - 'carbon-operational': 0.002, - 'carbon-embodied': 0.0005, - carbon: 0.00125, - duration: 2, - sci: 0.00125, + carbon: 0.205, + duration: 1, + requests: 10, + sci: 0.020499999999999997, }, - ]); - }); - - 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,78 +98,7 @@ 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 = [ - { - 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 negative time value.', async () => { - const sci = Sci({ - 'functional-unit': 'requests', - 'functional-unit-time': '-1 hour', - }); - 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 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', @@ -257,118 +107,7 @@ describe('lib/sci:', () => { 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 { @@ -377,53 +116,6 @@ describe('lib/sci:', () => { 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, - }, - ]); - }); }); }); }); From ea1ab6b238bb4ec8c619f9abffc25f1d575c7ce2 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Wed, 29 May 2024 11:40:40 +0100 Subject: [PATCH 04/11] fix(lib): update plugin manifests --- manifests/plugins/sci/failure-invalid-config-value.yml | 3 +-- manifests/plugins/sci/failure-missing-input-param.yml | 2 +- manifests/plugins/sci/success.yml | 7 ++++--- 3 files changed, 6 insertions(+), 6 deletions(-) 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 From c3193561eca85fa981ce1c273ab1fc236dc3c684 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Wed, 29 May 2024 11:42:37 +0100 Subject: [PATCH 05/11] fix(lib): update example manifests with new sci --- manifests/examples/nesting.yml | 31 +++++++++++++++++-- manifests/examples/pipeline-teads-sci.yml | 14 +++++++-- .../examples/pipeline-with-aggregate.yml | 21 +++++++++++-- manifests/examples/pipeline-with-mocks.yml | 21 +++++++++++-- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/manifests/examples/nesting.yml b/manifests/examples/nesting.yml index 589c59723..9f789b4ac 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: "@grnsft/if-plugins" + 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 621b6b196..15f154834 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: "@grnsft/if-plugins" + 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..35300f3d4 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: "@grnsft/if-plugins" + 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 2ba7ed40d..42f273d76 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: "@grnsft/if-plugins" + 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 From d512dd39379d8a77bd12fe82dcf7f2a07e3b7e53 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 31 May 2024 09:13:06 +0100 Subject: [PATCH 06/11] fix(lib): address review comments - refactor conditional statement and remove comment --- src/builtins/sci/index.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/builtins/sci/index.ts b/src/builtins/sci/index.ts index 8abaa708a..0b431e173 100644 --- a/src/builtins/sci/index.ts +++ b/src/builtins/sci/index.ts @@ -39,10 +39,10 @@ export const Sci = (globalConfig: ConfigParams): ExecutePlugin => { const execute = (inputs: PluginParams[]): PluginParams[] => { return inputs.map(input => { const safeInput = validateInput(input); - let sci = 0; - if (safeInput['carbon'] > 0) { - sci = safeInput['carbon'] / input[globalConfig['functional-unit']]; - } + const sci = + safeInput['carbon'] > 0 + ? safeInput['carbon'] / input[globalConfig['functional-unit']] + : 0; return { ...input, sci, @@ -50,15 +50,6 @@ export const Sci = (globalConfig: ConfigParams): ExecutePlugin => { }); }; - /** - * 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]; - /** * Checks for fields in input. */ From c36bd6ccc50b35a4bc7c84a3ea6afb8191b68a57 Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 31 May 2024 12:52:20 +0100 Subject: [PATCH 07/11] Update manifests/examples/nesting.yml Signed-off-by: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> --- manifests/examples/nesting.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifests/examples/nesting.yml b/manifests/examples/nesting.yml index 9f789b4ac..f585a6a8e 100644 --- a/manifests/examples/nesting.yml +++ b/manifests/examples/nesting.yml @@ -38,7 +38,7 @@ initialize: global-config: functional-unit: "requests" "sum-carbon": - path: "@grnsft/if-plugins" + path: "builtin" method: Sum global-config: input-parameters: From 98d6db30907545589237bc5661922ca99c9296a5 Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 31 May 2024 12:52:26 +0100 Subject: [PATCH 08/11] Update src/builtins/sci/README.md Signed-off-by: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> --- src/builtins/sci/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builtins/sci/README.md b/src/builtins/sci/README.md index 66582ece6..477f74dc0 100644 --- a/src/builtins/sci/README.md +++ b/src/builtins/sci/README.md @@ -39,7 +39,7 @@ const sci = Sci({'functional-unit': 'requests'}); const results = await sci.execute( [ { - 'carbon': 5', + 'carbon': 5' duration: 1, requests: 100, }, From b5439618e6d5e19ae1ea4cc5eb57dcb9c6819f63 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 31 May 2024 13:06:15 +0100 Subject: [PATCH 09/11] feat(lib): add check that functional unit value is +ve int --- src/builtins/sci/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/builtins/sci/index.ts b/src/builtins/sci/index.ts index 0b431e173..7a60db439 100644 --- a/src/builtins/sci/index.ts +++ b/src/builtins/sci/index.ts @@ -58,10 +58,16 @@ export const Sci = (globalConfig: ConfigParams): ExecutePlugin => { const validatedConfig = validateConfig(globalConfig); - if (!(validatedConfig['functional-unit'] in input)) { + if ( + !( + validatedConfig['functional-unit'] in input && + input[validatedConfig['functional-unit']] > 0 + ) + ) { throw new InputValidationError( errorBuilder({ - message: 'functional-unit value is missing from input data', + message: + 'functional-unit value is missing from input data or it is not a positive integer', }) ); } From d4c8abfed9a8c310304d50daaaf1018b90d22c30 Mon Sep 17 00:00:00 2001 From: jmc <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 31 May 2024 13:08:02 +0100 Subject: [PATCH 10/11] feat(lib): add unit test to cover negative functional unit value --- src/__tests__/unit/builtins/sci.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/__tests__/unit/builtins/sci.test.ts b/src/__tests__/unit/builtins/sci.test.ts index 66f7b5b6d..2b6261cda 100644 --- a/src/__tests__/unit/builtins/sci.test.ts +++ b/src/__tests__/unit/builtins/sci.test.ts @@ -116,6 +116,29 @@ describe('lib/sci:', () => { expect(error).toBeInstanceOf(InputValidationError); } }); + + it('throws exception if functional unit value is not positive integer.', async () => { + const sci = Sci({ + 'functional-unit': 'requests', + }); + const inputs = [ + { + timestamp: '2021-01-01T00:00:00Z', + 'carbon-operational': 0.002, + 'carbon-embodied': 0.0005, + duration: 1, + requests: -5, + }, + ]; + + expect.assertions(1); + + try { + await sci.execute(inputs); + } catch (error) { + expect(error).toBeInstanceOf(InputValidationError); + } + }); }); }); }); From 188e5e7d6d576912e195325cafa14ca851bc0430 Mon Sep 17 00:00:00 2001 From: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> Date: Fri, 31 May 2024 14:07:59 +0100 Subject: [PATCH 11/11] Apply suggestions from code review Signed-off-by: Joseph Cook <33655003+jmcook1186@users.noreply.github.com> --- manifests/examples/pipeline-teads-sci.yml | 2 +- manifests/examples/pipeline-with-aggregate.yml | 2 +- manifests/examples/pipeline-with-mocks.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifests/examples/pipeline-teads-sci.yml b/manifests/examples/pipeline-teads-sci.yml index 8c8499c61..01c491cdc 100644 --- a/manifests/examples/pipeline-teads-sci.yml +++ b/manifests/examples/pipeline-teads-sci.yml @@ -28,7 +28,7 @@ initialize: global-config: functional-unit: "component" "sum-carbon": - path: "@grnsft/if-plugins" + path: "builtin" method: Sum global-config: input-parameters: diff --git a/manifests/examples/pipeline-with-aggregate.yml b/manifests/examples/pipeline-with-aggregate.yml index 35300f3d4..bd3e637ac 100644 --- a/manifests/examples/pipeline-with-aggregate.yml +++ b/manifests/examples/pipeline-with-aggregate.yml @@ -27,7 +27,7 @@ initialize: global-config: functional-unit: "requests" "sum-carbon": - path: "@grnsft/if-plugins" + path: "builtin" method: Sum global-config: input-parameters: diff --git a/manifests/examples/pipeline-with-mocks.yml b/manifests/examples/pipeline-with-mocks.yml index 26b7f0021..008e319b1 100644 --- a/manifests/examples/pipeline-with-mocks.yml +++ b/manifests/examples/pipeline-with-mocks.yml @@ -40,7 +40,7 @@ initialize: path: "@grnsft/if-plugins" method: SciO "sum-carbon": - path: "@grnsft/if-plugins" + path: "builtin" method: Sum global-config: input-parameters: