vehicle/running/costs/components/fuelandelectric/ppm.js

/**
 * Calculate journey, fuel and electric ppm, total mileages
 *
 * @module
 * @since 0.1.21
 */

import { requiredKeys } from '../../../../../keys/required';
import { getMPG, getBatteryRangeMiles } from '../../../util';
import { roundToDP } from '../../../../../maths/util';
import { removeVAT } from '../../../../../calculations/vat';
import { hydrogenKgToLitres, kmToMiles } from '../../../../../maths/conversions';
import {
    ratio,
    litresUsedPerMile,
    costForFullCharge,
    costForFullChargePPM,
    applyRatio,
    costForICEPPM,
} from './calculate';

/**
 *
 * @returns {struct}
*/
const checkRequiredKeys = (skv_config, required_keys) => {
    const rst_required_keys = requiredKeys(skv_config, required_keys);
    // check to see if any keys missing
    if (
        !rst_required_keys.success
        || rst_required_keys.data.length > 0
    ) {
        let missing_keys_msg = '';

        if (rst_required_keys.data.length > 0) {
            missing_keys_msg = ` // Missing required keys: ${rst_required_keys.data}`;
        }

        throw new Error(` ${rst_required_keys.msg}${missing_keys_msg} for running.vehicle.journey`);
    }
};

/**
 * petrol/diesel or hydrogen can be used in calculation so switch here
 * @param {struct} skv_config including ice_pp_litre or hydrogen_pp_kg
 * @returns {number}
 */
const getICEPPLitre = (skv_config) => {
    let num_return;

    // if ice_pp_litre (petrol/diesel) is provided then use that
    if (
        Object.prototype.hasOwnProperty.call(skv_config, 'ice_pp_litre')
        && skv_config.ice_pp_litre > 0
    ) {
        return skv_config.ice_pp_litre;
    }

    // if hydrogen_pp_kg (hydrogen) is provided then use that and convert to litres
    if (
        Object.prototype.hasOwnProperty.call(skv_config, 'hydrogen_pp_kg')
        && skv_config.hydrogen_pp_kg > 0
    ) {
        // 1 kg of hydrogen is 14.128 litres
        num_return = hydrogenKgToLitres(skv_config.hydrogen_pp_kg);
    }

    return num_return;
};

/**
 * calculate vehicle ICE cost
 * @param {struct} skv_config ice_pp_litre, mpg, vat_percentage
 * @returns {struct}
 */
const getICEPPM = (skv_config) => {
    const required_keys = ['mpg', 'ice_pp_litre|hydrogen_pp_kg', 'vat_percentage'];
    checkRequiredKeys(skv_config, required_keys);

    const skv_return = {
        exc_vat: 0,
        inc_vat: 0,
    };

    // if mpg_user is provided then use that, otherwise use mpg which is the official figure
    const mpg = Object.prototype.hasOwnProperty.call(skv_config, 'mpg_user')
        ? skv_config.mpg_user : skv_config.mpg;

    const ltr_used_per_mile = litresUsedPerMile(mpg, 1);

    // handle petrol/diesel or hydrogen pp litre
    const ice_pp_litre = getICEPPLitre(skv_config);

    // calculate cost per mile
    const cost_ppm = costForICEPPM(ice_pp_litre, ltr_used_per_mile);

    // apply VAT
    skv_return.inc_vat = roundToDP(cost_ppm, 2);
    skv_return.exc_vat = removeVAT(skv_return.inc_vat, skv_config.vat_percentage);
    skv_return.exc_vat = roundToDP(skv_return.exc_vat, 2);

    return skv_return;
};

/**
 * calculate Battery Range Cost PPM
 * @param {struct} skv_config battery_range_miles_user, battery_range_miles, battery_total_capacity_kwh, elec_pp_kwh
 * @returns {number}
 */
const getBatteryRangeCostPPM = (skv_config) => {
    // ensure we have a user provided battery range defaulting to the official battery range
    const battery_range_miles_user = Object.prototype.hasOwnProperty.call(skv_config, 'battery_range_miles_user')
        ? skv_config.battery_range_miles_user : skv_config.battery_range_miles;
    // calculate the user provided battery range as a ratio of the official battery range
    const battery_range_ratio = ratio(skv_config.battery_range_miles, battery_range_miles_user);
    // cost for full charge
    const cost_for_full_charge = costForFullCharge(skv_config.battery_total_capacity_kwh, skv_config.elec_pp_kwh);
    // cost per mile
    let cost_ppm = costForFullChargePPM(cost_for_full_charge, skv_config.battery_range_miles);
    cost_ppm = applyRatio(cost_ppm, battery_range_ratio);
    return cost_ppm;
};

/**
 * calculate Battery Efficiency Cost PPM
 * @param {struct} skv_config electric_energy_consumption, elec_pp_kwh
 * @returns {number}
 */
const getEfficiencyCostPPM = (skv_config) => {
    // Set to per km instead of per 100km
    const elec_energy_consumption_kwh_per_km = skv_config.electric_energy_consumption / 100;
    // convert to miles. convert using equivalance flag on kmToMiles function
    const elec_energy_consumption_kwh_per_mile = kmToMiles(elec_energy_consumption_kwh_per_km, true);
    // cost per mile
    const cost_ppm = applyRatio(elec_energy_consumption_kwh_per_mile, skv_config.elec_pp_kwh);
    return cost_ppm;
};

/**
 * calculate vehicle electric cost
 * @param {struct} skv_config elec_pp_kwh, battery_range_miles, battery_total_capacity_kwh, vat_percentage
 * @returns {struct}
 */
const getElectricPPM = (skv_config) => {
    let required_keys = ['battery_range_miles', 'battery_total_capacity_kwh', 'elec_pp_kwh', 'vat_percentage'];
    if (skv_config.method === 'efficiency') {
        required_keys = ['electric_energy_consumption', 'elec_pp_kwh', 'vat_percentage'];
    }
    checkRequiredKeys(skv_config, required_keys);

    const skv_return = {
        exc_vat: 0,
        inc_vat: 0,
    };

    // apply the ratio to factor in a user provided value if provided
    let cost_ppm = '';
    // If we're expanding this in future change to switch
    // however only 2 cases currently, range and efficiency
    if (skv_config.method !== 'efficiency') {
        cost_ppm = getBatteryRangeCostPPM(skv_config);
    } else {
        cost_ppm = getEfficiencyCostPPM(skv_config);
    }
    // electric is gross
    skv_return.inc_vat = roundToDP(cost_ppm, 2);
    // remove vat to get net amount. Large outlets pay full vat (20%), smaller ones pay reduced (5%)
    // the vat could be an average of multiple vat rates if different tariffs are used
    skv_return.exc_vat = removeVAT(skv_return.inc_vat, skv_config.vat_percentage);
    skv_return.exc_vat = roundToDP(skv_return.exc_vat, 2);

    return skv_return;
};

/**
 * get PPM on electric and ice
 * @param {struct} skv_config
 * @returns {struct}
 */
const getPPM = (skv_config) => {
    let skv_ice_result = {
        exc_vat: 0,
        inc_vat: 0,
    };

    let skv_electric_result = {
        exc_vat: 0,
        inc_vat: 0,
    };

    if (getMPG(skv_config) > 0) {
        const skv_ice_config = {
            ice_pp_litre: getICEPPLitre(skv_config),
            mpg: getMPG(skv_config),
            vat_percentage: skv_config.ice_vat_percentage,
        };

        skv_ice_result = getICEPPM(skv_ice_config);
    }
    if (getBatteryRangeMiles(skv_config) > 0) {
        const skv_electric_config = {
            elec_pp_kwh: skv_config.elec_pp_kwh,
            battery_range_miles: getBatteryRangeMiles(skv_config),
            battery_total_capacity_kwh: skv_config.battery_total_capacity_kwh,
            vat_percentage: skv_config.elec_vat_percentage,
            electric_energy_consumption: skv_config.electric_energy_consumption,
            method: skv_config.method,
        };

        skv_electric_result = getElectricPPM(skv_electric_config);
    }
    return {
        electric: skv_electric_result,
        ice: skv_ice_result,
    };
};

export {
    getElectricPPM,
    getICEPPM,
    getPPM,
    getICEPPLitre,
    getEfficiencyCostPPM,
    getBatteryRangeCostPPM,
};