vehicle/running/journey/calculate.js

/**
 * methods for calculating miles and fuel/electric usage for journeys
 * @module
 */

import { num_cs_mode_distance_miles } from '../../../maths/constants';
import { getBatteryRangeMiles, getIsCSMode } from '../util';

/**
 * for PHEVs, if a journey range is not provided then add how far the vehicle
 * will travel on Charge Sustaining mode (previously called Condition B)
 * @param {struct} skv_config mpg, battery range, optional journey miles
 * @returns {number}
 */
const getJourneyMilesForPHEVs = (skv_config) => {
    if (getIsCSMode(skv_config)) {
        // cs mode formerly called Condition B
        return num_cs_mode_distance_miles + getBatteryRangeMiles(skv_config);
    }
    return skv_config.journey_miles;
};

/**
 * work out the electric miles
 * @param {number} battery_range_miles
 * @param {number} mpg
 * @param {number} journey_miles
 * @returns {number}
 */
const getElectricMiles = (battery_range_miles, mpg, journey_miles) => {
    let electric_miles = battery_range_miles;
    // if we have over electric miles the entire journey is carried out on
    // electric and no ICE
    if (
        electric_miles > journey_miles
        || (
            mpg === 0
            && battery_range_miles > 0
        )
    ) {
        electric_miles = journey_miles;
    }
    return electric_miles;
};

/**
 * work out the ICE miles
 * @param {number} electric_miles
 * @param {number} journey_miles
 * @returns {number}
 */
const getICEMiles = (electric_miles, journey_miles) => journey_miles - electric_miles;

/**
 * when user provides a custom mpg then we'll be applying a ratio to the official figures
 * @param {number} mpg_official
 * @param {number} mpg_user
 * @returns {number}
 */
const getMPGofficalRatio = (mpg_official, mpg_user) => mpg_official / mpg_user;

/**
* proportion of journey carried out on fuel or electric miles
 * @param {number} travelled_miles fuel or electric miles travelled
 * @param {number} journey_miles
 * @returns {number}
 */
const getJourneyFuelRatio = (travelled_miles, journey_miles) => travelled_miles / journey_miles;

/**
 * number of journeys carried out
 * @param {number} journey_miles
 * @param {number} total_miles
 * @returns {number}
 */
const numberOfJourneys = (journey_miles, total_miles) => total_miles / journey_miles;

/* *********************************
 ************* COSTS **************
 ********************************* */

/**
 * Calculate fuel or electric portion of journey
 * @param {struct} skv_data struct of details for ppm, journey
 * @returns {struct}
 */
const journeyCostPortion = (skv_journey, skv_ppm) => {
    const skv_return = {
        exc_vat: '',
        inc_vat: '',
    };
    skv_return.exc_vat = skv_ppm.exc_vat * skv_journey.miles;
    // convert to pounds and round to 2 decimal places
    skv_return.exc_vat = parseFloat((skv_return.exc_vat / 100).toFixed(2));
    skv_return.inc_vat = skv_ppm.inc_vat * skv_journey.miles;
    // convert to pounds and round to 2 decimal places
    skv_return.inc_vat = parseFloat((skv_return.inc_vat / 100).toFixed(2));
    // if skv_data has exc_reduced_vat then calculate it
    if (Object.prototype.hasOwnProperty.call(skv_ppm, 'exc_reduced_vat')) {
        skv_return.exc_reduced_vat = skv_ppm.exc_reduced_vat * skv_journey.miles;
        // convert to pounds and round to 2 decimal places
        skv_return.exc_reduced_vat = parseFloat((skv_return.exc_reduced_vat / 100).toFixed(2));
    }
    return skv_return;
};

/**
 * combine the electric and ice miles and costs
 * @param {struct} skv_journey_cost
 * @returns {struct}
 */
const journeyCostTotal = (skv_journey_cost) => {
    const total_exc_vat = skv_journey_cost.electric.total_pounds.exc_vat + skv_journey_cost.ice.total_pounds.exc_vat;
    const total_inc_vat = skv_journey_cost.electric.total_pounds.inc_vat + skv_journey_cost.ice.total_pounds.inc_vat;

    const skv_return = {
        miles: skv_journey_cost.electric.distance.miles + skv_journey_cost.ice.distance.miles,
        total_pounds: {
            exc_vat: total_exc_vat,
            inc_vat: total_inc_vat,
        },
    };

    const ppm = {
        exc_vat: (total_exc_vat / skv_return.miles) * 100,
        inc_vat: (total_inc_vat / skv_return.miles) * 100,
    };

    skv_return.ppm = ppm;

    return skv_return;
};

/**
 * bring together all the journeys to form the total overall cost
 * @param {struct} skv_journey_cost
 * @param {number} total_miles
 * @returns {struct}
 */
const totalCostOfAllJourneys = (skv_journey_cost, total_miles) => {
    const electric_miles = total_miles * skv_journey_cost.electric.distance.ratio;
    const ice_miles = total_miles * skv_journey_cost.ice.distance.ratio;
    const skv_return = {
        electric: {
            miles: electric_miles,
            cost_pounds: {
                exc_vat: skv_journey_cost.electric.ppm.exc_vat * electric_miles,
                inc_vat: skv_journey_cost.electric.ppm.inc_vat * electric_miles,
            },
        },
        ice: {
            miles: ice_miles,
            cost_pounds: {
                exc_vat: skv_journey_cost.ice.ppm.exc_vat * ice_miles,
                inc_vat: skv_journey_cost.ice.ppm.inc_vat * ice_miles,
            },
        },
    };
    skv_return.combined = {
        miles: total_miles,
        cost_pounds: {
            exc_vat: skv_return.electric.cost_pounds.exc_vat + skv_return.ice.cost_pounds.exc_vat,
            inc_vat: skv_return.electric.cost_pounds.inc_vat + skv_return.ice.cost_pounds.inc_vat,
        },
    };

    // values are currently in pence so convert to pounds and round to 2 decimal places
    Object.keys(skv_return).forEach((key) => {
        Object.keys(skv_return[key].cost_pounds).forEach((key2) => {
            skv_return[key].cost_pounds[key2] = parseFloat((skv_return[key].cost_pounds[key2] / 100).toFixed(2));
        });
    });

    return skv_return;
};

/** ********************************
 ********** EMISSIONS *************
 ********************************* */

/**
 * Calculate fuel or electric portion of journey
 * @param {struct} skv_data struct of optionally tailpipe and production for ppm, journey
 * @returns {struct}
 */
const journeyEmissionsPortion = (skv_journey, skv_co2_g_per_mile) => {
    const skv_return = {};
    const potential_keys = ['tail_pipe', 'production'];

    // loop over potential keys and if they exist then calculate them
    potential_keys.forEach((key) => {
        if (Object.prototype.hasOwnProperty.call(skv_co2_g_per_mile, key)) {
            // multiply the co2 grams per mile by the journey miles
            skv_return[key] = skv_co2_g_per_mile[key] * skv_journey.miles;
            // round to 2 decimal places
            skv_return[key] = parseFloat(skv_return[key].toFixed(2));
        }
    });

    return skv_return;
};

/**
 * combine the electric and ice miles and emissions
 * @param {struct} skv_journey_emissions
 * @returns {struct}
 */
const journeyEmissionsTotal = (skv_journey_emissions) => {
    const skv_return = {
        miles: skv_journey_emissions.electric.distance.miles + skv_journey_emissions.ice.distance.miles,
        total_co2_g: {},
    };

    const electric_total = skv_journey_emissions.electric.total_co2_g;
    const ice_total = skv_journey_emissions.ice.total_co2_g;

    const potential_keys = ['tail_pipe', 'production'];

    // loop over potential keys and see if they exist in the electric_total struct
    potential_keys.forEach((key) => {
        if (Object.prototype.hasOwnProperty.call(electric_total, key)) {
            skv_return.total_co2_g[key] = electric_total[key];
        }
    });

    // loop over potential keys and see if they exist in the ice_total struct and
    // add them to the skv_return struct
    potential_keys.forEach((key) => {
        if (Object.prototype.hasOwnProperty.call(ice_total, key)) {
            if (Object.prototype.hasOwnProperty.call(skv_return.total_co2_g, key)) {
                skv_return.total_co2_g[key] += ice_total[key];
            } else {
                skv_return.total_co2_g[key] = ice_total[key];
            }
        }
    });

    return skv_return;
};

/**
 * total emissions for all journeys based on total_miles passed in
 * @param {struct} skv_journey_emissions
 * @param {number} total_miles
 * @returns {struct}
 */
const totalEmissionsOfAllJourneys = (skv_journey_emissions, total_miles) => {
    const electric_miles = total_miles * skv_journey_emissions.electric.distance.ratio;
    const ice_miles = total_miles * skv_journey_emissions.ice.distance.ratio;
    const skv_return = {
        electric: {
            miles: electric_miles,
            total_co2_g: {
                production: skv_journey_emissions.electric.co2_g_per_mile.production * electric_miles,
            },
        },
        ice: {
            miles: ice_miles,
            total_co2_g: {
                tail_pipe: skv_journey_emissions.ice.co2_g_per_mile.tail_pipe * ice_miles,
                production: skv_journey_emissions.ice.co2_g_per_mile.production * ice_miles,
            },
        },
    };

    skv_return.combined = {
        miles: total_miles,
        total_co2_g: {
            tail_pipe: skv_return.ice.total_co2_g.tail_pipe,
            production: skv_return.ice.total_co2_g.production + skv_return.electric.total_co2_g.production,
        },
    };

    return skv_return;
};

export {
    getJourneyMilesForPHEVs,
    getElectricMiles,
    getICEMiles,
    getMPGofficalRatio,
    getJourneyFuelRatio,
    numberOfJourneys,
    // costs
    journeyCostPortion,
    journeyCostTotal,
    totalCostOfAllJourneys,
    // emissions
    journeyEmissionsPortion,
    journeyEmissionsTotal,
    totalEmissionsOfAllJourneys,
};