Skip to content

Commit

Permalink
Merge pull request #925 from Green-Software-Foundation/time-converter…
Browse files Browse the repository at this point in the history
…-plugin

Add `time-converter` plugin
  • Loading branch information
jmcook1186 authored Aug 9, 2024
2 parents cd19917 + e03b42c commit b0933d7
Show file tree
Hide file tree
Showing 6 changed files with 403 additions and 13 deletions.
24 changes: 24 additions & 0 deletions manifests/examples/builtins/time-converter/success.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: time-converter demo
description: successful path
tags:
initialize:
plugins:
time-converter:
method: TimeConverter
path: builtin
global-config:
input-parameter: "energy-per-year"
original-time-unit: "year"
new-time-unit: "duration"
output-parameter: "energy-per-duration"
tree:
children:
child:
pipeline:
- time-converter
config:
defaults:
energy-per-year: 10000
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
126 changes: 126 additions & 0 deletions src/__tests__/if-run/builtins/time-converter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {ERRORS} from '@grnsft/if-core/utils';

import {TimeConverter} from '../../../if-run/builtins/time-converter';

import {STRINGS} from '../../../if-run/config';

const {GlobalConfigError, InputValidationError} = ERRORS;
const {MISSING_GLOBAL_CONFIG} = STRINGS;

describe('builtins/time-converter: ', () => {
describe('TimeConverter: ', () => {
const globalConfig = {
'input-parameter': 'energy-per-year',
'original-time-unit': 'year',
'new-time-unit': 'duration',
'output-parameter': 'energy-per-duration',
};
const parametersMetadata = {
inputs: {},
outputs: {},
};
const timeConverter = TimeConverter(globalConfig, parametersMetadata);

describe('init: ', () => {
it('successfully initalized.', () => {
expect(timeConverter).toHaveProperty('metadata');
expect(timeConverter).toHaveProperty('execute');
});
});

describe('execute(): ', () => {
it('successfully applies TimeConverter strategy to given input.', () => {
expect.assertions(1);

const expectedResult = [
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'energy-per-year': 10000,
'energy-per-duration': 1.140795,
},
];

const result = timeConverter.execute([
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'energy-per-year': 10000,
},
]);

expect(result).toStrictEqual(expectedResult);
});

it('throws an error when global config is not provided.', () => {
const config = undefined;
const timeConverter = TimeConverter(config!, parametersMetadata);

expect.assertions(1);

try {
timeConverter.execute([
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'energy-per-year': 10000,
},
]);
} catch (error) {
expect(error).toStrictEqual(
new GlobalConfigError(MISSING_GLOBAL_CONFIG)
);
}
});

it('throws an error on missing params in input.', () => {
expect.assertions(1);

try {
timeConverter.execute([
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
},
]);
} catch (error) {
expect(error).toStrictEqual(
new InputValidationError(
'"energy-per-year" parameter is required. Error code: invalid_type.'
)
);
}
});

it('returns a result when `new-time-unit` is a different time unit than `duration`.', () => {
expect.assertions(1);
const newConfig = {
'input-parameter': 'energy-per-year',
'original-time-unit': 'year',
'new-time-unit': 'month',
'output-parameter': 'energy-per-duration',
};
const timeConverter = TimeConverter(newConfig, parametersMetadata);

const data = [
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'energy-per-year': 10000,
},
];
const response = timeConverter.execute(data);
const expectedResult = [
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'energy-per-year': 10000,
'energy-per-duration': 832.886522,
},
];

expect(response).toEqual(expectedResult);
});
});
});
});
1 change: 1 addition & 0 deletions src/if-run/builtins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export {CSVLookup} from './csv-lookup';
export {Shell} from './shell';
export {Regex} from './regex';
export {Copy} from './copy-param';
export {TimeConverter} from './time-converter';
export {TimeSync} from './time-sync';
136 changes: 136 additions & 0 deletions src/if-run/builtins/time-converter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
# Time Converter

`time-conversion` is a generic plugin for converting time values from a specified time unit to a new given time unit.

You provide the energy value, the time unit associated with this energy, and a new time unit to which you want to convert it.

For example, you could add `energy-per-year`, the time unit `year`, and the new time unit `duration`. The `energy-per-duration` would then be added to every observation in your input array as the converted value of `energy-per-year`, `year` and `duration`.

## Parameters

### Plugin config

These parameters are required in global config:

- `input-parameter`: a string that should match an existing key in the `inputs` array
- `original-time-unit`: a string that defines the time unit of the `input-parameter`. The original time unit should be a valid unit, like `year`, `month`, `day`, `hour` and so on
- `new-time-unit`: a string that defines the new time unit that the `input-parameter` value should be converted to. The time unit can be `duration`(in which case it grabs the value from the `duration` in the input), or can be other time unit like `second`, `month`, `day`, `week` and so on
- `output-parameter`: a string defining the name to use to add the result of converting the input parameter to the output array

### Plugin parameter metadata

The `parameter-metadata` section contains information about `description` and `unit` of the parameters of the inputs and outputs

- `inputs`: describe parameters of the `input-parameter` of the global config. Each parameter has:

- `description`: description of the parameter
- `unit`: unit of the parameter
- `aggregation-method`: the aggregation method of the parameter (can be `sum`, `avg` or `none`)

- `outputs`: describe the parameter of the `output-parameter` of the global config. The parameter has the following attributes:
- `description`: description of the parameter
- `unit`: unit of the parameter
- `aggregation-method`: the aggregation method of the parameter (can be `sum`, `avg` or `none`)

### Inputs

The `input-parameter` must be available in the input array.

## Returns

- `output-parameter`: the converted energy of the `input-parameter` with the parameter name defined by `output-parameter` in global config.

## Calculation

```pseudocode
output = input-parameter / original-time-unit * new-time-unit
```

## Implementation

To run the plugin, you must first create an instance of `TimeConverter`. Then, you can call `execute()`.

```typescript
const config = {
'input-parameter': 'energy-per-year',
'original-time-unit': 'year',
'new-time-unit': 'duration',
'output-parameter': 'energy-per-duration',
};

const timeConverter = TimeConverter(config, parametersMetadata);
const result = timeConverter.execute([
{
timestamp: '2021-01-01T00:00:00Z',
duration: 3600,
'energy-per-year': 10000,
},
]);
```

## 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 and does not have to be done explicitly by the user. The following is an example manifest that calls `time-coverstion`:

```yaml
name: time-coverstion demo
description:
tags:
initialize:
plugins:
time-converter:
method: TimeConverter
path: builtin
global-config:
input-parameter: 'energy-per-year'
original-time-unit: 'year'
new-time-unit: 'duration'
output-parameter: 'energy-per-duration'
tree:
children:
child:
pipeline:
- time-converter
config:
defaults:
energy-per-year: 10000
inputs:
- timestamp: 2023-08-06T00:00
duration: 3600
```
You can run this example by saving it as `./examples/manifests/time-coverstion.yml` and executing the following command from the project root:

```sh
if-run --manifest ./examples/manifests/time-coverstion.yml --output ./examples/outputs/time-coverstion.yml
```

The results will be saved to a new `yaml` file in `./examples/outputs`.

## Errors

`TimeConverter` exposes two of the IF error classes.

### GlobalConfigError

You will receive an error starting `GlobalConfigError: ` if you have not provided the expected configuration data in the plugin's `initialize` block.

The required parameters are:

- `input-parameter`: this must be a string, which is the name of parameter in the `inputs` array
- `original-time-unit`: this must be a string of time units (`minutes`, `seconds` and so on)
- `new-time-unit`: this must be a string of time units (e.g. `duration`, `minutes`, `seconds` and so on)
- `output-parameter`: this must be a string

You can fix this error by checking you are providing valid values for each parameter in the config.

### `MissingInputDataError`

This error arises when a necessary piece of input data is missing from the `inputs` array.
Every element in the `inputs` array must contain:

- `timestamp`
- `duration`
- whatever values you passed to `input-parameter`

For more information on our error classes, please visit [our docs](https://if.greensoftware.foundation/reference/errors).
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ export const TIME_UNITS_IN_SECONDS: Record<string, number> = {
wks: 604800,
week: 604800,
weeks: 604800,
m: 2419200,
mnth: 2419200,
mth: 2419200,
mnths: 2419200,
mths: 2419200,
month: 2419200,
months: 2419200,
y: 31536000,
ys: 31536000,
yr: 31536000,
yrs: 31536000,
year: 31536000,
years: 31536000,
m: 2628336,
mnth: 2628336,
mth: 2628336,
mnths: 2628336,
mths: 2628336,
month: 2628336,
months: 2628336,
y: 31556952,
ys: 31556952,
yr: 31556952,
yrs: 31556952,
year: 31556952,
years: 31556952,
};
Loading

0 comments on commit b0933d7

Please sign in to comment.