# SPG Drycooling Field Services

This is a collection of utils and tools for use with the SPG Asset Database.

## Install

```bash
npm install -S @spg-drycooling/field-services
```

or

```bash
yarn add @spg-drycooling/field-services
```

## Usage

The field services packages exposes a collection of tools that can be imported from the main entry point:

```typescript
import { FieldCalculcator, IUnit } from '@spg-drycooling/field-services';
```

<div style="page-break-after: always;"></div>

## Core concepts

### Supported units

The tools included in this package support a wide range of unit types and units:

| UnitType  | Base unit | Imperial | Metric (SI) | Other |
|-----------|-----------|----------|-------------|-------|
| Acceleration | g-force | | m/s2, g-force | |
| Angle | deg | | deg, rad, grad, arcmin, arcsec | |
| ApparentPower | VA | | VA, mVA, kVA, MVA, GVA | |
| Area | m2 | in2, ft2, ac, mi2, yd2 | mm2, cm2, m2, ha, km2 | |
| Charge | c | | c, mC, μC, nC, pC | |
| Current | A | | A, mA, kA | |
| Density | kg/m3 | | kg/m3 | |
| Each | ea | | / | ea, dz |
| Energy | J | | Wh, mWh, kWh, MWh, GWh, J, kJ | |
| Force | N | lbf | N, kN | |
| Frequency | Hz | | Hz, mHz, kHz, MHz, GHz, THz, rpm, deg/s, rad/s | |
| Generic | / | | / | wu, ul, %, kWe, W/m2K, kJ/kg, bar(g)+FV, bool |
| Illuminance | lx | ft-cd | lx | |
| Length | m | in, ft-us, ft, mi, yd, nMi | mm, cm, m, km | fathom |
| Mass | g | oz, lb, t | mcg, mg, g, kg, mt |
| MassFlowRate | kg/s | lb/s | kg/s | |
| Pace | s/m | s/ft, min/mi | s/m, min/km | |
| PartsPer | ppm | | / | ppm, ppb, ppt, ppq |
| Power | W | | W, kW, mW, kM, MW, GW | |
| Pressure | kPa | psi, ksi | Pa, hPa, kPa, MPa, bar, mbar | torr |
| ReactiveEnergy | VARh | | VARh, mVARh, kVARh, MVARh, GVARh | |
| ReactivePower | VAR | | VARh, mVARh, kVARh, MVARh, GVARh | |
| Sound | db(A) | | dB(A) | dB(L) | |
| Speed | km/h | ft/s, knot | m/s, km/h, m/h | |
| Temperature | C | F | C, K | R |
| Time | s | | ns, mu, ms, s, min, h, d, week, month, year | |
| Voltage | V | | V, mV, kV | |
| Volume | l | | tsp, Tbs, in3, fl-oz, cup, pnt, qt, gal, ft3, yd3 | mm3, cm3, ml, l, kl, m3, km3, cl, dl | |
| VolumeFlowRate | l/s | tsp/s, Tbs/s, in3/s, in3/min, in3/h, fl-oz/s, fl-oz/min, fl-oz/h, cup/s, pnt/s, pnt/min, pnt/h, qt/s, gal/s, gal/min, gal/h, ft3/s, ft3/min, ft3/h, yd3/s, yd3/min, yd3/h | mm3/s, cm3/s, ml/s, cl/s, dl/s, l/s, l/min, l/h, kl/s, kl/min, kl/h, m3/s, m3/min, m3/h, km3/s | |

#### Good to know

* `Generic` is a special case, this unit type contains all units that cannot be converted and should be used *as is*.
* All units displayed in the table can be converted to any other unit within their unit type.
* New units & unit types can be added, provided the correct conversion rules are implemented.

<div style="page-break-after: always;"></div>

### Formula

Fields are calculated and validation from formulas. A formula consists of 3 parts:

1. source: the lefthand value in the operation
2. operator: the operator to use
3. target: the righthand side in the operation

The `source` and `target` can be a new formula as well, allowing the recursive nesting of formulas.

#### Formula segment

The source and target properties are formula segments. They can contain 1 of 4 things:

1. a **value**: concrete value to use in the calculcations/validations
2. a **field ID**: a unique key that maps to a field provided in the fields map (see calculcation below)
3. a **formula**: a nested formula that results in a value
3. a **datalist**: an object containing info to map to a value provided in the datalistItems property

#### Available operators

Operations are always converted to a formula with a left- and righthand side.

E.g.:

```
10^3
```

will be seen as

```
10 POW 3
```

These operators are available:

* `ABS`: calculcate absolute value
* `ADD`: add the target to the source
* `DIVIDE`: divide the source by the target
* `MAX`: return the highest value between the source and the target
* `MIN`: return the lowest value between the source and the target
* `MODULUS`: return the remainder after dividing the source by the target
* `MULTIPLY`: multiply the source with the target
* `NOPE`: ignore this operation, return the source
* `POW`: exponentiate the source with the target
* `ROUND`: round the source to the number of decimal places as defined by the target (negative values get rounded to the left of the decimal point)
* `SUBTRACT`: subtract the target from the source

#### Examples

**Simple division:**

```json
{
  "source": {
    "value": 10
  },
  "operator": "DIVIDE",
  "target": {
    "value": 5
  }
}
```

Would translate to:

```plaintext
10 / 5
```

**Power:**

Formula:

```json
{
  "source": {
    "value": 15
  },
  "operator": "POW",
  "target": {
    "value": 2
  }
}
```

Would translate to:

```plaintext
15^2
```

**Nested formula:**

```json
{
  "source": [
    {
      "source": {
        "value": 15
      },
      "operator": "POW",
      "target": {
        "value": 2
      }
    }
  ],
  "operator": "SUBTRACT",
  "target": [
    {
      "source": [
        {
          "source": {
            "value": 3
          },
          "operator": "MULTIPLY",
          "target": {
            "value": 0.9
          }
        }
      ],
      "operator": "ROUND",
      "target": {
        "value": 2
      }
    }
  ]
}
```

Would translate to:

```plaintext
15^2 - ROUND(3 * 0.9, 2)
```

<div style="page-break-after: always;"></div>

### Validation rules

Validation rules follow the same syntax and rules as the calculation formulas. Instead of an operator, they use a comparator.

E.g.:

```json
{
  "source": {
    "value": 25
  },
  "comparator": "LTE",
  "target": {
    "value": 99
  }
}
```

#### Available comparators

* `EQ`: resolve if source is equal to target
* `GT`: resolve if source is greater than target
* `GTE`: resolve if source is greater than or equal to target
* `IN`: resolve if source is included in target (list)
* `LT`: resolve if source is less than target
* `LTE`: resolve if source is less than or equal to target
* `NE`: resolve if source is not equal to target
* `NIN`: resolve if source is not included in target (list)

<div style="page-break-after: always;"></div>

## Field Calculator

The `FieldCalculator` is used to calculcate a field value from a provided formula and values map.

It exposes 3 methods:

### mapOperation

Provided an operator, return a function to run the operation on a source and target. Used internally to map operators to operation methods.

```typescript
const operator = FieldCalculator.mapOperation("SUBTRACT");

return operator(2, 5); // return value: 7
```

### mapValue

Provided a field, formula segment (e.g. source) and fields map, return the resulting value. Used internally in the calculcate method to allow recursive calculcation.

```typescript
const field = { code: "FIELD", nominalValue: 2.4 };
const segment = {
  source: {
    value: 4
  },
  operator: "MULTIPLY",
  target: {
    fieldId: "TARGET_FIELD"
  }
};
const fields: {
  TARGET_FIELD: {
    currentValue: 0.3
  }
};

const value = FieldCalculator.mapValue(field, segment, fields); // return value: 1.2
```

### calculcate

Provided a field, formula and fields map, return the calculcated value.

```typescript
const field = { code: "FIELD", nominalValue: 2.4 };
const formula = {
  source: [{
    source: {
      value: 4
    },
    operator: "MULTIPLY",
    target: {
      fieldId: "TARGET_FIELD"
    }
  }],
  operator: ROUND,
  target: {
    value: 2
  }
};
const fields: {
  TARGET_FIELD: {
    currentValue: 0.3
  }
};

const value = FieldCalculator.calculate(field, formula, fields); // return value: 1.20
```

<div style="page-break-after: always;"></div>

## Field Validator

The `FieldValidator` is used to validate a field value from a provided validation rule and values map.

It exposes 3 methods:

### mapComparator

Provided an comparator, return a function to run the comparison on a source and target. Used internally to map comparators to comparison methods.

```typescript
const comparator = FieldValidator.mapComparator("EQ");

return comparator(2, 5); // return value: false
```

### mapValue

Provided a formula segment (e.g. source) and fields map, return the resulting value. Used internally in the validate method to allow recursive validation.

```typescript
const segment = {
  fieldId: "TARGET_FIELD"
};
const fields: {
  TARGET_FIELD: {
    currentValue: 9
  }
};

const value = FieldCalculator.mapValue(segment, fields); // return value: 9
```

### validate

Provided a field, validation rule and fields map, return the resulting valid state.

```typescript
const field = { code: "FIELD", nominalValue: 2.4 };
const validation = {
  source: {
    value: 4
  },
  comparator: "GTE",
  target: {
    fieldId: "TARGET_FIELD"
  }
};
const fields: {
  TARGET_FIELD: {
    currentValue: 0.3
  }
};

const value = FieldValidator.validate(field, validation, fields); // return value: true
```

<div style="page-break-after: always;"></div>


## Unit Converter

The unitConverter is capable of **converting a value** between the known units of a unit type, as well as returning **available unit types and units** based on a provided unit.

### Definitions

Each unit type (measure) has defined a **conversion rule** for each of its units. Based on these conversion rules, values can be converted from one unit to another.

A unit conversion definition can define units for multiple systems (e.g.: SI, US). For each system, a **base unit** is defined, providing a **ratio** to be used for conversion between systems.

E.g.:

```typescript
const length: IUnitConversionDefinition = {
  metric: {
    mm: {
      name: {
        singular: 'Millimeter',
        plural: 'Millimeters',
      },
      baseUnitFactor: 1 / 1000,
    },
    m: {
        name: {
        singular: 'Meter',
        plural: 'Meters',
        },
        baseUnitFactor: 1,
    }
  },
  imperial: {
    ft: {
      name: {
        singular: 'Foot',
        plural: 'Feet',
      },
      baseUnitFactor: 1,
    },
  }
  baseUnits: {
    metric: {
      unit: 'm',
      ratio: 3.2808399,
  },
  imperial: {
      unit: 'ft',
      ratio: 0.3048,
    },
  }
};
```

### Convert units within a measure

```typescript
const result = unitConverter.convert(10, { from: 'mm', to: 'ft' });

// return value: 10000g
```

### Get a list of all available units

```typescript
const result = unitConverter.getUnits();

/**
 *
 * result = [{
 *  unitSymbol: 'kg',
 *  measure: 'mass',
 *  system: 'metric',
 *  unit: {
 *    name: {
 *      singular: 'Kilogram';
        plural: 'Kilograms';
 *    },
 *    baseUnitFactor: 1000
 *  }
 * }]
 */
```

### Get a list of all available units from a measure

```typescript
const result = unitConverter.getUnitsByMeasure(UnitType.Mass, MeasurementSystems.Metric);

/**
 *
 * result = [{
 *   unitSymbol: 'kg',
 *  measure: 'mass',
 *  system: 'metric',
 *  unit: {
 *    name: {
 *      singular: 'Kilogram';
        plural: 'Kilograms';
 *    },
 *    baseUnitFactor: 1000
 *  }
 * }, ...]
 */
```

### Get a unit

```typescript
const result = unitConverter.getUnit(Units.kg);

/**
 *
 * result = {
 *  unitSymbol: 'kg',
 *  measure: 'mass',
 *  system: 'metric',
 *  unit: {
 *    name: {
 *      singular: 'Kilogram';
        plural: 'Kilograms';
 *    },
 *    baseUnitFactor: 1000
 *  }
 * }
 */
```

### Get the base unit

```typescript
const result = unitConverter.getBaseUnit(Units.kg);

/**
 *
 * result = {
 *  unitSymbol: 'g',
 *  measure: 'mass',
 *  system: 'metric',
 *  unit: {
 *    name: {
 *       singular: 'Gram';
         plural: 'Grams';
 *    },
 *    baseUnitFactor: 1
 *  }
 * }
 */
```

### Get the unit type for a unit

```typescript
const result = unitConverter.getMeasure(Units.kg);

/**
 *
 * result = 'Mass'
 */
```
