From de49924a7d42febde2109e999eb4d38ebbef7e25 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 8 Nov 2023 14:24:44 -0700 Subject: [PATCH 01/40] convert metrics services angular -> ts migration for metrics-mappings and metrics-factory The datasets have their own files, there is a customhelper, a methelper, and a footprint helper Tested in the emulator and dashboard still behaves as expected --- www/index.js | 2 - www/js/control/ProfileSettings.jsx | 16 +- www/js/metrics-factory.js | 271 ---------------- www/js/metrics-mappings.js | 425 ------------------------- www/js/metrics/CarbonDatasets.ts | 123 +++++++ www/js/metrics/CarbonFootprintCard.tsx | 33 +- www/js/metrics/CarbonTextCard.tsx | 29 +- www/js/metrics/CustomMetricsHelper.ts | 148 +++++++++ www/js/metrics/METDataset.ts | 128 ++++++++ www/js/metrics/footprintHelper.ts | 161 ++++++++++ www/js/metrics/metHelper.ts | 109 +++++++ 11 files changed, 704 insertions(+), 741 deletions(-) delete mode 100644 www/js/metrics-factory.js delete mode 100644 www/js/metrics-mappings.js create mode 100644 www/js/metrics/CarbonDatasets.ts create mode 100644 www/js/metrics/CustomMetricsHelper.ts create mode 100644 www/js/metrics/METDataset.ts create mode 100644 www/js/metrics/footprintHelper.ts create mode 100644 www/js/metrics/metHelper.ts diff --git a/www/index.js b/www/index.js index 78d29cf7a..06802d14f 100644 --- a/www/index.js +++ b/www/index.js @@ -22,6 +22,4 @@ import './js/survey/enketo/answer.js'; import './js/survey/enketo/enketo-trip-button.js'; import './js/survey/enketo/enketo-add-note-button.js'; import './js/control/emailService.js'; -import './js/metrics-factory.js'; -import './js/metrics-mappings.js'; import './js/plugin/logger.ts'; diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index b081e642a..b8943a81c 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -26,6 +26,11 @@ import ControlCollectionHelper, { helperToggleLowAccuracy, forceTransition, } from './ControlCollectionHelper'; +import { + getCarbonDatasetOptions, + getCurrentCarbonDatasetCode, + saveCurrentCarbonDatasetLocale, +} from '../metrics/customMetricsHelper'; import { resetDataAndRefresh } from '../config/dynamicConfig'; import { AppContext } from '../App'; import { shareQR } from '../components/QrCode'; @@ -43,7 +48,6 @@ const ProfileSettings = () => { const { setPermissionsPopupVis } = useContext(AppContext); //angular services needed - const CarbonDatasetHelper = getAngularService('CarbonDatasetHelper'); const EmailHelper = getAngularService('EmailHelper'); const NotificationScheduler = getAngularService('NotificationScheduler'); const ControlHelper = getAngularService('ControlHelper'); @@ -84,8 +88,8 @@ const ProfileSettings = () => { const appVersion = useRef(); let carbonDatasetString = - t('general-settings.carbon-dataset') + ': ' + CarbonDatasetHelper.getCurrentCarbonDatasetCode(); - const carbonOptions = CarbonDatasetHelper.getCarbonDatasetOptions(); + t('general-settings.carbon-dataset') + ': ' + getCurrentCarbonDatasetCode(); + const carbonOptions = getCarbonDatasetOptions(); const stateActions = [ { text: 'Initialize', transition: 'INITIALIZE' }, { text: 'Start trip', transition: 'EXITED_GEOFENCE' }, @@ -361,12 +365,10 @@ const ProfileSettings = () => { const onSelectCarbon = function (carbonObject) { console.log('changeCarbonDataset(): chose locale ' + carbonObject.value); - CarbonDatasetHelper.saveCurrentCarbonDatasetLocale(carbonObject.value); //there's some sort of error here + saveCurrentCarbonDatasetLocale(carbonObject.value); //there's some sort of error here //Unhandled Promise Rejection: While logging, error -[NSNull UTF8String]: unrecognized selector sent to instance 0x7fff8a625fb0 carbonDatasetString = - i18next.t('general-settings.carbon-dataset') + - ': ' + - CarbonDatasetHelper.getCurrentCarbonDatasetCode(); + i18next.t('general-settings.carbon-dataset') + ': ' + getCurrentCarbonDatasetCode(); }; //conditional creation of setting sections diff --git a/www/js/metrics-factory.js b/www/js/metrics-factory.js deleted file mode 100644 index 28ef5ae9e..000000000 --- a/www/js/metrics-factory.js +++ /dev/null @@ -1,271 +0,0 @@ -'use strict'; - -import angular from 'angular'; -import { getBaseModeByValue } from './diary/diaryHelper'; -import { labelOptions } from './survey/multilabel/confirmHelper'; -import { storageGet, storageRemove, storageSet } from './plugin/storage'; - -angular - .module('emission.main.metrics.factory', ['emission.main.metrics.mappings']) - - .factory('FootprintHelper', function (CarbonDatasetHelper, CustomDatasetHelper) { - var fh = {}; - var highestFootprint = 0; - - var mtokm = function (v) { - return v / 1000; - }; - fh.useCustom = false; - - fh.setUseCustomFootprint = function () { - fh.useCustom = true; - }; - - fh.getFootprint = function () { - if (this.useCustom == true) { - return CustomDatasetHelper.getCustomFootprint(); - } else { - return CarbonDatasetHelper.getCurrentCarbonDatasetFootprint(); - } - }; - - fh.readableFormat = function (v) { - return v > 999 ? Math.round(v / 1000) + 'k kg CO₂' : Math.round(v) + ' kg CO₂'; - }; - fh.getFootprintForMetrics = function (userMetrics, defaultIfMissing = 0) { - var footprint = fh.getFootprint(); - var result = 0; - for (var i in userMetrics) { - var mode = userMetrics[i].key; - if (mode == 'ON_FOOT') { - mode = 'WALKING'; - } - - if (mode in footprint) { - result += footprint[mode] * mtokm(userMetrics[i].values); - } else if (mode == 'IN_VEHICLE') { - result += - ((footprint['CAR'] + - footprint['BUS'] + - footprint['LIGHT_RAIL'] + - footprint['TRAIN'] + - footprint['TRAM'] + - footprint['SUBWAY']) / - 6) * - mtokm(userMetrics[i].values); - } else { - console.warn( - 'WARNING FootprintHelper.getFootprintFromMetrics() was requested for an unknown mode: ' + - mode + - ' metrics JSON: ' + - JSON.stringify(userMetrics), - ); - result += defaultIfMissing * mtokm(userMetrics[i].values); - } - } - return result; - }; - fh.getLowestFootprintForDistance = function (distance) { - var footprint = fh.getFootprint(); - var lowestFootprint = Number.MAX_SAFE_INTEGER; - for (var mode in footprint) { - if (mode == 'WALKING' || mode == 'BICYCLING') { - // these modes aren't considered when determining the lowest carbon footprint - } else { - lowestFootprint = Math.min(lowestFootprint, footprint[mode]); - } - } - return lowestFootprint * mtokm(distance); - }; - - fh.getHighestFootprint = function () { - if (!highestFootprint) { - var footprint = fh.getFootprint(); - let footprintList = []; - for (var mode in footprint) { - footprintList.push(footprint[mode]); - } - highestFootprint = Math.max(...footprintList); - } - return highestFootprint; - }; - - fh.getHighestFootprintForDistance = function (distance) { - return fh.getHighestFootprint() * mtokm(distance); - }; - - var getLowestMotorizedNonAirFootprint = function (footprint, rlmCO2) { - var lowestFootprint = Number.MAX_SAFE_INTEGER; - for (var mode in footprint) { - if (mode == 'AIR_OR_HSR' || mode == 'air') { - console.log('Air mode, ignoring'); - } else { - if (footprint[mode] == 0 || footprint[mode] <= rlmCO2) { - console.log( - 'Non motorized mode or footprint <= range_limited_motorized', - mode, - footprint[mode], - rlmCO2, - ); - } else { - lowestFootprint = Math.min(lowestFootprint, footprint[mode]); - } - } - } - return lowestFootprint; - }; - - fh.getOptimalDistanceRanges = function () { - const FIVE_KM = 5 * 1000; - const SIX_HUNDRED_KM = 600 * 1000; - if (!fh.useCustom) { - const defaultFootprint = CarbonDatasetHelper.getCurrentCarbonDatasetFootprint(); - const lowestMotorizedNonAir = getLowestMotorizedNonAirFootprint(defaultFootprint); - const airFootprint = defaultFootprint['AIR_OR_HSR']; - return [ - { low: 0, high: FIVE_KM, optimal: 0 }, - { low: FIVE_KM, high: SIX_HUNDRED_KM, optimal: lowestMotorizedNonAir }, - { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint }, - ]; - } else { - // custom footprint, let's get the custom values - const customFootprint = CustomDatasetHelper.getCustomFootprint(); - let airFootprint = customFootprint['air']; - if (!airFootprint) { - // 2341 BTU/PMT from - // https://tedb.ornl.gov/wp-content/uploads/2021/02/TEDB_Ed_39.pdf#page=68 - // 159.25 lb per million BTU from EIA - // https://www.eia.gov/environment/emissions/co2_vol_mass.php - // (2341 * (159.25/1000000))/(1.6*2.2) = 0.09975, rounded up a bit - console.log('No entry for air in ', customFootprint, ' using default'); - airFootprint = 0.1; - } - const rlm = CustomDatasetHelper.range_limited_motorized; - if (!rlm) { - return [ - { low: 0, high: FIVE_KM, optimal: 0 }, - { low: FIVE_KM, high: SIX_HUNDRED_KM, optimal: lowestMotorizedNonAir }, - { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint }, - ]; - } else { - console.log('Found range_limited_motorized mode', rlm); - const lowestMotorizedNonAir = getLowestMotorizedNonAirFootprint( - customFootprint, - rlm.kgCo2PerKm, - ); - return [ - { low: 0, high: FIVE_KM, optimal: 0 }, - { low: FIVE_KM, high: rlm.range_limit_km * 1000, optimal: rlm.kgCo2PerKm }, - { - low: rlm.range_limit_km * 1000, - high: SIX_HUNDRED_KM, - optimal: lowestMotorizedNonAir, - }, - { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint }, - ]; - } - } - }; - - return fh; - }) - - .factory('CalorieCal', function (METDatasetHelper, CustomDatasetHelper) { - var cc = {}; - var highestMET = 0; - var USER_DATA_KEY = 'user-data'; - cc.useCustom = false; - - cc.setUseCustomFootprint = function () { - cc.useCustom = true; - }; - - cc.getMETs = function () { - if (this.useCustom == true) { - return CustomDatasetHelper.getCustomMETs(); - } else { - return METDatasetHelper.getStandardMETs(); - } - }; - - cc.set = function (info) { - return storageSet(USER_DATA_KEY, info); - }; - cc.get = function () { - return storageGet(USER_DATA_KEY); - }; - cc.delete = function () { - return storageRemove(USER_DATA_KEY); - }; - Number.prototype.between = function (min, max) { - return this >= min && this <= max; - }; - cc.getHighestMET = function () { - if (!highestMET) { - var met = cc.getMETs(); - let metList = []; - for (var mode in met) { - var rangeList = met[mode]; - for (var range in rangeList) { - metList.push(rangeList[range].mets); - } - } - highestMET = Math.max(...metList); - } - return highestMET; - }; - cc.getMet = function (mode, speed, defaultIfMissing) { - if (mode == 'ON_FOOT') { - console.log("CalorieCal.getMet() converted 'ON_FOOT' to 'WALKING'"); - mode = 'WALKING'; - } - let currentMETs = cc.getMETs(); - if (!currentMETs[mode]) { - console.warn('CalorieCal.getMet() Illegal mode: ' + mode); - return defaultIfMissing; //So the calorie sum does not break with wrong return type - } - for (var i in currentMETs[mode]) { - if (mpstomph(speed).between(currentMETs[mode][i].range[0], currentMETs[mode][i].range[1])) { - return currentMETs[mode][i].mets; - } else if (mpstomph(speed) < 0) { - console.log('CalorieCal.getMet() Negative speed: ' + mpstomph(speed)); - return 0; - } - } - }; - var mpstomph = function (mps) { - return 2.23694 * mps; - }; - var lbtokg = function (lb) { - return lb * 0.453592; - }; - var fttocm = function (ft) { - return ft * 30.48; - }; - cc.getCorrectedMet = function (met, gender, age, height, heightUnit, weight, weightUnit) { - var height = heightUnit == 0 ? fttocm(height) : height; - var weight = weightUnit == 0 ? lbtokg(weight) : weight; - if (gender == 1) { - //male - var met = - (met * 3.5) / - (((66.473 + 5.0033 * height + 13.7516 * weight - 6.755 * age) / 1440 / 5 / weight) * - 1000); - return met; - } else if (gender == 0) { - //female - var met = - (met * 3.5) / - (((655.0955 + 1.8496 * height + 9.5634 * weight - 4.6756 * age) / 1440 / 5 / weight) * - 1000); - return met; - } - }; - cc.getuserCalories = function (durationInMin, met) { - return 65 * durationInMin * met; - }; - cc.getCalories = function (weightInKg, durationInMin, met) { - return weightInKg * durationInMin * met; - }; - return cc; - }); diff --git a/www/js/metrics-mappings.js b/www/js/metrics-mappings.js deleted file mode 100644 index 38836a3a1..000000000 --- a/www/js/metrics-mappings.js +++ /dev/null @@ -1,425 +0,0 @@ -import angular from 'angular'; -import { getLabelOptions } from './survey/multilabel/confirmHelper'; -import { getConfig } from './config/dynamicConfig'; -import { storageGet, storageSet } from './plugin/storage'; - -angular - .module('emission.main.metrics.mappings', ['emission.plugin.logger']) - - .service('CarbonDatasetHelper', function () { - var CARBON_DATASET_KEY = 'carbon_dataset_locale'; - - // Values are in Kg/PKm (kilograms per passenger-kilometer) - // Sources for EU values: - // - Tremod: 2017, CO2, CH4 and N2O in CO2-equivalent - // - HBEFA: 2020, CO2 (per country) - // German data uses Tremod. Other EU countries (and Switzerland) use HBEFA for car and bus, - // and Tremod for train and air (because HBEFA doesn't provide these). - // EU data is an average of the Tremod/HBEFA data for the countries listed; - // for this average the HBEFA data was used also in the German set (for car and bus). - var carbonDatasets = { - US: { - regionName: 'United States', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 267 / 1609, - BUS: 278 / 1609, - LIGHT_RAIL: 120 / 1609, - SUBWAY: 74 / 1609, - TRAM: 90 / 1609, - TRAIN: 92 / 1609, - AIR_OR_HSR: 217 / 1609, - }, - }, - EU: { - // Plain average of values for the countries below (using HBEFA for car and bus, Tremod for others) - regionName: 'European Union', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.14515, - BUS: 0.04751, - LIGHT_RAIL: 0.064, - SUBWAY: 0.064, - TRAM: 0.064, - TRAIN: 0.048, - AIR_OR_HSR: 0.201, - }, - }, - DE: { - regionName: 'Germany', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.139, // Tremod (passenger car) - BUS: 0.0535, // Tremod (average city/coach) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - FR: { - regionName: 'France', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.13125, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04838, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - AT: { - regionName: 'Austria', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.14351, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04625, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - SE: { - regionName: 'Sweden', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.13458, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04557, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - NO: { - regionName: 'Norway', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.13265, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04185, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - CH: { - regionName: 'Switzerland', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.17638, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04866, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - }; - - var defaultCarbonDatasetCode = 'US'; - var currentCarbonDatasetCode = defaultCarbonDatasetCode; - - // we need to call the method from within a promise in initialize() - // and using this.setCurrentCarbonDatasetLocale doesn't seem to work - var setCurrentCarbonDatasetLocale = function (localeCode) { - for (var code in carbonDatasets) { - if (code == localeCode) { - currentCarbonDatasetCode = localeCode; - break; - } - } - }; - - this.loadCarbonDatasetLocale = function () { - return storageGet(CARBON_DATASET_KEY).then(function (localeCode) { - Logger.log( - 'CarbonDatasetHelper.loadCarbonDatasetLocale() obtained value from storage [' + - localeCode + - ']', - ); - if (!localeCode) { - localeCode = defaultCarbonDatasetCode; - Logger.log( - 'CarbonDatasetHelper.loadCarbonDatasetLocale() no value in storage, using [' + - localeCode + - '] instead', - ); - } - setCurrentCarbonDatasetLocale(localeCode); - }); - }; - - this.saveCurrentCarbonDatasetLocale = function (localeCode) { - setCurrentCarbonDatasetLocale(localeCode); - storageSet(CARBON_DATASET_KEY, currentCarbonDatasetCode); - Logger.log( - 'CarbonDatasetHelper.saveCurrentCarbonDatasetLocale() saved value [' + - currentCarbonDatasetCode + - '] to storage', - ); - }; - - this.getCarbonDatasetOptions = function () { - var options = []; - for (var code in carbonDatasets) { - options.push({ - text: code, //carbonDatasets[code].regionName, - value: code, - }); - } - return options; - }; - - this.getCurrentCarbonDatasetCode = function () { - return currentCarbonDatasetCode; - }; - - this.getCurrentCarbonDatasetFootprint = function () { - return carbonDatasets[currentCarbonDatasetCode].footprintData; - }; - }) - .service('METDatasetHelper', function () { - var standardMETs = { - WALKING: { - VERY_SLOW: { - range: [0, 2.0], - mets: 2.0, - }, - SLOW: { - range: [2.0, 2.5], - mets: 2.8, - }, - MODERATE_0: { - range: [2.5, 2.8], - mets: 3.0, - }, - MODERATE_1: { - range: [2.8, 3.2], - mets: 3.5, - }, - FAST: { - range: [3.2, 3.5], - mets: 4.3, - }, - VERY_FAST_0: { - range: [3.5, 4.0], - mets: 5.0, - }, - 'VERY_FAST_!': { - range: [4.0, 4.5], - mets: 6.0, - }, - VERY_VERY_FAST: { - range: [4.5, 5], - mets: 7.0, - }, - SUPER_FAST: { - range: [5, 6], - mets: 8.3, - }, - RUNNING: { - range: [6, Number.MAX_VALUE], - mets: 9.8, - }, - }, - BICYCLING: { - VERY_VERY_SLOW: { - range: [0, 5.5], - mets: 3.5, - }, - VERY_SLOW: { - range: [5.5, 10], - mets: 5.8, - }, - SLOW: { - range: [10, 12], - mets: 6.8, - }, - MODERATE: { - range: [12, 14], - mets: 8.0, - }, - FAST: { - range: [14, 16], - mets: 10.0, - }, - VERT_FAST: { - range: [16, 19], - mets: 12.0, - }, - RACING: { - range: [20, Number.MAX_VALUE], - mets: 15.8, - }, - }, - UNKNOWN: { - ALL: { - range: [0, Number.MAX_VALUE], - mets: 0, - }, - }, - IN_VEHICLE: { - ALL: { - range: [0, Number.MAX_VALUE], - mets: 0, - }, - }, - CAR: { - ALL: { - range: [0, Number.MAX_VALUE], - mets: 0, - }, - }, - BUS: { - ALL: { - range: [0, Number.MAX_VALUE], - mets: 0, - }, - }, - LIGHT_RAIL: { - ALL: { - range: [0, Number.MAX_VALUE], - mets: 0, - }, - }, - TRAIN: { - ALL: { - range: [0, Number.MAX_VALUE], - mets: 0, - }, - }, - TRAM: { - ALL: { - range: [0, Number.MAX_VALUE], - mets: 0, - }, - }, - SUBWAY: { - ALL: { - range: [0, Number.MAX_VALUE], - mets: 0, - }, - }, - AIR_OR_HSR: { - ALL: { - range: [0, Number.MAX_VALUE], - mets: 0, - }, - }, - }; - this.getStandardMETs = function () { - return standardMETs; - }; - }) - .factory('CustomDatasetHelper', function (METDatasetHelper, Logger, $ionicPlatform) { - var cdh = {}; - - cdh.getCustomMETs = function () { - console.log('Getting custom METs', cdh.customMETs); - return cdh.customMETs; - }; - - cdh.getCustomFootprint = function () { - console.log('Getting custom footprint', cdh.customPerKmFootprint); - return cdh.customPerKmFootprint; - }; - - cdh.populateCustomMETs = function () { - let standardMETs = METDatasetHelper.getStandardMETs(); - let modeOptions = cdh.inputParams['MODE']; - let modeMETEntries = modeOptions.map((opt) => { - if (opt.met_equivalent) { - let currMET = standardMETs[opt.met_equivalent]; - return [opt.value, currMET]; - } else { - if (opt.met) { - let currMET = opt.met; - // if the user specifies a custom MET, they can't specify - // Number.MAX_VALUE since it is not valid JSON - // we assume that they specify -1 instead, and we will - // map -1 to Number.MAX_VALUE here by iterating over all the ranges - for (const rangeName in currMET) { - // console.log("Handling range ", rangeName); - currMET[rangeName].range = currMET[rangeName].range.map((i) => - i == -1 ? Number.MAX_VALUE : i, - ); - } - return [opt.value, currMET]; - } else { - console.warn( - 'Did not find either met_equivalent or met for ' + opt.value + ' ignoring entry', - ); - return undefined; - } - } - }); - cdh.customMETs = Object.fromEntries(modeMETEntries.filter((e) => angular.isDefined(e))); - console.log('After populating, custom METs = ', cdh.customMETs); - }; - - cdh.populateCustomFootprints = function () { - let modeOptions = cdh.inputParams['MODE']; - let modeCO2PerKm = modeOptions - .map((opt) => { - if (opt.range_limit_km) { - if (cdh.range_limited_motorized) { - Logger.displayError('Found two range limited motorized options', { - first: cdh.range_limited_motorized, - second: opt, - }); - } - cdh.range_limited_motorized = opt; - console.log('Found range limited motorized mode', cdh.range_limited_motorized); - } - if (angular.isDefined(opt.kgCo2PerKm)) { - return [opt.value, opt.kgCo2PerKm]; - } else { - return undefined; - } - }) - .filter((modeCO2) => angular.isDefined(modeCO2)); - cdh.customPerKmFootprint = Object.fromEntries(modeCO2PerKm); - console.log('After populating, custom perKmFootprint', cdh.customPerKmFootprint); - }; - - cdh.init = function (newConfig) { - try { - getLabelOptions(newConfig).then((inputParams) => { - console.log('Input params = ', inputParams); - cdh.inputParams = inputParams; - cdh.populateCustomMETs(); - cdh.populateCustomFootprints(); - }); - } catch (e) { - setTimeout(() => { - Logger.displayError( - 'Error in metrics-mappings while initializing custom dataset helper', - e, - ); - }, 1000); - } - }; - - $ionicPlatform.ready().then(function () { - getConfig().then((newConfig) => cdh.init(newConfig)); - }); - - return cdh; - }); diff --git a/www/js/metrics/CarbonDatasets.ts b/www/js/metrics/CarbonDatasets.ts new file mode 100644 index 000000000..b4815eead --- /dev/null +++ b/www/js/metrics/CarbonDatasets.ts @@ -0,0 +1,123 @@ +// Values are in Kg/PKm (kilograms per passenger-kilometer) +// Sources for EU values: +// - Tremod: 2017, CO2, CH4 and N2O in CO2-equivalent +// - HBEFA: 2020, CO2 (per country) +// German data uses Tremod. Other EU countries (and Switzerland) use HBEFA for car and bus, +// and Tremod for train and air (because HBEFA doesn't provide these). +// EU data is an average of the Tremod/HBEFA data for the countries listed; +// for this average the HBEFA data was used also in the German set (for car and bus). +export const carbonDatasets = { + US: { + regionName: 'United States', + footprintData: { + WALKING: 0, + BICYCLING: 0, + CAR: 267 / 1609, + BUS: 278 / 1609, + LIGHT_RAIL: 120 / 1609, + SUBWAY: 74 / 1609, + TRAM: 90 / 1609, + TRAIN: 92 / 1609, + AIR_OR_HSR: 217 / 1609, + }, + }, + EU: { + // Plain average of values for the countries below (using HBEFA for car and bus, Tremod for others) + regionName: 'European Union', + footprintData: { + WALKING: 0, + BICYCLING: 0, + CAR: 0.14515, + BUS: 0.04751, + LIGHT_RAIL: 0.064, + SUBWAY: 0.064, + TRAM: 0.064, + TRAIN: 0.048, + AIR_OR_HSR: 0.201, + }, + }, + DE: { + regionName: 'Germany', + footprintData: { + WALKING: 0, + BICYCLING: 0, + CAR: 0.139, // Tremod (passenger car) + BUS: 0.0535, // Tremod (average city/coach) + LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) + SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) + TRAM: 0.064, // Tremod (DE tram, urban rail and subway) + TRAIN: 0.048, // Tremod (DE average short/long distance) + AIR_OR_HSR: 0.201, // Tremod (DE airplane) + }, + }, + FR: { + regionName: 'France', + footprintData: { + WALKING: 0, + BICYCLING: 0, + CAR: 0.13125, // HBEFA (passenger car, considering 1 passenger) + BUS: 0.04838, // HBEFA (average short/long distance, considering 16/25 passengers) + LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) + SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) + TRAM: 0.064, // Tremod (DE tram, urban rail and subway) + TRAIN: 0.048, // Tremod (DE average short/long distance) + AIR_OR_HSR: 0.201, // Tremod (DE airplane) + }, + }, + AT: { + regionName: 'Austria', + footprintData: { + WALKING: 0, + BICYCLING: 0, + CAR: 0.14351, // HBEFA (passenger car, considering 1 passenger) + BUS: 0.04625, // HBEFA (average short/long distance, considering 16/25 passengers) + LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) + SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) + TRAM: 0.064, // Tremod (DE tram, urban rail and subway) + TRAIN: 0.048, // Tremod (DE average short/long distance) + AIR_OR_HSR: 0.201, // Tremod (DE airplane) + }, + }, + SE: { + regionName: 'Sweden', + footprintData: { + WALKING: 0, + BICYCLING: 0, + CAR: 0.13458, // HBEFA (passenger car, considering 1 passenger) + BUS: 0.04557, // HBEFA (average short/long distance, considering 16/25 passengers) + LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) + SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) + TRAM: 0.064, // Tremod (DE tram, urban rail and subway) + TRAIN: 0.048, // Tremod (DE average short/long distance) + AIR_OR_HSR: 0.201, // Tremod (DE airplane) + }, + }, + NO: { + regionName: 'Norway', + footprintData: { + WALKING: 0, + BICYCLING: 0, + CAR: 0.13265, // HBEFA (passenger car, considering 1 passenger) + BUS: 0.04185, // HBEFA (average short/long distance, considering 16/25 passengers) + LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) + SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) + TRAM: 0.064, // Tremod (DE tram, urban rail and subway) + TRAIN: 0.048, // Tremod (DE average short/long distance) + AIR_OR_HSR: 0.201, // Tremod (DE airplane) + }, + }, + CH: { + regionName: 'Switzerland', + footprintData: { + WALKING: 0, + BICYCLING: 0, + CAR: 0.17638, // HBEFA (passenger car, considering 1 passenger) + BUS: 0.04866, // HBEFA (average short/long distance, considering 16/25 passengers) + LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) + SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) + TRAM: 0.064, // Tremod (DE tram, urban rail and subway) + TRAIN: 0.048, // Tremod (DE average short/long distance) + AIR_OR_HSR: 0.201, // Tremod (DE airplane) + }, + }, +}; diff --git a/www/js/metrics/CarbonFootprintCard.tsx b/www/js/metrics/CarbonFootprintCard.tsx index 7c9bf3891..f2ac1cc76 100644 --- a/www/js/metrics/CarbonFootprintCard.tsx +++ b/www/js/metrics/CarbonFootprintCard.tsx @@ -3,6 +3,12 @@ import { View } from 'react-native'; import { Card, Text, useTheme } from 'react-native-paper'; import { MetricsData } from './metricsTypes'; import { cardStyles } from './MetricsTab'; +import { + getFootprintForMetrics, + setUseCustomFootprint, + getHighestFootprint, + getHighestFootprintForDistance, +} from './footprintHelper'; import { formatDateRangeOfDays, parseDataFromMetrics, @@ -13,13 +19,11 @@ import { } from './metricsHelper'; import { useTranslation } from 'react-i18next'; import BarChart from '../components/BarChart'; -import { getAngularService } from '../angular-react-helper'; import ChangeIndicator from './ChangeIndicator'; import color from 'color'; type Props = { userMetrics: MetricsData; aggMetrics: MetricsData }; const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { - const FootprintHelper = getAngularService('FootprintHelper'); const { colors } = useTheme(); const { t } = useTranslation(); @@ -51,18 +55,15 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { //set custon dataset, if the labels are custom if (isCustomLabels(userThisWeekModeMap)) { - FootprintHelper.setUseCustomFootprint(); + setUseCustomFootprint(); } //calculate low-high and format range for prev week, if exists (14 days ago -> 8 days ago) let userPrevWeek; if (userLastWeekSummaryMap[0]) { userPrevWeek = { - low: FootprintHelper.getFootprintForMetrics(userLastWeekSummaryMap, 0), - high: FootprintHelper.getFootprintForMetrics( - userLastWeekSummaryMap, - FootprintHelper.getHighestFootprint(), - ), + low: getFootprintForMetrics(userLastWeekSummaryMap, 0), + high: getFootprintForMetrics(userLastWeekSummaryMap, getHighestFootprint()), }; graphRecords.push({ label: t('main-metrics.unlabeled'), @@ -78,11 +79,8 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { //calculate low-high and format range for past week (7 days ago -> yesterday) let userPastWeek = { - low: FootprintHelper.getFootprintForMetrics(userThisWeekSummaryMap, 0), - high: FootprintHelper.getFootprintForMetrics( - userThisWeekSummaryMap, - FootprintHelper.getHighestFootprint(), - ), + low: getFootprintForMetrics(userThisWeekSummaryMap, 0), + high: getFootprintForMetrics(userThisWeekSummaryMap, getHighestFootprint()), }; graphRecords.push({ label: t('main-metrics.unlabeled'), @@ -100,7 +98,7 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { } //calculate worst-case carbon footprint - let worstCarbon = FootprintHelper.getHighestFootprintForDistance(worstDistance); + let worstCarbon = getHighestFootprintForDistance(worstDistance); graphRecords.push({ label: t('main-metrics.labeled'), x: worstCarbon, @@ -138,11 +136,8 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { let groupRecords = []; let aggCarbon = { - low: FootprintHelper.getFootprintForMetrics(aggCarbonData, 0), - high: FootprintHelper.getFootprintForMetrics( - aggCarbonData, - FootprintHelper.getHighestFootprint(), - ), + low: getFootprintForMetrics(aggCarbonData, 0), + high: getFootprintForMetrics(aggCarbonData, getHighestFootprint()), }; console.log('testing group past week', aggCarbon); groupRecords.push({ diff --git a/www/js/metrics/CarbonTextCard.tsx b/www/js/metrics/CarbonTextCard.tsx index 9f1b4490f..bf40c4a61 100644 --- a/www/js/metrics/CarbonTextCard.tsx +++ b/www/js/metrics/CarbonTextCard.tsx @@ -4,6 +4,11 @@ import { Card, Text, useTheme } from 'react-native-paper'; import { MetricsData } from './metricsTypes'; import { cardStyles } from './MetricsTab'; import { useTranslation } from 'react-i18next'; +import { + getFootprintForMetrics, + getHighestFootprint, + getHighestFootprintForDistance, +} from './footprintHelper'; import { formatDateRangeOfDays, parseDataFromMetrics, @@ -17,7 +22,6 @@ type Props = { userMetrics: MetricsData; aggMetrics: MetricsData }; const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => { const { colors } = useTheme(); const { t } = useTranslation(); - const FootprintHelper = getAngularService('FootprintHelper'); const userText = useMemo(() => { if (userMetrics?.distance?.length > 0) { @@ -46,11 +50,8 @@ const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => { //calculate low-high and format range for prev week, if exists (14 days ago -> 8 days ago) if (userLastWeekSummaryMap[0]) { let userPrevWeek = { - low: FootprintHelper.getFootprintForMetrics(userLastWeekSummaryMap, 0), - high: FootprintHelper.getFootprintForMetrics( - userLastWeekSummaryMap, - FootprintHelper.getHighestFootprint(), - ), + low: getFootprintForMetrics(userLastWeekSummaryMap, 0), + high: getFootprintForMetrics(userLastWeekSummaryMap, getHighestFootprint()), }; const label = `${t('main-metrics.prev-week')} (${formatDateRangeOfDays(lastWeekDistance)})`; if (userPrevWeek.low == userPrevWeek.high) @@ -64,11 +65,8 @@ const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => { //calculate low-high and format range for past week (7 days ago -> yesterday) let userPastWeek = { - low: FootprintHelper.getFootprintForMetrics(userThisWeekSummaryMap, 0), - high: FootprintHelper.getFootprintForMetrics( - userThisWeekSummaryMap, - FootprintHelper.getHighestFootprint(), - ), + low: getFootprintForMetrics(userThisWeekSummaryMap, 0), + high: getFootprintForMetrics(userThisWeekSummaryMap, getHighestFootprint()), }; const label = `${t('main-metrics.past-week')} (${formatDateRangeOfDays(thisWeekDistance)})`; if (userPastWeek.low == userPastWeek.high) @@ -80,7 +78,7 @@ const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => { }); //calculate worst-case carbon footprint - let worstCarbon = FootprintHelper.getHighestFootprintForDistance(worstDistance); + let worstCarbon = getHighestFootprintForDistance(worstDistance); textList.push({ label: t('main-metrics.worst-case'), value: Math.round(worstCarbon) }); return textList; @@ -113,11 +111,8 @@ const CarbonTextCard = ({ userMetrics, aggMetrics }: Props) => { let groupText = []; let aggCarbon = { - low: FootprintHelper.getFootprintForMetrics(aggCarbonData, 0), - high: FootprintHelper.getFootprintForMetrics( - aggCarbonData, - FootprintHelper.getHighestFootprint(), - ), + low: getFootprintForMetrics(aggCarbonData, 0), + high: getFootprintForMetrics(aggCarbonData, getHighestFootprint()), }; console.log('testing group past week', aggCarbon); const label = t('main-metrics.average'); diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/CustomMetricsHelper.ts new file mode 100644 index 000000000..062f6a2ca --- /dev/null +++ b/www/js/metrics/CustomMetricsHelper.ts @@ -0,0 +1,148 @@ +import angular from 'angular'; +import { getLabelOptions } from '../survey/multilabel/confirmHelper'; +import { getConfig } from '../config/dynamicConfig'; +import { storageGet, storageSet } from '../plugin/storage'; +import { displayError, logDebug } from '../plugin/logger'; +import { standardMETs } from './metDataset'; +import { carbonDatasets } from './carbonDatasets'; + +const CARBON_DATASET_KEY = 'carbon_dataset_locale'; +const defaultCarbonDatasetCode = 'US'; + +let _customMETs; +let _customPerKmFootprint; +let _range_limited_motorized; +let _inputParams; +let _currentCarbonDatasetCode = defaultCarbonDatasetCode; + +// we need to call the method from within a promise in initialize() +// and using this.setCurrentCarbonDatasetLocale doesn't seem to work +const setCurrentCarbonDatasetLocale = function (localeCode) { + for (var code in carbonDatasets) { + if (code == localeCode) { + _currentCarbonDatasetCode = localeCode; + break; + } + } +}; + +const loadCarbonDatasetLocale = function () { + return storageGet(CARBON_DATASET_KEY).then(function (localeCode) { + logDebug('loadCarbonDatasetLocale() obtained value from storage [' + localeCode + ']'); + if (!localeCode) { + localeCode = defaultCarbonDatasetCode; + logDebug('loadCarbonDatasetLocale() no value in storage, using [' + localeCode + '] instead'); + } + setCurrentCarbonDatasetLocale(localeCode); + }); +}; + +export const saveCurrentCarbonDatasetLocale = function (localeCode) { + setCurrentCarbonDatasetLocale(localeCode); + storageSet(CARBON_DATASET_KEY, _currentCarbonDatasetCode); + logDebug( + 'saveCurrentCarbonDatasetLocale() saved value [' + _currentCarbonDatasetCode + '] to storage', + ); +}; + +export const getCarbonDatasetOptions = function () { + var options = []; + for (var code in carbonDatasets) { + options.push({ + text: code, //carbonDatasets[code].regionName, + value: code, + }); + } + return options; +}; + +export const getCurrentCarbonDatasetCode = function () { + return _currentCarbonDatasetCode; +}; + +export const getCurrentCarbonDatasetFootprint = function () { + return carbonDatasets[_currentCarbonDatasetCode].footprintData; +}; + +export const getCustomMETs = function () { + console.log('Getting custom METs', _customMETs); + return _customMETs; +}; + +export const getCustomFootprint = function () { + console.log('Getting custom footprint', _customPerKmFootprint); + return _customPerKmFootprint; +}; + +const populateCustomMETs = function () { + let modeOptions = _inputParams['MODE']; + let modeMETEntries = modeOptions.map((opt) => { + if (opt.met_equivalent) { + let currMET = standardMETs[opt.met_equivalent]; + return [opt.value, currMET]; + } else { + if (opt.met) { + let currMET = opt.met; + // if the user specifies a custom MET, they can't specify + // Number.MAX_VALUE since it is not valid JSON + // we assume that they specify -1 instead, and we will + // map -1 to Number.MAX_VALUE here by iterating over all the ranges + for (const rangeName in currMET) { + // console.log("Handling range ", rangeName); + currMET[rangeName].range = currMET[rangeName].range.map((i) => + i == -1 ? Number.MAX_VALUE : i, + ); + } + return [opt.value, currMET]; + } else { + console.warn( + 'Did not find either met_equivalent or met for ' + opt.value + ' ignoring entry', + ); + return undefined; + } + } + }); + _customMETs = Object.fromEntries(modeMETEntries.filter((e) => angular.isDefined(e))); + console.log('After populating, custom METs = ', _customMETs); +}; + +const populateCustomFootprints = function () { + let modeOptions = _inputParams['MODE']; + let modeCO2PerKm = modeOptions + .map((opt) => { + if (opt.range_limit_km) { + if (_range_limited_motorized) { + displayError( + { first: _range_limited_motorized, second: opt }, + 'Found two range limited motorized options', + ); + } + _range_limited_motorized = opt; + console.log('Found range limited motorized mode', _range_limited_motorized); + } + if (angular.isDefined(opt.kgCo2PerKm)) { + return [opt.value, opt.kgCo2PerKm]; + } else { + return undefined; + } + }) + .filter((modeCO2) => angular.isDefined(modeCO2)); + _customPerKmFootprint = Object.fromEntries(modeCO2PerKm); + console.log('After populating, custom perKmFootprint', _customPerKmFootprint); +}; + +const initCustomDatasetHelper = async function (newConfig) { + newConfig = await getConfig(); + try { + getLabelOptions(newConfig).then((inputParams) => { + console.log('Input params = ', inputParams); + _inputParams = inputParams; + populateCustomMETs(); + populateCustomFootprints(); + }); + } catch (e) { + setTimeout(() => { + displayError(e, 'Error while initializing custom dataset helper'); + }, 1000); + } +}; diff --git a/www/js/metrics/METDataset.ts b/www/js/metrics/METDataset.ts new file mode 100644 index 000000000..901c17ae6 --- /dev/null +++ b/www/js/metrics/METDataset.ts @@ -0,0 +1,128 @@ +export const standardMETs = { + WALKING: { + VERY_SLOW: { + range: [0, 2.0], + mets: 2.0, + }, + SLOW: { + range: [2.0, 2.5], + mets: 2.8, + }, + MODERATE_0: { + range: [2.5, 2.8], + mets: 3.0, + }, + MODERATE_1: { + range: [2.8, 3.2], + mets: 3.5, + }, + FAST: { + range: [3.2, 3.5], + mets: 4.3, + }, + VERY_FAST_0: { + range: [3.5, 4.0], + mets: 5.0, + }, + 'VERY_FAST_!': { + range: [4.0, 4.5], + mets: 6.0, + }, + VERY_VERY_FAST: { + range: [4.5, 5], + mets: 7.0, + }, + SUPER_FAST: { + range: [5, 6], + mets: 8.3, + }, + RUNNING: { + range: [6, Number.MAX_VALUE], + mets: 9.8, + }, + }, + BICYCLING: { + VERY_VERY_SLOW: { + range: [0, 5.5], + mets: 3.5, + }, + VERY_SLOW: { + range: [5.5, 10], + mets: 5.8, + }, + SLOW: { + range: [10, 12], + mets: 6.8, + }, + MODERATE: { + range: [12, 14], + mets: 8.0, + }, + FAST: { + range: [14, 16], + mets: 10.0, + }, + VERT_FAST: { + range: [16, 19], + mets: 12.0, + }, + RACING: { + range: [20, Number.MAX_VALUE], + mets: 15.8, + }, + }, + UNKNOWN: { + ALL: { + range: [0, Number.MAX_VALUE], + mets: 0, + }, + }, + IN_VEHICLE: { + ALL: { + range: [0, Number.MAX_VALUE], + mets: 0, + }, + }, + CAR: { + ALL: { + range: [0, Number.MAX_VALUE], + mets: 0, + }, + }, + BUS: { + ALL: { + range: [0, Number.MAX_VALUE], + mets: 0, + }, + }, + LIGHT_RAIL: { + ALL: { + range: [0, Number.MAX_VALUE], + mets: 0, + }, + }, + TRAIN: { + ALL: { + range: [0, Number.MAX_VALUE], + mets: 0, + }, + }, + TRAM: { + ALL: { + range: [0, Number.MAX_VALUE], + mets: 0, + }, + }, + SUBWAY: { + ALL: { + range: [0, Number.MAX_VALUE], + mets: 0, + }, + }, + AIR_OR_HSR: { + ALL: { + range: [0, Number.MAX_VALUE], + mets: 0, + }, + }, +}; diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts new file mode 100644 index 000000000..763e067a2 --- /dev/null +++ b/www/js/metrics/footprintHelper.ts @@ -0,0 +1,161 @@ +import { getCustomFootprint } from './CustomMetricsHelper'; +import { getCurrentCarbonDatasetFootprint } from './CustomMetricsHelper'; + +var highestFootprint = 0; + +var mtokm = function (v) { + return v / 1000; +}; +let useCustom = false; + +export const setUseCustomFootprint = function () { + useCustom = true; +}; + +const getFootprint = function () { + if (useCustom == true) { + return getCustomFootprint(); + } else { + return getCurrentCarbonDatasetFootprint(); + } +}; + +const readableFormat = function (v) { + return v > 999 ? Math.round(v / 1000) + 'k kg CO₂' : Math.round(v) + ' kg CO₂'; +}; + +export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = 0) { + var footprint = getFootprint(); + var result = 0; + for (var i in userMetrics) { + var mode = userMetrics[i].key; + if (mode == 'ON_FOOT') { + mode = 'WALKING'; + } + + if (mode in footprint) { + result += footprint[mode] * mtokm(userMetrics[i].values); + } else if (mode == 'IN_VEHICLE') { + result += + ((footprint['CAR'] + + footprint['BUS'] + + footprint['LIGHT_RAIL'] + + footprint['TRAIN'] + + footprint['TRAM'] + + footprint['SUBWAY']) / + 6) * + mtokm(userMetrics[i].values); + } else { + console.warn( + 'WARNING getFootprintFromMetrics() was requested for an unknown mode: ' + + mode + + ' metrics JSON: ' + + JSON.stringify(userMetrics), + ); + result += defaultIfMissing * mtokm(userMetrics[i].values); + } + } + return result; +}; + +const getLowestFootprintForDistance = function (distance) { + var footprint = getFootprint(); + var lowestFootprint = Number.MAX_SAFE_INTEGER; + for (var mode in footprint) { + if (mode == 'WALKING' || mode == 'BICYCLING') { + // these modes aren't considered when determining the lowest carbon footprint + } else { + lowestFootprint = Math.min(lowestFootprint, footprint[mode]); + } + } + return lowestFootprint * mtokm(distance); +}; + +export const getHighestFootprint = function () { + if (!highestFootprint) { + var footprint = getFootprint(); + let footprintList = []; + for (var mode in footprint) { + footprintList.push(footprint[mode]); + } + highestFootprint = Math.max(...footprintList); + } + return highestFootprint; +}; + +export const getHighestFootprintForDistance = function (distance) { + return getHighestFootprint() * mtokm(distance); +}; + +const getLowestMotorizedNonAirFootprint = function (footprint, rlmCO2) { + var lowestFootprint = Number.MAX_SAFE_INTEGER; + for (var mode in footprint) { + if (mode == 'AIR_OR_HSR' || mode == 'air') { + console.log('Air mode, ignoring'); + } else { + if (footprint[mode] == 0 || footprint[mode] <= rlmCO2) { + console.log( + 'Non motorized mode or footprint <= range_limited_motorized', + mode, + footprint[mode], + rlmCO2, + ); + } else { + lowestFootprint = Math.min(lowestFootprint, footprint[mode]); + } + } + } + return lowestFootprint; +}; + +const getOptimalDistanceRanges = function () { + const FIVE_KM = 5 * 1000; + const SIX_HUNDRED_KM = 600 * 1000; + if (!useCustom) { + const defaultFootprint = getCurrentCarbonDatasetFootprint(); + const lowestMotorizedNonAir = getLowestMotorizedNonAirFootprint(defaultFootprint); + const airFootprint = defaultFootprint['AIR_OR_HSR']; + return [ + { low: 0, high: FIVE_KM, optimal: 0 }, + { low: FIVE_KM, high: SIX_HUNDRED_KM, optimal: lowestMotorizedNonAir }, + { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint }, + ]; + } else { + // custom footprint, let's get the custom values + const customFootprint = getCustomFootprint(); + let airFootprint = customFootprint['air']; + if (!airFootprint) { + // 2341 BTU/PMT from + // https://tedb.ornl.gov/wp-content/uploads/2021/02/TEDB_Ed_39.pdf#page=68 + // 159.25 lb per million BTU from EIA + // https://www.eia.gov/environment/emissions/co2_vol_mass.php + // (2341 * (159.25/1000000))/(1.6*2.2) = 0.09975, rounded up a bit + console.log('No entry for air in ', customFootprint, ' using default'); + airFootprint = 0.1; + } + const rlm = CustomDatasetHelper.range_limited_motorized; + if (!rlm) { + return [ + { low: 0, high: FIVE_KM, optimal: 0 }, + { low: FIVE_KM, high: SIX_HUNDRED_KM, optimal: lowestMotorizedNonAir }, + { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint }, + ]; + } else { + console.log('Found range_limited_motorized mode', rlm); + const lowestMotorizedNonAir = getLowestMotorizedNonAirFootprint( + customFootprint, + rlm.kgCo2PerKm, + ); + return [ + { low: 0, high: FIVE_KM, optimal: 0 }, + { low: FIVE_KM, high: rlm.range_limit_km * 1000, optimal: rlm.kgCo2PerKm }, + { + low: rlm.range_limit_km * 1000, + high: SIX_HUNDRED_KM, + optimal: lowestMotorizedNonAir, + }, + { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint }, + ]; + } + } +}; diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts new file mode 100644 index 000000000..8f8b4df85 --- /dev/null +++ b/www/js/metrics/metHelper.ts @@ -0,0 +1,109 @@ +import { getCustomMETs } from './customMetricsHelper'; +import { standardMETs } from './metDataset'; +import { storageGet, storageSet, storageRemove } from '../plugin/storage'; + +var highestMET = 0; +var USER_DATA_KEY = 'user-data'; +let useCustom = false; + +const setUseCustomFootprint = function () { + useCustom = true; +}; + +const getMETs = function () { + if (useCustom == true) { + return getCustomMETs(); + } else { + return standardMETs; + } +}; + +const set = function (info) { + return storageSet(USER_DATA_KEY, info); +}; + +const get = function () { + return storageGet(USER_DATA_KEY); +}; + +const remove = function () { + return storageRemove(USER_DATA_KEY); +}; + +const between = function (num, min, max) { + return num >= min && num <= max; +}; + +const getHighestMET = function () { + if (!highestMET) { + var met = getMETs(); + let metList = []; + for (var mode in met) { + var rangeList = met[mode]; + for (var range in rangeList) { + metList.push(rangeList[range].mets); + } + } + highestMET = Math.max(...metList); + } + return highestMET; +}; + +const getMet = function (mode, speed, defaultIfMissing) { + if (mode == 'ON_FOOT') { + console.log("getMet() converted 'ON_FOOT' to 'WALKING'"); + mode = 'WALKING'; + } + let currentMETs = getMETs(); + if (!currentMETs[mode]) { + console.warn('getMet() Illegal mode: ' + mode); + return defaultIfMissing; //So the calorie sum does not break with wrong return type + } + for (var i in currentMETs[mode]) { + if (between(mpstomph(speed), currentMETs[mode][i].range[0], currentMETs[mode][i].range[1])) { + return currentMETs[mode][i].mets; + } else if (mpstomph(speed) < 0) { + console.log('getMet() Negative speed: ' + mpstomph(speed)); + return 0; + } + } +}; + +const mpstomph = function (mps) { + return 2.23694 * mps; +}; + +const lbtokg = function (lb) { + return lb * 0.453592; +}; + +const fttocm = function (ft) { + return ft * 30.48; +}; + +const getCorrectedMet = function (met, gender, age, height, heightUnit, weight, weightUnit) { + var height = heightUnit == 0 ? fttocm(height) : height; + var weight = weightUnit == 0 ? lbtokg(weight) : weight; + let calcMet; + if (gender == 1) { + //male + calcMet = + (met * 3.5) / + (((66.473 + 5.0033 * height + 13.7516 * weight - 6.755 * age) / 1440 / 5 / weight) * 1000); + return met; + } else if (gender == 0) { + //female + let met = + (calcMet * 3.5) / + (((655.0955 + 1.8496 * height + 9.5634 * weight - 4.6756 * age) / 1440 / 5 / weight) * 1000); + return calcMet; + } +}; + +const getuserCalories = function (durationInMin, met) { + return 65 * durationInMin * met; +}; + +const getCalories = function (weightInKg, durationInMin, met) { + return weightInKg * durationInMin * met; +}; From 70f7238b232006c3e1ae5a40d4d30a195691ae23 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 8 Nov 2023 15:55:29 -0700 Subject: [PATCH 02/40] display message instead of error I was using the wrong function when I first converted --- www/js/metrics/CustomMetricsHelper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/CustomMetricsHelper.ts index 062f6a2ca..7d87f0344 100644 --- a/www/js/metrics/CustomMetricsHelper.ts +++ b/www/js/metrics/CustomMetricsHelper.ts @@ -2,7 +2,7 @@ import angular from 'angular'; import { getLabelOptions } from '../survey/multilabel/confirmHelper'; import { getConfig } from '../config/dynamicConfig'; import { storageGet, storageSet } from '../plugin/storage'; -import { displayError, logDebug } from '../plugin/logger'; +import { displayError, displayErrorMsg, logDebug } from '../plugin/logger'; import { standardMETs } from './metDataset'; import { carbonDatasets } from './carbonDatasets'; @@ -112,8 +112,8 @@ const populateCustomFootprints = function () { .map((opt) => { if (opt.range_limit_km) { if (_range_limited_motorized) { - displayError( - { first: _range_limited_motorized, second: opt }, + displayErrorMsg( + JSON.stringify({ first: _range_limited_motorized, second: opt }), 'Found two range limited motorized options', ); } From 5108352e77ad04bdb0f563c2a8f74a63cb611096 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 8 Nov 2023 15:56:12 -0700 Subject: [PATCH 03/40] reduce naming confusion there is a similar function for the footprint, lets make sure they have different names --- www/js/metrics/metHelper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index 8f8b4df85..6c052b0be 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -6,7 +6,7 @@ var highestMET = 0; var USER_DATA_KEY = 'user-data'; let useCustom = false; -const setUseCustomFootprint = function () { +const setUseCustomMET = function () { useCustom = true; }; From 9118133e23ae2298779d23315020c388c8be7d84 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 8 Nov 2023 19:13:30 -0700 Subject: [PATCH 04/40] write footprinthelper tests first pass of tests over the footprintHelper getFootprintForMetrics is one of the main functions, used to calculate displayed values for the dashboard screen --- www/__mocks__/cordovaMocks.ts | 22 ++- www/__mocks__/fakeConfig.json | 88 +++++++++ www/__mocks__/fakeLabels.json | 211 ++++++++++++++++++++++ www/__tests__/footprintHelper.test.ts | 64 +++++++ www/js/metrics/CustomMetricsHelper.ts | 2 +- www/js/metrics/footprintHelper.ts | 4 +- www/js/survey/multilabel/confirmHelper.ts | 3 +- 7 files changed, 385 insertions(+), 9 deletions(-) create mode 100644 www/__mocks__/fakeConfig.json create mode 100644 www/__mocks__/fakeLabels.json create mode 100644 www/__tests__/footprintHelper.test.ts diff --git a/www/__mocks__/cordovaMocks.ts b/www/__mocks__/cordovaMocks.ts index 62aa9be1a..ad69b52fe 100644 --- a/www/__mocks__/cordovaMocks.ts +++ b/www/__mocks__/cordovaMocks.ts @@ -1,4 +1,5 @@ import packageJsonBuild from '../../package.cordovabuild.json'; +import fakeConfig from './fakeConfig.json'; export const mockCordova = () => { window['cordova'] ||= {}; @@ -99,11 +100,22 @@ export const mockBEMUserCache = () => { ); }, getDocument: (key: string, withMetadata?: boolean) => { - return new Promise((rs, rj) => - setTimeout(() => { - rs(_storage[key]); - }, 100), - ); + // this was mocked specifically for enketoHelper's use, could be expanded if needed + const fakeSurveyConfig = fakeConfig; + + if (key == 'config/app_ui_config') { + return new Promise((rs, rj) => + setTimeout(() => { + rs(fakeSurveyConfig); + }, 100), + ); + } else { + return new Promise((rs, rj) => + setTimeout(() => { + rs(_storage[key]); + }, 100), + ); + } }, isEmptyDoc: (doc) => { if (doc == undefined) { diff --git a/www/__mocks__/fakeConfig.json b/www/__mocks__/fakeConfig.json new file mode 100644 index 000000000..dabec6cd9 --- /dev/null +++ b/www/__mocks__/fakeConfig.json @@ -0,0 +1,88 @@ +{ + "version": 1, + "ts": 1655143472, + "server": { + "connectUrl": "https://openpath-test.nrel.gov/api/", + "aggregate_call_auth": "user_only" + }, + "intro": { + "program_or_study": "study", + "start_month": "10", + "start_year": "2023", + "program_admin_contact": "K. Shankari", + "deployment_partner_name": "NREL", + "translated_text": { + "en": { + "deployment_partner_name": "NREL", + "deployment_name": "Testing environment for Jest testing", + "summary_line_1": "", + "summary_line_2": "", + "summary_line_3": "", + "short_textual_description": "", + "why_we_collect": "", + "research_questions": ["", ""] + }, + "es": { + "deployment_partner_name": "NREL", + "deployment_name": "Ambiente prueba para las pruebas de Jest", + "summary_line_1": "", + "summary_line_2": "", + "summary_line_3": "", + "short_textual_description": "", + "why_we_collect": "", + "research_questions": ["", ""] + } + } + }, + "survey_info": { + "surveys": { + "TimeUseSurvey": { + "compatibleWith": 1, + "formPath": "https://raw.githubusercontent.com/sebastianbarry/nrel-openpath-deploy-configs/surveys-info-and-surveys-data/survey-resources/data-json/time-use-survey-form-v9.json", + "labelTemplate": { + "en": "{ erea, plural, =0 {} other {# Employment/Education, } }{ da, plural, =0 {} other {# Domestic, } }", + "es": "{ erea, plural, =0 {} other {# Empleo/Educación, } }{ da, plural, =0 {} other {# Actividades domesticas, }}" + }, + "labelVars": { + "da": { + "key": "Domestic_activities", + "type": "length" + }, + "erea": { + "key": "Employment_related_a_Education_activities", + "type": "length" + } + }, + "version": 9 + } + }, + "trip-labels": "ENKETO" + }, + "display_config": { + "use_imperial": false + }, + "profile_controls": { + "support_upload": true, + "trip_end_notification": false + }, + "admin_dashboard": { + "overview_users": true, + "overview_active_users": true, + "overview_trips": true, + "overview_signup_trends": true, + "overview_trips_trend": true, + "data_uuids": true, + "data_trips": true, + "data_trips_columns_exclude": [], + "additional_trip_columns": [], + "data_uuids_columns_exclude": [], + "token_generate": true, + "token_prefix": "nrelop", + "map_heatmap": true, + "map_bubble": true, + "map_trip_lines": true, + "push_send": true, + "options_uuids": true, + "options_emails": true + } +} diff --git a/www/__mocks__/fakeLabels.json b/www/__mocks__/fakeLabels.json new file mode 100644 index 000000000..2b1f3e824 --- /dev/null +++ b/www/__mocks__/fakeLabels.json @@ -0,0 +1,211 @@ +{ + "MODE": [ + { + "value": "walk", + "baseMode": "WALKING", + "met_equivalent": "WALKING", + "kgCo2PerKm": 0 + }, + { + "value": "e-bike", + "baseMode": "E_BIKE", + "met": { + "ALL": { + "range": [ + 0, + -1 + ], + "mets": 4.9 + } + }, + "kgCo2PerKm": 0.00728 + }, + { + "value": "bike", + "baseMode": "BICYCLING", + "met_equivalent": "BICYCLING", + "kgCo2PerKm": 0 + }, + { + "value": "bikeshare", + "baseMode": "BICYCLING", + "met_equivalent": "BICYCLING", + "kgCo2PerKm": 0 + }, + { + "value": "scootershare", + "baseMode": "E_SCOOTER", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.00894 + }, + { + "value": "drove_alone", + "baseMode": "CAR", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.22031 + }, + { + "value": "shared_ride", + "baseMode": "CAR", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.11015 + }, + { + "value": "hybrid_drove_alone", + "baseMode": "CAR", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.127 + }, + { + "value": "hybrid_shared_ride", + "baseMode": "CAR", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.0635 + }, + { + "value": "e_car_drove_alone", + "baseMode": "E_CAR", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.08216 + }, + { + "value": "e_car_shared_ride", + "baseMode": "E_CAR", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.04108 + }, + { + "value": "taxi", + "baseMode": "TAXI", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.30741 + }, + { + "value": "bus", + "baseMode": "BUS", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.20727 + }, + { + "value": "train", + "baseMode": "TRAIN", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.12256 + }, + { + "value": "free_shuttle", + "baseMode": "BUS", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.20727 + }, + { + "value": "air", + "baseMode": "AIR", + "met_equivalent": "IN_VEHICLE", + "kgCo2PerKm": 0.09975 + }, + { + "value": "not_a_trip", + "baseMode": "UNKNOWN", + "met_equivalent": "UNKNOWN", + "kgCo2PerKm": 0 + }, + { + "value": "other", + "baseMode": "OTHER", + "met_equivalent": "UNKNOWN", + "kgCo2PerKm": 0 + } + ], + "PURPOSE": [ + { + "value": "home" + }, + { + "value": "work" + }, + { + "value": "at_work" + }, + { + "value": "school" + }, + { + "value": "transit_transfer" + }, + { + "value": "shopping" + }, + { + "value": "meal" + }, + { + "value": "pick_drop_person" + }, + { + "value": "pick_drop_item" + }, + { + "value": "personal_med" + }, + { + "value": "access_recreation" + }, + { + "value": "exercise" + }, + { + "value": "entertainment" + }, + { + "value": "religious" + }, + { + "value": "other" + } + ], + "REPLACED_MODE": [ + { + "value": "no_travel" + }, + { + "value": "walk" + }, + { + "value": "bike" + }, + { + "value": "bikeshare" + }, + { + "value": "scootershare" + }, + { + "value": "drove_alone" + }, + { + "value": "shared_ride" + }, + { + "value": "e_car_drove_alone" + }, + { + "value": "e_car_shared_ride" + }, + { + "value": "taxi" + }, + { + "value": "bus" + }, + { + "value": "train" + }, + { + "value": "free_shuttle" + }, + { + "value": "other" + } + ] +} diff --git a/www/__tests__/footprintHelper.test.ts b/www/__tests__/footprintHelper.test.ts new file mode 100644 index 000000000..336e46c1d --- /dev/null +++ b/www/__tests__/footprintHelper.test.ts @@ -0,0 +1,64 @@ +import { initCustomDatasetHelper } from '../js/metrics/CustomMetricsHelper'; +import { + getFootprintForMetrics, + mtokm, + setUseCustomFootprint, +} from '../js/metrics/footprintHelper'; +import { getConfig } from '../js/config/dynamicConfig'; +import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; +import { mockLogger } from '../__mocks__/globalMocks'; +import fakeLabels from '../__mocks__/fakeLabels.json'; + +mockBEMUserCache(); +mockLogger(); + +global.fetch = (url: string) => + new Promise((rs, rj) => { + setTimeout(() => + rs({ + text: () => + new Promise((rs, rj) => { + let myJSON = JSON.stringify(fakeLabels); + setTimeout(() => rs(myJSON), 100); + }), + }), + ); + }) as any; + +const metrics = [ + { key: 'WALKING', values: 3000 }, + { key: 'BICYCLING', values: 6500 }, + { key: 'CAR', values: 50000 }, + { key: 'LIGHT_RAIL', values: 30000 }, + { key: 'Unicycle', values: 5000 }, +]; + +it('gets footprint for metrics (not custom, fallback 0', () => { + expect(getFootprintForMetrics(metrics, 0)).toBe(10.534493474207583); +}); + +it('gets footprint for metrics (not custom, fallback 0.1', () => { + expect(getFootprintForMetrics(metrics, 0.1)).toBe(10.534493474207583 + 0.5); +}); + +const custom_metrics = [ + { key: 'walk', values: 3000 }, + { key: 'bike', values: 6500 }, + { key: 'drove_alone', values: 10000 }, + { key: 'scootershare', values: 25000 }, + { key: 'unicycle', values: 5000 }, +]; + +it('gets footprint for metrics (custom, fallback 0', async () => { + initCustomDatasetHelper(getConfig()); + setUseCustomFootprint(); + await new Promise((r) => setTimeout(r, 500)); + expect(getFootprintForMetrics(custom_metrics, 0)).toBe(2.4266); +}); + +it('gets footprint for metrics (custom, fallback 0.1', async () => { + initCustomDatasetHelper(getConfig()); + setUseCustomFootprint(); + await new Promise((r) => setTimeout(r, 500)); + expect(getFootprintForMetrics(custom_metrics, 0.1)).toBe(2.4266 + 0.5); +}); diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/CustomMetricsHelper.ts index 7d87f0344..3d7eaf507 100644 --- a/www/js/metrics/CustomMetricsHelper.ts +++ b/www/js/metrics/CustomMetricsHelper.ts @@ -131,7 +131,7 @@ const populateCustomFootprints = function () { console.log('After populating, custom perKmFootprint', _customPerKmFootprint); }; -const initCustomDatasetHelper = async function (newConfig) { +export const initCustomDatasetHelper = async function (newConfig) { newConfig = await getConfig(); try { getLabelOptions(newConfig).then((inputParams) => { diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index 763e067a2..4725aa1c7 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -2,11 +2,11 @@ import { getCustomFootprint } from './CustomMetricsHelper'; import { getCurrentCarbonDatasetFootprint } from './CustomMetricsHelper'; var highestFootprint = 0; +let useCustom = false; -var mtokm = function (v) { +const mtokm = function (v) { return v / 1000; }; -let useCustom = false; export const setUseCustomFootprint = function () { useCustom = true; diff --git a/www/js/survey/multilabel/confirmHelper.ts b/www/js/survey/multilabel/confirmHelper.ts index a8972709b..6bcd85a50 100644 --- a/www/js/survey/multilabel/confirmHelper.ts +++ b/www/js/survey/multilabel/confirmHelper.ts @@ -35,7 +35,7 @@ export let inputDetails: InputDetails<'MODE' | 'PURPOSE' | 'REPLACED_MODE'>; export async function getLabelOptions(appConfigParam?) { if (appConfigParam) appConfig = appConfigParam; if (labelOptions) return labelOptions; - + console.log('in get label options', appConfig); if (appConfig.label_options) { const labelOptionsJson = await fetchUrlCached(appConfig.label_options); logDebug( @@ -48,6 +48,7 @@ export async function getLabelOptions(appConfigParam?) { 'No label_options found in config, using default label options at ' + defaultLabelOptionsURL, ); const defaultLabelOptionsJson = await fetchUrlCached(defaultLabelOptionsURL); + console.log('label options', defaultLabelOptionsJson); labelOptions = JSON.parse(defaultLabelOptionsJson) as LabelOptions; } /* fill in the translations to the 'text' fields of the labelOptions, From 2cdd212c778320fb76cfb151545389b2012c8e09 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 8 Nov 2023 19:15:29 -0700 Subject: [PATCH 05/40] fix formatting --- www/__mocks__/fakeLabels.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/www/__mocks__/fakeLabels.json b/www/__mocks__/fakeLabels.json index 2b1f3e824..676dc97b6 100644 --- a/www/__mocks__/fakeLabels.json +++ b/www/__mocks__/fakeLabels.json @@ -11,10 +11,7 @@ "baseMode": "E_BIKE", "met": { "ALL": { - "range": [ - 0, - -1 - ], + "range": [0, -1], "mets": 4.9 } }, From 1a734f766a887a13006f6c1d67281a46d60fd441 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 8 Nov 2023 19:15:44 -0700 Subject: [PATCH 06/40] remove unused import --- www/__tests__/footprintHelper.test.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/www/__tests__/footprintHelper.test.ts b/www/__tests__/footprintHelper.test.ts index 336e46c1d..07c0a1264 100644 --- a/www/__tests__/footprintHelper.test.ts +++ b/www/__tests__/footprintHelper.test.ts @@ -1,9 +1,5 @@ import { initCustomDatasetHelper } from '../js/metrics/CustomMetricsHelper'; -import { - getFootprintForMetrics, - mtokm, - setUseCustomFootprint, -} from '../js/metrics/footprintHelper'; +import { getFootprintForMetrics, setUseCustomFootprint } from '../js/metrics/footprintHelper'; import { getConfig } from '../js/config/dynamicConfig'; import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; From 1d7bf5b2bc42c6e55556bdcfd8aa00f84586ef7a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 09:13:35 -0700 Subject: [PATCH 07/40] remove country-specific since we've changed the way fallback labels are handled, everything should be custom now. This means that we're not using the carbon values from the set country, but rather from the labels --- www/__tests__/footprintHelper.test.ts | 4 +- www/js/metrics/CarbonDatasets.ts | 123 ------------------------ www/js/metrics/CustomMetricsHelper.ts | 74 +++----------- www/js/metrics/carbonDatasetFallback.ts | 14 +++ www/js/metrics/footprintHelper.ts | 12 +-- 5 files changed, 36 insertions(+), 191 deletions(-) delete mode 100644 www/js/metrics/CarbonDatasets.ts create mode 100644 www/js/metrics/carbonDatasetFallback.ts diff --git a/www/__tests__/footprintHelper.test.ts b/www/__tests__/footprintHelper.test.ts index 07c0a1264..5267bc858 100644 --- a/www/__tests__/footprintHelper.test.ts +++ b/www/__tests__/footprintHelper.test.ts @@ -29,11 +29,11 @@ const metrics = [ { key: 'Unicycle', values: 5000 }, ]; -it('gets footprint for metrics (not custom, fallback 0', () => { +it('gets footprint for metrics (not custom, fallback 0)', () => { expect(getFootprintForMetrics(metrics, 0)).toBe(10.534493474207583); }); -it('gets footprint for metrics (not custom, fallback 0.1', () => { +it('gets footprint for metrics (not custom, fallback 0.1)', () => { expect(getFootprintForMetrics(metrics, 0.1)).toBe(10.534493474207583 + 0.5); }); diff --git a/www/js/metrics/CarbonDatasets.ts b/www/js/metrics/CarbonDatasets.ts deleted file mode 100644 index b4815eead..000000000 --- a/www/js/metrics/CarbonDatasets.ts +++ /dev/null @@ -1,123 +0,0 @@ -// Values are in Kg/PKm (kilograms per passenger-kilometer) -// Sources for EU values: -// - Tremod: 2017, CO2, CH4 and N2O in CO2-equivalent -// - HBEFA: 2020, CO2 (per country) -// German data uses Tremod. Other EU countries (and Switzerland) use HBEFA for car and bus, -// and Tremod for train and air (because HBEFA doesn't provide these). -// EU data is an average of the Tremod/HBEFA data for the countries listed; -// for this average the HBEFA data was used also in the German set (for car and bus). -export const carbonDatasets = { - US: { - regionName: 'United States', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 267 / 1609, - BUS: 278 / 1609, - LIGHT_RAIL: 120 / 1609, - SUBWAY: 74 / 1609, - TRAM: 90 / 1609, - TRAIN: 92 / 1609, - AIR_OR_HSR: 217 / 1609, - }, - }, - EU: { - // Plain average of values for the countries below (using HBEFA for car and bus, Tremod for others) - regionName: 'European Union', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.14515, - BUS: 0.04751, - LIGHT_RAIL: 0.064, - SUBWAY: 0.064, - TRAM: 0.064, - TRAIN: 0.048, - AIR_OR_HSR: 0.201, - }, - }, - DE: { - regionName: 'Germany', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.139, // Tremod (passenger car) - BUS: 0.0535, // Tremod (average city/coach) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - FR: { - regionName: 'France', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.13125, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04838, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - AT: { - regionName: 'Austria', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.14351, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04625, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - SE: { - regionName: 'Sweden', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.13458, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04557, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - NO: { - regionName: 'Norway', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.13265, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04185, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, - CH: { - regionName: 'Switzerland', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 0.17638, // HBEFA (passenger car, considering 1 passenger) - BUS: 0.04866, // HBEFA (average short/long distance, considering 16/25 passengers) - LIGHT_RAIL: 0.064, // Tremod (DE tram, urban rail and subway) - SUBWAY: 0.064, // Tremod (DE tram, urban rail and subway) - TRAM: 0.064, // Tremod (DE tram, urban rail and subway) - TRAIN: 0.048, // Tremod (DE average short/long distance) - AIR_OR_HSR: 0.201, // Tremod (DE airplane) - }, - }, -}; diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/CustomMetricsHelper.ts index 3d7eaf507..11493cb95 100644 --- a/www/js/metrics/CustomMetricsHelper.ts +++ b/www/js/metrics/CustomMetricsHelper.ts @@ -1,79 +1,35 @@ import angular from 'angular'; import { getLabelOptions } from '../survey/multilabel/confirmHelper'; import { getConfig } from '../config/dynamicConfig'; -import { storageGet, storageSet } from '../plugin/storage'; import { displayError, displayErrorMsg, logDebug } from '../plugin/logger'; import { standardMETs } from './metDataset'; -import { carbonDatasets } from './carbonDatasets'; - -const CARBON_DATASET_KEY = 'carbon_dataset_locale'; -const defaultCarbonDatasetCode = 'US'; +import { fallbackCarbon } from './carbonDatasetFallback'; let _customMETs; let _customPerKmFootprint; let _range_limited_motorized; let _inputParams; -let _currentCarbonDatasetCode = defaultCarbonDatasetCode; - -// we need to call the method from within a promise in initialize() -// and using this.setCurrentCarbonDatasetLocale doesn't seem to work -const setCurrentCarbonDatasetLocale = function (localeCode) { - for (var code in carbonDatasets) { - if (code == localeCode) { - _currentCarbonDatasetCode = localeCode; - break; - } - } -}; - -const loadCarbonDatasetLocale = function () { - return storageGet(CARBON_DATASET_KEY).then(function (localeCode) { - logDebug('loadCarbonDatasetLocale() obtained value from storage [' + localeCode + ']'); - if (!localeCode) { - localeCode = defaultCarbonDatasetCode; - logDebug('loadCarbonDatasetLocale() no value in storage, using [' + localeCode + '] instead'); - } - setCurrentCarbonDatasetLocale(localeCode); - }); -}; - -export const saveCurrentCarbonDatasetLocale = function (localeCode) { - setCurrentCarbonDatasetLocale(localeCode); - storageSet(CARBON_DATASET_KEY, _currentCarbonDatasetCode); - logDebug( - 'saveCurrentCarbonDatasetLocale() saved value [' + _currentCarbonDatasetCode + '] to storage', - ); -}; - -export const getCarbonDatasetOptions = function () { - var options = []; - for (var code in carbonDatasets) { - options.push({ - text: code, //carbonDatasets[code].regionName, - value: code, - }); - } - return options; -}; - -export const getCurrentCarbonDatasetCode = function () { - return _currentCarbonDatasetCode; -}; - -export const getCurrentCarbonDatasetFootprint = function () { - return carbonDatasets[_currentCarbonDatasetCode].footprintData; -}; export const getCustomMETs = function () { - console.log('Getting custom METs', _customMETs); + logDebug('Getting custom METs' + JSON.stringify(_customMETs)); return _customMETs; }; export const getCustomFootprint = function () { - console.log('Getting custom footprint', _customPerKmFootprint); + logDebug('Getting custom footprint' + JSON.stringify(_customPerKmFootprint)); return _customPerKmFootprint; }; +export const getRangeLimitedMotorixe = function () { + logDebug('Getting range limited motorized' + JSON.stringify(_range_limited_motorized)); + return _range_limited_motorized; +}; + +export const getFallbackFootprint = function () { + console.log('getting fallback carbon'); + return fallbackCarbon.footprintData; +}; + const populateCustomMETs = function () { let modeOptions = _inputParams['MODE']; let modeMETEntries = modeOptions.map((opt) => { @@ -103,7 +59,7 @@ const populateCustomMETs = function () { } }); _customMETs = Object.fromEntries(modeMETEntries.filter((e) => angular.isDefined(e))); - console.log('After populating, custom METs = ', _customMETs); + logDebug('After populating, custom METs = ' + JSON.stringify(_customMETs)); }; const populateCustomFootprints = function () { @@ -128,7 +84,7 @@ const populateCustomFootprints = function () { }) .filter((modeCO2) => angular.isDefined(modeCO2)); _customPerKmFootprint = Object.fromEntries(modeCO2PerKm); - console.log('After populating, custom perKmFootprint', _customPerKmFootprint); + logDebug('After populating, custom perKmFootprint' + JSON.stringify(_customPerKmFootprint)); }; export const initCustomDatasetHelper = async function (newConfig) { diff --git a/www/js/metrics/carbonDatasetFallback.ts b/www/js/metrics/carbonDatasetFallback.ts new file mode 100644 index 000000000..4561d078a --- /dev/null +++ b/www/js/metrics/carbonDatasetFallback.ts @@ -0,0 +1,14 @@ +export const fallbackCarbon = { + regionName: 'United States', + footprintData: { + WALKING: 0, + BICYCLING: 0, + CAR: 267 / 1609, + BUS: 278 / 1609, + LIGHT_RAIL: 120 / 1609, + SUBWAY: 74 / 1609, + TRAM: 90 / 1609, + TRAIN: 92 / 1609, + AIR_OR_HSR: 217 / 1609, + }, +}; diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index 4725aa1c7..2162e87bf 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -1,5 +1,5 @@ -import { getCustomFootprint } from './CustomMetricsHelper'; -import { getCurrentCarbonDatasetFootprint } from './CustomMetricsHelper'; +import { displayErrorMsg } from '../plugin/logger'; +import { getCustomFootprint, getFallbackFootprint } from './CustomMetricsHelper'; var highestFootprint = 0; let useCustom = false; @@ -16,14 +16,12 @@ const getFootprint = function () { if (useCustom == true) { return getCustomFootprint(); } else { - return getCurrentCarbonDatasetFootprint(); + //TODO: check through configs and ensure they all have custom lables + displayErrorMsg('Error in Footprint Calculatons', 'issue with data or default labels'); + return getFallbackFootprint(); } }; -const readableFormat = function (v) { - return v > 999 ? Math.round(v / 1000) + 'k kg CO₂' : Math.round(v) + ' kg CO₂'; -}; - export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = 0) { var footprint = getFootprint(); var result = 0; From 1eec588f8e3c231830d0c10402f66049972523d8 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 09:39:49 -0700 Subject: [PATCH 08/40] remove stale references after we take out the angular modules, we need to take out all the references to them as well --- www/js/main.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/www/js/main.js b/www/js/main.js index 2b351e2c4..2c789891a 100644 --- a/www/js/main.js +++ b/www/js/main.js @@ -7,8 +7,6 @@ angular 'emission.main.diary', 'emission.i18n.utils', 'emission.splash.notifscheduler', - 'emission.main.metrics.factory', - 'emission.main.metrics.mappings', 'emission.services', ]) From 6102d62e43ef646f150ed995ad05145de570b96b Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 09:43:19 -0700 Subject: [PATCH 09/40] remove dataset setting since we no longer rely on a set country for carbon calculations, but rather custom or default label configurations, we can take out the option to set it from the profile --- www/js/control/ProfileSettings.jsx | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/www/js/control/ProfileSettings.jsx b/www/js/control/ProfileSettings.jsx index b8943a81c..894a47bee 100644 --- a/www/js/control/ProfileSettings.jsx +++ b/www/js/control/ProfileSettings.jsx @@ -26,11 +26,6 @@ import ControlCollectionHelper, { helperToggleLowAccuracy, forceTransition, } from './ControlCollectionHelper'; -import { - getCarbonDatasetOptions, - getCurrentCarbonDatasetCode, - saveCurrentCarbonDatasetLocale, -} from '../metrics/customMetricsHelper'; import { resetDataAndRefresh } from '../config/dynamicConfig'; import { AppContext } from '../App'; import { shareQR } from '../components/QrCode'; @@ -59,7 +54,6 @@ const ProfileSettings = () => { //states and variables used to control/create the settings const [opCodeVis, setOpCodeVis] = useState(false); const [nukeSetVis, setNukeVis] = useState(false); - const [carbonDataVis, setCarbonDataVis] = useState(false); const [forceStateVis, setForceStateVis] = useState(false); const [logoutVis, setLogoutVis] = useState(false); const [invalidateSuccessVis, setInvalidateSuccessVis] = useState(false); @@ -87,9 +81,6 @@ const ProfileSettings = () => { const [uploadReason, setUploadReason] = useState(''); const appVersion = useRef(); - let carbonDatasetString = - t('general-settings.carbon-dataset') + ': ' + getCurrentCarbonDatasetCode(); - const carbonOptions = getCarbonDatasetOptions(); const stateActions = [ { text: 'Initialize', transition: 'INITIALIZE' }, { text: 'Start trip', transition: 'EXITED_GEOFENCE' }, @@ -363,14 +354,6 @@ const ProfileSettings = () => { forceTransition(stateObject.transition); }; - const onSelectCarbon = function (carbonObject) { - console.log('changeCarbonDataset(): chose locale ' + carbonObject.value); - saveCurrentCarbonDatasetLocale(carbonObject.value); //there's some sort of error here - //Unhandled Promise Rejection: While logging, error -[NSNull UTF8String]: unrecognized selector sent to instance 0x7fff8a625fb0 - carbonDatasetString = - i18next.t('general-settings.carbon-dataset') + ': ' + getCurrentCarbonDatasetCode(); - }; - //conditional creation of setting sections let logUploadSection; @@ -441,10 +424,6 @@ const ProfileSettings = () => { textKey="control.medium-accuracy" action={toggleLowAccuracy} switchValue={collectSettings.lowAccuracy}> - setCarbonDataVis(true)}> { - {/* menu for "set carbon dataset - only somewhat working" */} - clearNotifications()}> - {/* force state sheet */} Date: Thu, 9 Nov 2023 09:51:06 -0700 Subject: [PATCH 10/40] increase message readability --- www/js/metrics/CustomMetricsHelper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/CustomMetricsHelper.ts index 11493cb95..088ec8831 100644 --- a/www/js/metrics/CustomMetricsHelper.ts +++ b/www/js/metrics/CustomMetricsHelper.ts @@ -11,17 +11,17 @@ let _range_limited_motorized; let _inputParams; export const getCustomMETs = function () { - logDebug('Getting custom METs' + JSON.stringify(_customMETs)); + logDebug('Getting custom METs ' + JSON.stringify(_customMETs)); return _customMETs; }; export const getCustomFootprint = function () { - logDebug('Getting custom footprint' + JSON.stringify(_customPerKmFootprint)); + logDebug('Getting custom footprint ' + JSON.stringify(_customPerKmFootprint)); return _customPerKmFootprint; }; export const getRangeLimitedMotorixe = function () { - logDebug('Getting range limited motorized' + JSON.stringify(_range_limited_motorized)); + logDebug('Getting range limited motorized ' + JSON.stringify(_range_limited_motorized)); return _range_limited_motorized; }; From ec4abff2a35ea478c78d0750fe9b262ed8f3458a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 09:52:03 -0700 Subject: [PATCH 11/40] ensure initialization of footprints we need to initialize the footprints before we can call them, accomplish this by calling the initialization function from the metrics tab once the app config is loaded --- www/js/metrics/MetricsTab.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/www/js/metrics/MetricsTab.tsx b/www/js/metrics/MetricsTab.tsx index d23cdd454..6385e2fd3 100644 --- a/www/js/metrics/MetricsTab.tsx +++ b/www/js/metrics/MetricsTab.tsx @@ -17,6 +17,8 @@ import DailyActiveMinutesCard from './DailyActiveMinutesCard'; import CarbonTextCard from './CarbonTextCard'; import ActiveMinutesTableCard from './ActiveMinutesTableCard'; import { getAggregateData, getMetrics } from '../commHelper'; +import useAppConfig from '../useAppConfig'; +import { initCustomDatasetHelper } from './CustomMetricsHelper'; export const METRIC_LIST = ['duration', 'mean_speed', 'count', 'distance'] as const; @@ -41,6 +43,7 @@ function getLastTwoWeeksDtRange() { const MetricsTab = () => { const { t } = useTranslation(); + const appConfig = useAppConfig(); const { getFormattedSpeed, speedSuffix, getFormattedDistance, distanceSuffix } = useImperialConfig(); @@ -53,6 +56,12 @@ const MetricsTab = () => { loadMetricsForPopulation('aggregate', dateRange); }, [dateRange]); + //initialize once config is populated + useEffect(() => { + if (!appConfig) return; + initCustomDatasetHelper(appConfig); + }, [appConfig]); + async function loadMetricsForPopulation(population: 'user' | 'aggregate', dateRange: DateTime[]) { const serverResponse = await fetchMetricsFromServer(population, dateRange); console.debug('Got metrics = ', serverResponse); From 7820646de8433ab19c2eadbce5389095ac0a559a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 10:13:09 -0700 Subject: [PATCH 12/40] remove old MET code we no longer store user's height/weight/etc because we are not calculating calories burned, for that reason the code related to storing user's data or calculating calories is not needed we will keep MET handling so we can bin activity into high/med/low intensity later not currently using the "highestMET" calculation code, so commenting that out for now --- www/js/metrics/metHelper.ts | 87 ++++++++----------------------------- 1 file changed, 19 insertions(+), 68 deletions(-) diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index 6c052b0be..b696c5790 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -1,12 +1,9 @@ import { getCustomMETs } from './customMetricsHelper'; import { standardMETs } from './metDataset'; -import { storageGet, storageSet, storageRemove } from '../plugin/storage'; -var highestMET = 0; -var USER_DATA_KEY = 'user-data'; let useCustom = false; -const setUseCustomMET = function () { +export const setUseCustomMET = function () { useCustom = true; }; @@ -18,38 +15,15 @@ const getMETs = function () { } }; -const set = function (info) { - return storageSet(USER_DATA_KEY, info); -}; - -const get = function () { - return storageGet(USER_DATA_KEY); -}; - -const remove = function () { - return storageRemove(USER_DATA_KEY); -}; - const between = function (num, min, max) { return num >= min && num <= max; }; -const getHighestMET = function () { - if (!highestMET) { - var met = getMETs(); - let metList = []; - for (var mode in met) { - var rangeList = met[mode]; - for (var range in rangeList) { - metList.push(rangeList[range].mets); - } - } - highestMET = Math.max(...metList); - } - return highestMET; +const mpstomph = function (mps) { + return 2.23694 * mps; }; -const getMet = function (mode, speed, defaultIfMissing) { +export const getMet = function (mode, speed, defaultIfMissing) { if (mode == 'ON_FOOT') { console.log("getMet() converted 'ON_FOOT' to 'WALKING'"); mode = 'WALKING'; @@ -69,41 +43,18 @@ const getMet = function (mode, speed, defaultIfMissing) { } }; -const mpstomph = function (mps) { - return 2.23694 * mps; -}; - -const lbtokg = function (lb) { - return lb * 0.453592; -}; - -const fttocm = function (ft) { - return ft * 30.48; -}; - -const getCorrectedMet = function (met, gender, age, height, heightUnit, weight, weightUnit) { - var height = heightUnit == 0 ? fttocm(height) : height; - var weight = weightUnit == 0 ? lbtokg(weight) : weight; - let calcMet; - if (gender == 1) { - //male - calcMet = - (met * 3.5) / - (((66.473 + 5.0033 * height + 13.7516 * weight - 6.755 * age) / 1440 / 5 / weight) * 1000); - return met; - } else if (gender == 0) { - //female - let met = - (calcMet * 3.5) / - (((655.0955 + 1.8496 * height + 9.5634 * weight - 4.6756 * age) / 1440 / 5 / weight) * 1000); - return calcMet; - } -}; - -const getuserCalories = function (durationInMin, met) { - return 65 * durationInMin * met; -}; - -const getCalories = function (weightInKg, durationInMin, met) { - return weightInKg * durationInMin * met; -}; +// var highestMET = 0; +// const getHighestMET = function () { +// if (!highestMET) { +// var met = getMETs(); +// let metList = []; +// for (var mode in met) { +// var rangeList = met[mode]; +// for (var range in rangeList) { +// metList.push(rangeList[range].mets); +// } +// } +// highestMET = Math.max(...metList); +// } +// return highestMET; +// }; From 7ebcc94fd68ce97dceaa31a96f815fbc1cf61a5a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 10:51:35 -0700 Subject: [PATCH 13/40] add tests for highest footprint needed to include methods for clearing out the local variables, so added parameter to be able to set useCuston to false, and to clearHighest --- www/__tests__/footprintHelper.test.ts | 43 +++++++++++++++++++++++--- www/js/metrics/CarbonFootprintCard.tsx | 2 +- www/js/metrics/footprintHelper.ts | 8 +++-- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/www/__tests__/footprintHelper.test.ts b/www/__tests__/footprintHelper.test.ts index 5267bc858..fa0b4574d 100644 --- a/www/__tests__/footprintHelper.test.ts +++ b/www/__tests__/footprintHelper.test.ts @@ -1,5 +1,11 @@ import { initCustomDatasetHelper } from '../js/metrics/CustomMetricsHelper'; -import { getFootprintForMetrics, setUseCustomFootprint } from '../js/metrics/footprintHelper'; +import { + clearHighestFootprint, + getFootprintForMetrics, + getHighestFootprint, + getHighestFootprintForDistance, + setUseCustomFootprint, +} from '../js/metrics/footprintHelper'; import { getConfig } from '../js/config/dynamicConfig'; import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; @@ -21,6 +27,11 @@ global.fetch = (url: string) => ); }) as any; +beforeEach(() => { + setUseCustomFootprint(false); + clearHighestFootprint(); +}); + const metrics = [ { key: 'WALKING', values: 3000 }, { key: 'BICYCLING', values: 6500 }, @@ -37,6 +48,14 @@ it('gets footprint for metrics (not custom, fallback 0.1)', () => { expect(getFootprintForMetrics(metrics, 0.1)).toBe(10.534493474207583 + 0.5); }); +it('gets the highest footprint from the dataset, not custom', () => { + expect(getHighestFootprint()).toBe(278 / 1609); +}); + +it('gets the highest footprint for distance, not custom', () => { + expect(getHighestFootprintForDistance(12345)).toBe((278 / 1609) * (12345 / 1000)); +}); + const custom_metrics = [ { key: 'walk', values: 3000 }, { key: 'bike', values: 6500 }, @@ -45,16 +64,30 @@ const custom_metrics = [ { key: 'unicycle', values: 5000 }, ]; -it('gets footprint for metrics (custom, fallback 0', async () => { +it('gets footprint for metrics (custom, fallback 0)', async () => { initCustomDatasetHelper(getConfig()); - setUseCustomFootprint(); + setUseCustomFootprint(true); await new Promise((r) => setTimeout(r, 500)); expect(getFootprintForMetrics(custom_metrics, 0)).toBe(2.4266); }); -it('gets footprint for metrics (custom, fallback 0.1', async () => { +it('gets footprint for metrics (custom, fallback 0.1)', async () => { initCustomDatasetHelper(getConfig()); - setUseCustomFootprint(); + setUseCustomFootprint(true); await new Promise((r) => setTimeout(r, 500)); expect(getFootprintForMetrics(custom_metrics, 0.1)).toBe(2.4266 + 0.5); }); + +it('gets the highest footprint from the dataset, custom', async () => { + initCustomDatasetHelper(getConfig()); + setUseCustomFootprint(true); + await new Promise((r) => setTimeout(r, 500)); + expect(getHighestFootprint()).toBe(0.30741); +}); + +it('gets the highest footprint for distance, custom', async () => { + initCustomDatasetHelper(getConfig()); + setUseCustomFootprint(true); + await new Promise((r) => setTimeout(r, 500)); + expect(getHighestFootprintForDistance(12345)).toBe(0.30741 * (12345 / 1000)); +}); diff --git a/www/js/metrics/CarbonFootprintCard.tsx b/www/js/metrics/CarbonFootprintCard.tsx index f2ac1cc76..30c265bfc 100644 --- a/www/js/metrics/CarbonFootprintCard.tsx +++ b/www/js/metrics/CarbonFootprintCard.tsx @@ -55,7 +55,7 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { //set custon dataset, if the labels are custom if (isCustomLabels(userThisWeekModeMap)) { - setUseCustomFootprint(); + setUseCustomFootprint(true); } //calculate low-high and format range for prev week, if exists (14 days ago -> 8 days ago) diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index 2162e87bf..1c950e690 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -7,9 +7,13 @@ let useCustom = false; const mtokm = function (v) { return v / 1000; }; +export const setUseCustomFootprint = function (val: boolean) { + useCustom = val; +}; -export const setUseCustomFootprint = function () { - useCustom = true; +export const clearHighestFootprint = function () { + //need to clear for testing + highestFootprint = undefined; }; const getFootprint = function () { From 3943053782924e449e8befc9d0d8080812c5c7de Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 12:13:55 -0700 Subject: [PATCH 14/40] write tests for metHelper --- www/__tests__/metHelper.test.ts | 45 +++++++++++++++++++++++++++++++++ www/js/metrics/metHelper.ts | 4 +-- 2 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 www/__tests__/metHelper.test.ts diff --git a/www/__tests__/metHelper.test.ts b/www/__tests__/metHelper.test.ts new file mode 100644 index 000000000..ee4fbd70d --- /dev/null +++ b/www/__tests__/metHelper.test.ts @@ -0,0 +1,45 @@ +import { getMet, setUseCustomMET } from '../js/metrics/metHelper'; +import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; +import { mockLogger } from '../__mocks__/globalMocks'; +import fakeLabels from '../__mocks__/fakeLabels.json'; +import { getConfig } from '../js/config/dynamicConfig'; +import { initCustomDatasetHelper } from '../js/metrics/CustomMetricsHelper'; + +mockBEMUserCache(); +mockLogger(); + +global.fetch = (url: string) => + new Promise((rs, rj) => { + setTimeout(() => + rs({ + text: () => + new Promise((rs, rj) => { + let myJSON = JSON.stringify(fakeLabels); + setTimeout(() => rs(myJSON), 100); + }), + }), + ); + }) as any; + +beforeEach(() => { + setUseCustomMET(false); +}); + +it('gets met for mode and speed', () => { + expect(getMet('WALKING', 1.47523, 0)).toBe(4.3); + expect(getMet('BICYCLING', 4.5, 0)).toBe(6.8); + expect(getMet('UNICYCLE', 100, 0)).toBe(0); + expect(getMet('CAR', 25, 1)).toBe(0); +}); + +it('gets custom met for mode and speed', async () => { + initCustomDatasetHelper(getConfig()); + setUseCustomMET(true); + await new Promise((r) => setTimeout(r, 500)); + expect(getMet('walk', 1.47523, 0)).toBe(4.3); + expect(getMet('bike', 4.5, 0)).toBe(6.8); + expect(getMet('unicycle', 100, 0)).toBe(0); + expect(getMet('drove_alone', 25, 1)).toBe(0); + expect(getMet('e-bike', 6, 1)).toBe(4.9); + expect(getMet('e-bike', 12, 1)).toBe(4.9); +}); diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index b696c5790..dc1f7d296 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -3,8 +3,8 @@ import { standardMETs } from './metDataset'; let useCustom = false; -export const setUseCustomMET = function () { - useCustom = true; +export const setUseCustomMET = function (val: boolean) { + useCustom = val; }; const getMETs = function () { From 7436502a0d56d790e58b72e5e4152cb41fe67a44 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 14:45:12 -0700 Subject: [PATCH 15/40] custom dataset helper tests --- www/__tests__/customMetricsHelper.test.ts | 68 +++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 www/__tests__/customMetricsHelper.test.ts diff --git a/www/__tests__/customMetricsHelper.test.ts b/www/__tests__/customMetricsHelper.test.ts new file mode 100644 index 000000000..ec4b9d709 --- /dev/null +++ b/www/__tests__/customMetricsHelper.test.ts @@ -0,0 +1,68 @@ +import { getConfig } from '../js/config/dynamicConfig'; +import { + getCustomFootprint, + getCustomMETs, + getFallbackFootprint, + initCustomDatasetHelper, +} from '../js/metrics/CustomMetricsHelper'; +import { setUseCustomMET } from '../js/metrics/metHelper'; +import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; +import { mockLogger } from '../__mocks__/globalMocks'; +import fakeLabels from '../__mocks__/fakeLabels.json'; +import { setUseCustomFootprint } from '../js/metrics/footprintHelper'; +import { number } from 'prop-types'; + +mockBEMUserCache(); +mockLogger(); + +global.fetch = (url: string) => + new Promise((rs, rj) => { + setTimeout(() => + rs({ + text: () => + new Promise((rs, rj) => { + let myJSON = JSON.stringify(fakeLabels); + setTimeout(() => rs(myJSON), 100); + }), + }), + ); + }) as any; + +it('gets the fallback carbon', async () => { + expect(getFallbackFootprint()).toEqual( + expect.objectContaining({ + WALKING: 0, + BICYCLING: 0, + CAR: 267 / 1609, + TRAIN: 92 / 1609, + }), + ); +}); + +it('gets the custom mets', async () => { + initCustomDatasetHelper(getConfig()); + setUseCustomMET(true); + await new Promise((r) => setTimeout(r, 800)); + expect(getCustomMETs()).toMatchObject({ + walk: {}, + bike: {}, + bikeshare: {}, + 'e-bike': {}, + scootershare: {}, + drove_alone: {}, + }); +}); + +it('gets the custom footprint', async () => { + initCustomDatasetHelper(getConfig()); + setUseCustomFootprint(true); + await new Promise((r) => setTimeout(r, 800)); + expect(getCustomFootprint()).toMatchObject({ + walk: {}, + bike: {}, + bikeshare: {}, + 'e-bike': {}, + scootershare: {}, + drove_alone: {}, + }); +}); From 974ac3ad124038dc46a11edc3b3e73bdc8284816 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 15:30:27 -0700 Subject: [PATCH 16/40] remove carbon fallback This code should never be used at this point -- labels will default to those in the sample file, which means that they are "walk, bike, ect" -- custom labels rather than the default "WALKING, BICYCLING, etc" Since the labels are always some version of custom (even the fallback) those values will always be used over the country-specific fallback values --- www/__tests__/customMetricsHelper.test.ts | 13 ------------ www/__tests__/footprintHelper.test.ts | 24 ----------------------- www/js/metrics/CustomMetricsHelper.ts | 8 +------- www/js/metrics/carbonDatasetFallback.ts | 14 ------------- www/js/metrics/footprintHelper.ts | 4 ++-- 5 files changed, 3 insertions(+), 60 deletions(-) delete mode 100644 www/js/metrics/carbonDatasetFallback.ts diff --git a/www/__tests__/customMetricsHelper.test.ts b/www/__tests__/customMetricsHelper.test.ts index ec4b9d709..0f221e12c 100644 --- a/www/__tests__/customMetricsHelper.test.ts +++ b/www/__tests__/customMetricsHelper.test.ts @@ -2,7 +2,6 @@ import { getConfig } from '../js/config/dynamicConfig'; import { getCustomFootprint, getCustomMETs, - getFallbackFootprint, initCustomDatasetHelper, } from '../js/metrics/CustomMetricsHelper'; import { setUseCustomMET } from '../js/metrics/metHelper'; @@ -10,7 +9,6 @@ import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; import fakeLabels from '../__mocks__/fakeLabels.json'; import { setUseCustomFootprint } from '../js/metrics/footprintHelper'; -import { number } from 'prop-types'; mockBEMUserCache(); mockLogger(); @@ -28,17 +26,6 @@ global.fetch = (url: string) => ); }) as any; -it('gets the fallback carbon', async () => { - expect(getFallbackFootprint()).toEqual( - expect.objectContaining({ - WALKING: 0, - BICYCLING: 0, - CAR: 267 / 1609, - TRAIN: 92 / 1609, - }), - ); -}); - it('gets the custom mets', async () => { initCustomDatasetHelper(getConfig()); setUseCustomMET(true); diff --git a/www/__tests__/footprintHelper.test.ts b/www/__tests__/footprintHelper.test.ts index fa0b4574d..1de4fd701 100644 --- a/www/__tests__/footprintHelper.test.ts +++ b/www/__tests__/footprintHelper.test.ts @@ -32,30 +32,6 @@ beforeEach(() => { clearHighestFootprint(); }); -const metrics = [ - { key: 'WALKING', values: 3000 }, - { key: 'BICYCLING', values: 6500 }, - { key: 'CAR', values: 50000 }, - { key: 'LIGHT_RAIL', values: 30000 }, - { key: 'Unicycle', values: 5000 }, -]; - -it('gets footprint for metrics (not custom, fallback 0)', () => { - expect(getFootprintForMetrics(metrics, 0)).toBe(10.534493474207583); -}); - -it('gets footprint for metrics (not custom, fallback 0.1)', () => { - expect(getFootprintForMetrics(metrics, 0.1)).toBe(10.534493474207583 + 0.5); -}); - -it('gets the highest footprint from the dataset, not custom', () => { - expect(getHighestFootprint()).toBe(278 / 1609); -}); - -it('gets the highest footprint for distance, not custom', () => { - expect(getHighestFootprintForDistance(12345)).toBe((278 / 1609) * (12345 / 1000)); -}); - const custom_metrics = [ { key: 'walk', values: 3000 }, { key: 'bike', values: 6500 }, diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/CustomMetricsHelper.ts index 088ec8831..0c25b7d41 100644 --- a/www/js/metrics/CustomMetricsHelper.ts +++ b/www/js/metrics/CustomMetricsHelper.ts @@ -3,7 +3,6 @@ import { getLabelOptions } from '../survey/multilabel/confirmHelper'; import { getConfig } from '../config/dynamicConfig'; import { displayError, displayErrorMsg, logDebug } from '../plugin/logger'; import { standardMETs } from './metDataset'; -import { fallbackCarbon } from './carbonDatasetFallback'; let _customMETs; let _customPerKmFootprint; @@ -20,16 +19,11 @@ export const getCustomFootprint = function () { return _customPerKmFootprint; }; -export const getRangeLimitedMotorixe = function () { +export const getRangeLimitedMotorized = function () { logDebug('Getting range limited motorized ' + JSON.stringify(_range_limited_motorized)); return _range_limited_motorized; }; -export const getFallbackFootprint = function () { - console.log('getting fallback carbon'); - return fallbackCarbon.footprintData; -}; - const populateCustomMETs = function () { let modeOptions = _inputParams['MODE']; let modeMETEntries = modeOptions.map((opt) => { diff --git a/www/js/metrics/carbonDatasetFallback.ts b/www/js/metrics/carbonDatasetFallback.ts deleted file mode 100644 index 4561d078a..000000000 --- a/www/js/metrics/carbonDatasetFallback.ts +++ /dev/null @@ -1,14 +0,0 @@ -export const fallbackCarbon = { - regionName: 'United States', - footprintData: { - WALKING: 0, - BICYCLING: 0, - CAR: 267 / 1609, - BUS: 278 / 1609, - LIGHT_RAIL: 120 / 1609, - SUBWAY: 74 / 1609, - TRAM: 90 / 1609, - TRAIN: 92 / 1609, - AIR_OR_HSR: 217 / 1609, - }, -}; diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index 1c950e690..aef7249b3 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -1,5 +1,5 @@ import { displayErrorMsg } from '../plugin/logger'; -import { getCustomFootprint, getFallbackFootprint } from './CustomMetricsHelper'; +import { getCustomFootprint } from './CustomMetricsHelper'; var highestFootprint = 0; let useCustom = false; @@ -22,7 +22,7 @@ const getFootprint = function () { } else { //TODO: check through configs and ensure they all have custom lables displayErrorMsg('Error in Footprint Calculatons', 'issue with data or default labels'); - return getFallbackFootprint(); + return; } }; From 8c28737a73f3e078f0a606b78f79114bf06aff9a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 16:47:38 -0700 Subject: [PATCH 17/40] remove unused code --- www/js/metrics/footprintHelper.ts | 86 ------------------------------- www/js/metrics/metHelper.ts | 16 ------ 2 files changed, 102 deletions(-) diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index aef7249b3..840647e75 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -60,19 +60,6 @@ export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = return result; }; -const getLowestFootprintForDistance = function (distance) { - var footprint = getFootprint(); - var lowestFootprint = Number.MAX_SAFE_INTEGER; - for (var mode in footprint) { - if (mode == 'WALKING' || mode == 'BICYCLING') { - // these modes aren't considered when determining the lowest carbon footprint - } else { - lowestFootprint = Math.min(lowestFootprint, footprint[mode]); - } - } - return lowestFootprint * mtokm(distance); -}; - export const getHighestFootprint = function () { if (!highestFootprint) { var footprint = getFootprint(); @@ -88,76 +75,3 @@ export const getHighestFootprint = function () { export const getHighestFootprintForDistance = function (distance) { return getHighestFootprint() * mtokm(distance); }; - -const getLowestMotorizedNonAirFootprint = function (footprint, rlmCO2) { - var lowestFootprint = Number.MAX_SAFE_INTEGER; - for (var mode in footprint) { - if (mode == 'AIR_OR_HSR' || mode == 'air') { - console.log('Air mode, ignoring'); - } else { - if (footprint[mode] == 0 || footprint[mode] <= rlmCO2) { - console.log( - 'Non motorized mode or footprint <= range_limited_motorized', - mode, - footprint[mode], - rlmCO2, - ); - } else { - lowestFootprint = Math.min(lowestFootprint, footprint[mode]); - } - } - } - return lowestFootprint; -}; - -const getOptimalDistanceRanges = function () { - const FIVE_KM = 5 * 1000; - const SIX_HUNDRED_KM = 600 * 1000; - if (!useCustom) { - const defaultFootprint = getCurrentCarbonDatasetFootprint(); - const lowestMotorizedNonAir = getLowestMotorizedNonAirFootprint(defaultFootprint); - const airFootprint = defaultFootprint['AIR_OR_HSR']; - return [ - { low: 0, high: FIVE_KM, optimal: 0 }, - { low: FIVE_KM, high: SIX_HUNDRED_KM, optimal: lowestMotorizedNonAir }, - { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint }, - ]; - } else { - // custom footprint, let's get the custom values - const customFootprint = getCustomFootprint(); - let airFootprint = customFootprint['air']; - if (!airFootprint) { - // 2341 BTU/PMT from - // https://tedb.ornl.gov/wp-content/uploads/2021/02/TEDB_Ed_39.pdf#page=68 - // 159.25 lb per million BTU from EIA - // https://www.eia.gov/environment/emissions/co2_vol_mass.php - // (2341 * (159.25/1000000))/(1.6*2.2) = 0.09975, rounded up a bit - console.log('No entry for air in ', customFootprint, ' using default'); - airFootprint = 0.1; - } - const rlm = CustomDatasetHelper.range_limited_motorized; - if (!rlm) { - return [ - { low: 0, high: FIVE_KM, optimal: 0 }, - { low: FIVE_KM, high: SIX_HUNDRED_KM, optimal: lowestMotorizedNonAir }, - { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint }, - ]; - } else { - console.log('Found range_limited_motorized mode', rlm); - const lowestMotorizedNonAir = getLowestMotorizedNonAirFootprint( - customFootprint, - rlm.kgCo2PerKm, - ); - return [ - { low: 0, high: FIVE_KM, optimal: 0 }, - { low: FIVE_KM, high: rlm.range_limit_km * 1000, optimal: rlm.kgCo2PerKm }, - { - low: rlm.range_limit_km * 1000, - high: SIX_HUNDRED_KM, - optimal: lowestMotorizedNonAir, - }, - { low: SIX_HUNDRED_KM, high: Number.MAX_VALUE, optimal: airFootprint }, - ]; - } - } -}; diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index dc1f7d296..e1d1ce28b 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -42,19 +42,3 @@ export const getMet = function (mode, speed, defaultIfMissing) { } } }; - -// var highestMET = 0; -// const getHighestMET = function () { -// if (!highestMET) { -// var met = getMETs(); -// let metList = []; -// for (var mode in met) { -// var rangeList = met[mode]; -// for (var range in rangeList) { -// metList.push(rangeList[range].mets); -// } -// } -// highestMET = Math.max(...metList); -// } -// return highestMET; -// }; From deb7104362459ad108ea9022a2ec16421384e4f3 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Thu, 9 Nov 2023 18:09:20 -0700 Subject: [PATCH 18/40] add docstrings --- www/js/metrics/CustomMetricsHelper.ts | 27 +++++++++++++---- www/js/metrics/footprintHelper.ts | 43 ++++++++++++++++++++++++--- www/js/metrics/metHelper.ts | 27 +++++++++++++++++ 3 files changed, 88 insertions(+), 9 deletions(-) diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/CustomMetricsHelper.ts index 0c25b7d41..eab094062 100644 --- a/www/js/metrics/CustomMetricsHelper.ts +++ b/www/js/metrics/CustomMetricsHelper.ts @@ -4,26 +4,34 @@ import { getConfig } from '../config/dynamicConfig'; import { displayError, displayErrorMsg, logDebug } from '../plugin/logger'; import { standardMETs } from './metDataset'; +//variables to store values locally let _customMETs; let _customPerKmFootprint; let _range_limited_motorized; let _inputParams; +/** + * @function gets custom mets, must be initialized + * @returns the custom mets stored locally + */ export const getCustomMETs = function () { logDebug('Getting custom METs ' + JSON.stringify(_customMETs)); return _customMETs; }; +/** + * @function gets the custom footprint, must be initialized + * @returns custom footprint + */ export const getCustomFootprint = function () { logDebug('Getting custom footprint ' + JSON.stringify(_customPerKmFootprint)); return _customPerKmFootprint; }; -export const getRangeLimitedMotorized = function () { - logDebug('Getting range limited motorized ' + JSON.stringify(_range_limited_motorized)); - return _range_limited_motorized; -}; - +/** + * @function stores custom mets in local var + * needs _inputParams, label options stored after gotten from config + */ const populateCustomMETs = function () { let modeOptions = _inputParams['MODE']; let modeMETEntries = modeOptions.map((opt) => { @@ -56,6 +64,10 @@ const populateCustomMETs = function () { logDebug('After populating, custom METs = ' + JSON.stringify(_customMETs)); }; +/** + * @function stores custom footprint in local var + * needs _inputParams which is stored after gotten from config + */ const populateCustomFootprints = function () { let modeOptions = _inputParams['MODE']; let modeCO2PerKm = modeOptions @@ -81,6 +93,11 @@ const populateCustomFootprints = function () { logDebug('After populating, custom perKmFootprint' + JSON.stringify(_customPerKmFootprint)); }; +/** + * @function initializes the datasets based on configured label options + * calls popuplateCustomMETs and populateCustomFootprint + * @param newConfig the app config file + */ export const initCustomDatasetHelper = async function (newConfig) { newConfig = await getConfig(); try { diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index 840647e75..a7b7f74ce 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -1,31 +1,57 @@ import { displayErrorMsg } from '../plugin/logger'; import { getCustomFootprint } from './CustomMetricsHelper'; -var highestFootprint = 0; +//variables for the highest footprint in the set and if using custom +let highestFootprint = 0; let useCustom = false; +/** + * @function converts meters to kilometers + * @param {number} v value in meters to be converted + * @returns {number} converted value in km + */ const mtokm = function (v) { return v / 1000; }; + +/** + * @function sets the value of useCustom + * @param {boolean} val if using custom footprint + */ export const setUseCustomFootprint = function (val: boolean) { useCustom = val; }; +/** + * @function clears the stored highest footprint + */ export const clearHighestFootprint = function () { //need to clear for testing highestFootprint = undefined; }; +/** + * @function gets the footprint + * currently will only be custom, as all labels are "custom" + * fallback is json/label-options.json.sample, with MET and kgCO2 defined + * @returns the footprint or undefined + */ const getFootprint = function () { if (useCustom == true) { return getCustomFootprint(); } else { - //TODO: check through configs and ensure they all have custom lables - displayErrorMsg('Error in Footprint Calculatons', 'issue with data or default labels'); - return; + displayErrorMsg('failed to use custom labels', 'Error in Footprint Calculatons'); + return undefined; } }; +/** + * @function calculates footprint for given metrics + * @param {Array} userMetrics string mode + number distance in meters pairs + * ex: const custom_metrics = [ { key: 'walk', values: 3000 }, { key: 'bike', values: 6500 }, ]; + * @param {number} defaultIfMissing optional, carbon intensity if mode not in footprint + * @returns {number} the sum of carbon emissions for userMetrics given + */ export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = 0) { var footprint = getFootprint(); var result = 0; @@ -60,6 +86,10 @@ export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = return result; }; +/** + * @function gets highest co2 intensity in the footprint + * @returns {number} the highest co2 intensity in the footprint + */ export const getHighestFootprint = function () { if (!highestFootprint) { var footprint = getFootprint(); @@ -72,6 +102,11 @@ export const getHighestFootprint = function () { return highestFootprint; }; +/** + * @function gets highest theoretical footprint for given distance + * @param {number} distance in meters to calculate max footprint + * @returns max footprint for given distance + */ export const getHighestFootprintForDistance = function (distance) { return getHighestFootprint() * mtokm(distance); }; diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index e1d1ce28b..c5ea7554e 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -3,10 +3,18 @@ import { standardMETs } from './metDataset'; let useCustom = false; +/** + * @function sets boolean to use custom mets + * @param {boolean} val + */ export const setUseCustomMET = function (val: boolean) { useCustom = val; }; +/** + * @function gets the METs object + * @returns {object} mets either custom or standard + */ const getMETs = function () { if (useCustom == true) { return getCustomMETs(); @@ -15,14 +23,33 @@ const getMETs = function () { } }; +/** + * @function checks number agains bounds + * @param num the number to check + * @param min lower bound + * @param max upper bound + * @returns {boolean} if number is within given bounds + */ const between = function (num, min, max) { return num >= min && num <= max; }; +/** + * @function converts meters per second to miles per hour + * @param mps meters per second speed + * @returns speed in miles per hour + */ const mpstomph = function (mps) { return 2.23694 * mps; }; +/** + * @function gets met for a given mode and speed + * @param {string} mode of travel + * @param {number} speed of travel in meters per second + * @param {number} defaultIfMissing default MET if mode not in METs + * @returns + */ export const getMet = function (mode, speed, defaultIfMissing) { if (mode == 'ON_FOOT') { console.log("getMet() converted 'ON_FOOT' to 'WALKING'"); From 9340297c6d94d4edaf7cba89aac9a8ef0a9ed23b Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 10 Nov 2023 09:15:55 -0700 Subject: [PATCH 19/40] working to debug dashboard --- www/js/metrics/CustomMetricsHelper.ts | 2 +- www/js/metrics/footprintHelper.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/CustomMetricsHelper.ts index eab094062..5a70933f8 100644 --- a/www/js/metrics/CustomMetricsHelper.ts +++ b/www/js/metrics/CustomMetricsHelper.ts @@ -99,8 +99,8 @@ const populateCustomFootprints = function () { * @param newConfig the app config file */ export const initCustomDatasetHelper = async function (newConfig) { - newConfig = await getConfig(); try { + logDebug('initializing custom datasets with config' + newConfig); getLabelOptions(newConfig).then((inputParams) => { console.log('Input params = ', inputParams); _inputParams = inputParams; diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index a7b7f74ce..fd4ef8122 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -1,4 +1,4 @@ -import { displayErrorMsg } from '../plugin/logger'; +import { displayErrorMsg, logDebug } from '../plugin/logger'; import { getCustomFootprint } from './CustomMetricsHelper'; //variables for the highest footprint in the set and if using custom @@ -54,6 +54,7 @@ const getFootprint = function () { */ export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = 0) { var footprint = getFootprint(); + logDebug('getting footprint for ' + userMetrics + ' with ' + footprint); var result = 0; for (var i in userMetrics) { var mode = userMetrics[i].key; From acc80223cb1115ab60235b8a22d1ecef0800fe85 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 10 Nov 2023 09:41:23 -0700 Subject: [PATCH 20/40] lift dataset initialization dataset was not initialized in time from metricsTab, lifting the call up to App fixed this issue --- www/js/App.tsx | 2 ++ www/js/metrics/MetricsTab.tsx | 10 ---------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/www/js/App.tsx b/www/js/App.tsx index a955b032d..77bf42463 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -19,6 +19,7 @@ import { initPushNotify } from './splash/pushNotifySettings'; import { initStoreDeviceSettings } from './splash/storeDeviceSettings'; import { initRemoteNotifyHandler } from './splash/remoteNotifyHandler'; import { withErrorBoundary } from './plugin/ErrorBoundary'; +import { initCustomDatasetHelper } from './metrics/CustomMetricsHelper'; const defaultRoutes = (t) => [ { @@ -77,6 +78,7 @@ const App = () => { initPushNotify(); initStoreDeviceSettings(); initRemoteNotifyHandler(); + initCustomDatasetHelper(appConfig); }, [appConfig]); const appContextValue = { diff --git a/www/js/metrics/MetricsTab.tsx b/www/js/metrics/MetricsTab.tsx index 67670bf0c..03c6737a9 100644 --- a/www/js/metrics/MetricsTab.tsx +++ b/www/js/metrics/MetricsTab.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState, useMemo } from 'react'; -import { getAngularService } from '../angular-react-helper'; import { View, ScrollView, useWindowDimensions } from 'react-native'; import { Appbar } from 'react-native-paper'; import NavBarButton from '../components/NavBarButton'; @@ -17,8 +16,6 @@ import DailyActiveMinutesCard from './DailyActiveMinutesCard'; import CarbonTextCard from './CarbonTextCard'; import ActiveMinutesTableCard from './ActiveMinutesTableCard'; import { getAggregateData, getMetrics } from '../commHelper'; -import useAppConfig from '../useAppConfig'; -import { initCustomDatasetHelper } from './CustomMetricsHelper'; import { displayError, logDebug } from '../plugin/logger'; export const METRIC_LIST = ['duration', 'mean_speed', 'count', 'distance'] as const; @@ -44,7 +41,6 @@ function getLastTwoWeeksDtRange() { const MetricsTab = () => { const { t } = useTranslation(); - const appConfig = useAppConfig(); const { getFormattedSpeed, speedSuffix, getFormattedDistance, distanceSuffix } = useImperialConfig(); @@ -57,12 +53,6 @@ const MetricsTab = () => { loadMetricsForPopulation('aggregate', dateRange); }, [dateRange]); - //initialize once config is populated - useEffect(() => { - if (!appConfig) return; - initCustomDatasetHelper(appConfig); - }, [appConfig]); - async function loadMetricsForPopulation(population: 'user' | 'aggregate', dateRange: DateTime[]) { try { logDebug(`MetricsTab: fetching metrics for population ${population}' From fa22ee4b7621430ef5b4399aafd3578fa9ce0733 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Fri, 10 Nov 2023 17:45:19 -0500 Subject: [PATCH 21/40] remove use of `rootScope` in getAggregateData This function used the Angular `rootScope` to get 2 of the server conn setting: connectUrl and aggregateAuth. These are both derived from dynamic config file in the 'server' section. So we can useAppConfig() in MetricsTab and pass 'appConfig.server' in as a parameter 'serverConnConfig'. Also, - cleaned up some syntax of getAggregateData to make it more readable - added type definitions for the server conn config One thing to note is that I don't think any active study or program uses "no_auth". But it still looks like it is an option so I am including it. --- www/js/commHelper.ts | 29 +++++++++++------------------ www/js/metrics/MetricsTab.tsx | 16 ++++++++++++---- www/js/types/appConfigTypes.ts | 12 ++++++++++++ 3 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 www/js/types/appConfigTypes.ts diff --git a/www/js/commHelper.ts b/www/js/commHelper.ts index 5f144888b..f69f42331 100644 --- a/www/js/commHelper.ts +++ b/www/js/commHelper.ts @@ -1,5 +1,6 @@ import { DateTime } from 'luxon'; import { logDebug } from './plugin/logger'; +import { ServerConnConfig } from './types/appConfigTypes'; /** * @param url URL endpoint for the request @@ -129,20 +130,17 @@ export function getMetrics(timeType: 'timestamp' | 'local_date', metricsQuery) { }); } -export function getAggregateData(path: string, data: any) { +export function getAggregateData(path: string, query, serverConnConfig: ServerConnConfig) { return new Promise((rs, rj) => { - const fullUrl = `${window['$rootScope'].connectUrl}/${path}`; - data['aggregate'] = true; + const fullUrl = `${serverConnConfig.connectUrl}/${path}`; + query['aggregate'] = true; - if (window['$rootScope'].aggregateAuth === 'no_auth') { - logDebug( - `getting aggregate data without user authentication from ${fullUrl} with arguments ${JSON.stringify( - data, - )}`, - ); + if (serverConnConfig.aggregate_call_auth == 'no_auth') { + logDebug(`getting aggregate data without user authentication from ${fullUrl} + with arguments ${JSON.stringify(query)}`); const options = { method: 'post', - data: data, + data: query, responseType: 'json', }; window['cordova'].plugin.http.sendRequest( @@ -156,14 +154,9 @@ export function getAggregateData(path: string, data: any) { }, ); } else { - logDebug( - `getting aggregate data with user authentication from ${fullUrl} with arguments ${JSON.stringify( - data, - )}`, - ); - const msgFiller = (message) => { - return Object.assign(message, data); - }; + logDebug(`getting aggregate data with user authentication from ${fullUrl} + with arguments ${JSON.stringify(query)}`); + const msgFiller = (message) => Object.assign(message, query); window['cordova'].plugins.BEMServerComm.pushGetJSON(`/${path}`, msgFiller, rs, rj); } }).catch((error) => { diff --git a/www/js/metrics/MetricsTab.tsx b/www/js/metrics/MetricsTab.tsx index 03c6737a9..7a1636b61 100644 --- a/www/js/metrics/MetricsTab.tsx +++ b/www/js/metrics/MetricsTab.tsx @@ -17,10 +17,16 @@ import CarbonTextCard from './CarbonTextCard'; import ActiveMinutesTableCard from './ActiveMinutesTableCard'; import { getAggregateData, getMetrics } from '../commHelper'; import { displayError, logDebug } from '../plugin/logger'; +import useAppConfig from '../useAppConfig'; +import { ServerConnConfig } from '../types/appConfigTypes'; export const METRIC_LIST = ['duration', 'mean_speed', 'count', 'distance'] as const; -async function fetchMetricsFromServer(type: 'user' | 'aggregate', dateRange: DateTime[]) { +async function fetchMetricsFromServer( + type: 'user' | 'aggregate', + dateRange: DateTime[], + serverConnConfig: ServerConnConfig, +) { const query = { freq: 'D', start_time: dateRange[0].toSeconds(), @@ -29,7 +35,7 @@ async function fetchMetricsFromServer(type: 'user' | 'aggregate', dateRange: Dat is_return_aggregate: type == 'aggregate', }; if (type == 'user') return getMetrics('timestamp', query); - return getAggregateData('result/metrics/timestamp', query); + return getAggregateData('result/metrics/timestamp', query, serverConnConfig); } function getLastTwoWeeksDtRange() { @@ -40,6 +46,7 @@ function getLastTwoWeeksDtRange() { } const MetricsTab = () => { + const appConfig = useAppConfig(); const { t } = useTranslation(); const { getFormattedSpeed, speedSuffix, getFormattedDistance, distanceSuffix } = useImperialConfig(); @@ -49,15 +56,16 @@ const MetricsTab = () => { const [userMetrics, setUserMetrics] = useState(null); useEffect(() => { + if (!appConfig?.server) return; loadMetricsForPopulation('user', dateRange); loadMetricsForPopulation('aggregate', dateRange); - }, [dateRange]); + }, [dateRange, appConfig?.server]); async function loadMetricsForPopulation(population: 'user' | 'aggregate', dateRange: DateTime[]) { try { logDebug(`MetricsTab: fetching metrics for population ${population}' in date range ${JSON.stringify(dateRange)}`); - const serverResponse = await fetchMetricsFromServer(population, dateRange); + const serverResponse = await fetchMetricsFromServer(population, dateRange, appConfig.server); logDebug('MetricsTab: received metrics: ' + JSON.stringify(serverResponse)); const metrics = {}; const dataKey = population == 'user' ? 'user_metrics' : 'aggregate_metrics'; diff --git a/www/js/types/appConfigTypes.ts b/www/js/types/appConfigTypes.ts new file mode 100644 index 000000000..3d62f51cf --- /dev/null +++ b/www/js/types/appConfigTypes.ts @@ -0,0 +1,12 @@ +// WIP: type definitions for the 'dynamic config' spec +// examples of configs: https://github.com/e-mission/nrel-openpath-deploy-configs/tree/main/configs + +export type AppConfig = { + server: ServerConnConfig; + [k: string]: any; // TODO fill in all the other fields +}; + +export type ServerConnConfig = { + connectUrl: `https://${string}`; + aggregate_call_auth: 'no_auth' | 'user_only' | 'never'; +}; From 6cf3a802bcdaa4e786193854fc0562f63ac4f318 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 15 Nov 2023 13:42:31 -0700 Subject: [PATCH 22/40] patch for WSOD on dashboard the reason I was seeing WSOD with some of my data was that I had spans of weeks where one week I only biked and the other I only walked, so each dataset (walk and bike) only had one datapoint each. I was able to fix this by acesssing the 0th entry in the ith dataset instead of the ith entry in the 0th dataset, since the latter does not always exist in the active minutes chart --- www/js/components/Chart.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/js/components/Chart.tsx b/www/js/components/Chart.tsx index 4ebf49c24..5374946f5 100644 --- a/www/js/components/Chart.tsx +++ b/www/js/components/Chart.tsx @@ -174,8 +174,8 @@ const Chart = ({ callback: (value, i) => { logDebug(`Vertical axis callback: i = ${i}; chartDatasets = ${JSON.stringify(chartDatasets)}; - chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`); - const label = chartDatasets[0].data[i].x; + chartDatasets[i].data = ${JSON.stringify(chartDatasets[i].data)}`); + const label = chartDatasets[i].data[0].x; if (typeof label == 'string' && label.includes('\n')) return label.split('\n'); return label; From 1693409d3a19d6ccbefb55ade32e06c1d6a7659e Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Wed, 15 Nov 2023 14:11:28 -0700 Subject: [PATCH 23/40] tweak to account for same mode both weeks as an add on to my previous patch, we also need to account for if the same mode is both weeks (like the span in July where I just walked both weeks) This if statement accounts for both data cases. --- www/js/components/Chart.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/www/js/components/Chart.tsx b/www/js/components/Chart.tsx index 5374946f5..8d9154713 100644 --- a/www/js/components/Chart.tsx +++ b/www/js/components/Chart.tsx @@ -174,8 +174,14 @@ const Chart = ({ callback: (value, i) => { logDebug(`Vertical axis callback: i = ${i}; chartDatasets = ${JSON.stringify(chartDatasets)}; - chartDatasets[i].data = ${JSON.stringify(chartDatasets[i].data)}`); - const label = chartDatasets[i].data[0].x; + chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`); + //account for different data possiblities - one mode per weeek, one mode both weeks, mixed weeks + let label; + if (chartDatasets[0].data[i]) { + label = chartDatasets[0].data[i].x; + } else { + label = chartDatasets[i].data[0].x; + } if (typeof label == 'string' && label.includes('\n')) return label.split('\n'); return label; From 1f495e21b298918b7310b9d750183821187f39e3 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 13:56:31 -0700 Subject: [PATCH 24/40] clean up bugfix fixed typo, made single line, and added to horizontal chart as well https://github.com/e-mission/e-mission-phone/pull/1098#discussion_r1397765721 --- www/js/components/Chart.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/www/js/components/Chart.tsx b/www/js/components/Chart.tsx index 8d9154713..372d7e6c8 100644 --- a/www/js/components/Chart.tsx +++ b/www/js/components/Chart.tsx @@ -138,7 +138,8 @@ const Chart = ({ logDebug(`Horizontal axis callback: i = ${i}; chartDatasets = ${JSON.stringify(chartDatasets)}; chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`); - const label = chartDatasets[0].data[i].y; + //account for different data possiblities + const label = chartDatasets[0].data[i]?.y || chartDatasets[i].data[0]?.y if (typeof label == 'string' && label.includes('\n')) return label.split('\n'); return label; @@ -175,13 +176,8 @@ const Chart = ({ logDebug(`Vertical axis callback: i = ${i}; chartDatasets = ${JSON.stringify(chartDatasets)}; chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`); - //account for different data possiblities - one mode per weeek, one mode both weeks, mixed weeks - let label; - if (chartDatasets[0].data[i]) { - label = chartDatasets[0].data[i].x; - } else { - label = chartDatasets[i].data[0].x; - } + //account for different data possiblities - one mode per week, one mode both weeks, mixed weeks + const label = chartDatasets[0].data[i]?.x || chartDatasets[i].data[0]?.x if (typeof label == 'string' && label.includes('\n')) return label.split('\n'); return label; From ef3bd59e2d1b6ad76371ade69855485e5c2c9b51 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 13:59:42 -0700 Subject: [PATCH 25/40] renamed file CustomMetricsHelper -> customMetricsHelper --- www/__tests__/customMetricsHelper.test.ts | 2 +- www/__tests__/footprintHelper.test.ts | 2 +- www/__tests__/metHelper.test.ts | 2 +- www/js/App.tsx | 2 +- www/js/metrics/footprintHelper.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/www/__tests__/customMetricsHelper.test.ts b/www/__tests__/customMetricsHelper.test.ts index 0f221e12c..0a45f739a 100644 --- a/www/__tests__/customMetricsHelper.test.ts +++ b/www/__tests__/customMetricsHelper.test.ts @@ -3,7 +3,7 @@ import { getCustomFootprint, getCustomMETs, initCustomDatasetHelper, -} from '../js/metrics/CustomMetricsHelper'; +} from '../js/metrics/customMetricsHelper'; import { setUseCustomMET } from '../js/metrics/metHelper'; import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; diff --git a/www/__tests__/footprintHelper.test.ts b/www/__tests__/footprintHelper.test.ts index 1de4fd701..5360b7a39 100644 --- a/www/__tests__/footprintHelper.test.ts +++ b/www/__tests__/footprintHelper.test.ts @@ -1,4 +1,4 @@ -import { initCustomDatasetHelper } from '../js/metrics/CustomMetricsHelper'; +import { initCustomDatasetHelper } from '../js/metrics/customMetricsHelper'; import { clearHighestFootprint, getFootprintForMetrics, diff --git a/www/__tests__/metHelper.test.ts b/www/__tests__/metHelper.test.ts index ee4fbd70d..ea36ec87f 100644 --- a/www/__tests__/metHelper.test.ts +++ b/www/__tests__/metHelper.test.ts @@ -3,7 +3,7 @@ import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; import fakeLabels from '../__mocks__/fakeLabels.json'; import { getConfig } from '../js/config/dynamicConfig'; -import { initCustomDatasetHelper } from '../js/metrics/CustomMetricsHelper'; +import { initCustomDatasetHelper } from '../js/metrics/customMetricsHelper'; mockBEMUserCache(); mockLogger(); diff --git a/www/js/App.tsx b/www/js/App.tsx index 77bf42463..2eece7f55 100644 --- a/www/js/App.tsx +++ b/www/js/App.tsx @@ -19,7 +19,7 @@ import { initPushNotify } from './splash/pushNotifySettings'; import { initStoreDeviceSettings } from './splash/storeDeviceSettings'; import { initRemoteNotifyHandler } from './splash/remoteNotifyHandler'; import { withErrorBoundary } from './plugin/ErrorBoundary'; -import { initCustomDatasetHelper } from './metrics/CustomMetricsHelper'; +import { initCustomDatasetHelper } from './metrics/customMetricsHelper'; const defaultRoutes = (t) => [ { diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index fd4ef8122..e5a615c4a 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -1,5 +1,5 @@ import { displayErrorMsg, logDebug } from '../plugin/logger'; -import { getCustomFootprint } from './CustomMetricsHelper'; +import { getCustomFootprint } from './customMetricsHelper'; //variables for the highest footprint in the set and if using custom let highestFootprint = 0; From df8ec72287d95872aedaa05fdb80b4de60a465d4 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 14:07:34 -0700 Subject: [PATCH 26/40] input Params -> labelOptions --- www/js/metrics/CustomMetricsHelper.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/CustomMetricsHelper.ts index 5a70933f8..3b479f8a2 100644 --- a/www/js/metrics/CustomMetricsHelper.ts +++ b/www/js/metrics/CustomMetricsHelper.ts @@ -1,6 +1,5 @@ import angular from 'angular'; import { getLabelOptions } from '../survey/multilabel/confirmHelper'; -import { getConfig } from '../config/dynamicConfig'; import { displayError, displayErrorMsg, logDebug } from '../plugin/logger'; import { standardMETs } from './metDataset'; @@ -8,7 +7,7 @@ import { standardMETs } from './metDataset'; let _customMETs; let _customPerKmFootprint; let _range_limited_motorized; -let _inputParams; +let _labelOptions; /** * @function gets custom mets, must be initialized @@ -30,10 +29,10 @@ export const getCustomFootprint = function () { /** * @function stores custom mets in local var - * needs _inputParams, label options stored after gotten from config + * needs _labelOptions, stored after gotten from config */ const populateCustomMETs = function () { - let modeOptions = _inputParams['MODE']; + let modeOptions = _labelOptions['MODE']; let modeMETEntries = modeOptions.map((opt) => { if (opt.met_equivalent) { let currMET = standardMETs[opt.met_equivalent]; @@ -69,7 +68,7 @@ const populateCustomMETs = function () { * needs _inputParams which is stored after gotten from config */ const populateCustomFootprints = function () { - let modeOptions = _inputParams['MODE']; + let modeOptions = _labelOptions['MODE']; let modeCO2PerKm = modeOptions .map((opt) => { if (opt.range_limit_km) { @@ -103,7 +102,7 @@ export const initCustomDatasetHelper = async function (newConfig) { logDebug('initializing custom datasets with config' + newConfig); getLabelOptions(newConfig).then((inputParams) => { console.log('Input params = ', inputParams); - _inputParams = inputParams; + _labelOptions = inputParams; populateCustomMETs(); populateCustomFootprints(); }); From c1607887fafa37c576369bdecb1e3d36de52193a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 14:16:09 -0700 Subject: [PATCH 27/40] footprint will always be custom removing the "is custom" variable for the footprint, and all references checking that footprint gotten is defined before returning, if not defined will throw an error --- www/__tests__/customMetricsHelper.test.ts | 2 -- www/__tests__/footprintHelper.test.ts | 6 ------ www/js/metrics/CarbonFootprintCard.tsx | 6 ------ www/js/metrics/footprintHelper.ts | 14 +++----------- 4 files changed, 3 insertions(+), 25 deletions(-) diff --git a/www/__tests__/customMetricsHelper.test.ts b/www/__tests__/customMetricsHelper.test.ts index 0a45f739a..251c1d977 100644 --- a/www/__tests__/customMetricsHelper.test.ts +++ b/www/__tests__/customMetricsHelper.test.ts @@ -8,7 +8,6 @@ import { setUseCustomMET } from '../js/metrics/metHelper'; import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; import fakeLabels from '../__mocks__/fakeLabels.json'; -import { setUseCustomFootprint } from '../js/metrics/footprintHelper'; mockBEMUserCache(); mockLogger(); @@ -42,7 +41,6 @@ it('gets the custom mets', async () => { it('gets the custom footprint', async () => { initCustomDatasetHelper(getConfig()); - setUseCustomFootprint(true); await new Promise((r) => setTimeout(r, 800)); expect(getCustomFootprint()).toMatchObject({ walk: {}, diff --git a/www/__tests__/footprintHelper.test.ts b/www/__tests__/footprintHelper.test.ts index 5360b7a39..3f6883bbe 100644 --- a/www/__tests__/footprintHelper.test.ts +++ b/www/__tests__/footprintHelper.test.ts @@ -4,7 +4,6 @@ import { getFootprintForMetrics, getHighestFootprint, getHighestFootprintForDistance, - setUseCustomFootprint, } from '../js/metrics/footprintHelper'; import { getConfig } from '../js/config/dynamicConfig'; import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; @@ -28,7 +27,6 @@ global.fetch = (url: string) => }) as any; beforeEach(() => { - setUseCustomFootprint(false); clearHighestFootprint(); }); @@ -42,28 +40,24 @@ const custom_metrics = [ it('gets footprint for metrics (custom, fallback 0)', async () => { initCustomDatasetHelper(getConfig()); - setUseCustomFootprint(true); await new Promise((r) => setTimeout(r, 500)); expect(getFootprintForMetrics(custom_metrics, 0)).toBe(2.4266); }); it('gets footprint for metrics (custom, fallback 0.1)', async () => { initCustomDatasetHelper(getConfig()); - setUseCustomFootprint(true); await new Promise((r) => setTimeout(r, 500)); expect(getFootprintForMetrics(custom_metrics, 0.1)).toBe(2.4266 + 0.5); }); it('gets the highest footprint from the dataset, custom', async () => { initCustomDatasetHelper(getConfig()); - setUseCustomFootprint(true); await new Promise((r) => setTimeout(r, 500)); expect(getHighestFootprint()).toBe(0.30741); }); it('gets the highest footprint for distance, custom', async () => { initCustomDatasetHelper(getConfig()); - setUseCustomFootprint(true); await new Promise((r) => setTimeout(r, 500)); expect(getHighestFootprintForDistance(12345)).toBe(0.30741 * (12345 / 1000)); }); diff --git a/www/js/metrics/CarbonFootprintCard.tsx b/www/js/metrics/CarbonFootprintCard.tsx index 30c265bfc..835f20a22 100644 --- a/www/js/metrics/CarbonFootprintCard.tsx +++ b/www/js/metrics/CarbonFootprintCard.tsx @@ -5,7 +5,6 @@ import { MetricsData } from './metricsTypes'; import { cardStyles } from './MetricsTab'; import { getFootprintForMetrics, - setUseCustomFootprint, getHighestFootprint, getHighestFootprintForDistance, } from './footprintHelper'; @@ -53,11 +52,6 @@ const CarbonFootprintCard = ({ userMetrics, aggMetrics }: Props) => { //setting up data to be displayed let graphRecords = []; - //set custon dataset, if the labels are custom - if (isCustomLabels(userThisWeekModeMap)) { - setUseCustomFootprint(true); - } - //calculate low-high and format range for prev week, if exists (14 days ago -> 8 days ago) let userPrevWeek; if (userLastWeekSummaryMap[0]) { diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index e5a615c4a..8b3a5de9e 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -3,7 +3,6 @@ import { getCustomFootprint } from './customMetricsHelper'; //variables for the highest footprint in the set and if using custom let highestFootprint = 0; -let useCustom = false; /** * @function converts meters to kilometers @@ -14,14 +13,6 @@ const mtokm = function (v) { return v / 1000; }; -/** - * @function sets the value of useCustom - * @param {boolean} val if using custom footprint - */ -export const setUseCustomFootprint = function (val: boolean) { - useCustom = val; -}; - /** * @function clears the stored highest footprint */ @@ -37,8 +28,9 @@ export const clearHighestFootprint = function () { * @returns the footprint or undefined */ const getFootprint = function () { - if (useCustom == true) { - return getCustomFootprint(); + let footprint = getCustomFootprint(); + if (footprint) { + return footprint; } else { displayErrorMsg('failed to use custom labels', 'Error in Footprint Calculatons'); return undefined; From 4c1a4a6a3d9a00d0c90e0f554fb29807cb8bdd28 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 14:19:02 -0700 Subject: [PATCH 28/40] rename the metDatasets file even though its an acronym, it is less confusing to have the name be lowercase since it is not a component --- www/js/metrics/{METDataset.ts => metDatasets.ts} | 0 www/js/metrics/metHelper.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename www/js/metrics/{METDataset.ts => metDatasets.ts} (100%) diff --git a/www/js/metrics/METDataset.ts b/www/js/metrics/metDatasets.ts similarity index 100% rename from www/js/metrics/METDataset.ts rename to www/js/metrics/metDatasets.ts diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index c5ea7554e..d3522e152 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -1,5 +1,5 @@ import { getCustomMETs } from './customMetricsHelper'; -import { standardMETs } from './metDataset'; +import { standardMETs } from './metDatasets'; let useCustom = false; From b655f4a17ea4d2630ade33af0375ce5672082896 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 14:23:53 -0700 Subject: [PATCH 29/40] name updates, just one dataset --- www/js/metrics/{metDatasets.ts => metDataset.ts} | 0 www/js/metrics/metHelper.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename www/js/metrics/{metDatasets.ts => metDataset.ts} (100%) diff --git a/www/js/metrics/metDatasets.ts b/www/js/metrics/metDataset.ts similarity index 100% rename from www/js/metrics/metDatasets.ts rename to www/js/metrics/metDataset.ts diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index d3522e152..c5ea7554e 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -1,5 +1,5 @@ import { getCustomMETs } from './customMetricsHelper'; -import { standardMETs } from './metDatasets'; +import { standardMETs } from './metDataset'; let useCustom = false; From dec322fc7262408d7814059a0b77cbd6d8ccc6e9 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 14:24:06 -0700 Subject: [PATCH 30/40] prettier in Chart.tsx --- www/js/components/Chart.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/www/js/components/Chart.tsx b/www/js/components/Chart.tsx index 372d7e6c8..257eb3cf6 100644 --- a/www/js/components/Chart.tsx +++ b/www/js/components/Chart.tsx @@ -139,7 +139,8 @@ const Chart = ({ chartDatasets = ${JSON.stringify(chartDatasets)}; chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`); //account for different data possiblities - const label = chartDatasets[0].data[i]?.y || chartDatasets[i].data[0]?.y + const label = + chartDatasets[0].data[i]?.y || chartDatasets[i].data[0]?.y; if (typeof label == 'string' && label.includes('\n')) return label.split('\n'); return label; @@ -177,7 +178,8 @@ const Chart = ({ chartDatasets = ${JSON.stringify(chartDatasets)}; chartDatasets[0].data = ${JSON.stringify(chartDatasets[0].data)}`); //account for different data possiblities - one mode per week, one mode both weeks, mixed weeks - const label = chartDatasets[0].data[i]?.x || chartDatasets[i].data[0]?.x + const label = + chartDatasets[0].data[i]?.x || chartDatasets[i].data[0]?.x; if (typeof label == 'string' && label.includes('\n')) return label.split('\n'); return label; From a29957459dcb34224c0124998f0818350d2ef72c Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 14:40:33 -0700 Subject: [PATCH 31/40] remove debugging log statements --- www/js/survey/multilabel/confirmHelper.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/www/js/survey/multilabel/confirmHelper.ts b/www/js/survey/multilabel/confirmHelper.ts index 7fffa54cb..f032f2f5a 100644 --- a/www/js/survey/multilabel/confirmHelper.ts +++ b/www/js/survey/multilabel/confirmHelper.ts @@ -37,7 +37,6 @@ export let inputDetails: InputDetails; export async function getLabelOptions(appConfigParam?) { if (appConfigParam) appConfig = appConfigParam; if (labelOptions) return labelOptions; - console.log('in get label options', appConfig); if (appConfig.label_options) { const labelOptionsJson = await fetchUrlCached(appConfig.label_options); logDebug( @@ -50,7 +49,6 @@ export async function getLabelOptions(appConfigParam?) { 'No label_options found in config, using default label options at ' + defaultLabelOptionsURL, ); const defaultLabelOptionsJson = await fetchUrlCached(defaultLabelOptionsURL); - console.log('label options', defaultLabelOptionsJson); labelOptions = JSON.parse(defaultLabelOptionsJson) as LabelOptions; } /* fill in the translations to the 'text' fields of the labelOptions, From 6272fb0d11d6e9e1558798f3cc2a7e865b6c17ff Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 14:44:13 -0700 Subject: [PATCH 32/40] logDebug statements logDebug/logWarn instead of the console equivalents --- www/js/metrics/metHelper.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index c5ea7554e..377352a9b 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -1,3 +1,4 @@ +import { logDebug, logWarn } from '../plugin/logger'; import { getCustomMETs } from './customMetricsHelper'; import { standardMETs } from './metDataset'; @@ -52,19 +53,19 @@ const mpstomph = function (mps) { */ export const getMet = function (mode, speed, defaultIfMissing) { if (mode == 'ON_FOOT') { - console.log("getMet() converted 'ON_FOOT' to 'WALKING'"); + logDebug("getMet() converted 'ON_FOOT' to 'WALKING'"); mode = 'WALKING'; } let currentMETs = getMETs(); if (!currentMETs[mode]) { - console.warn('getMet() Illegal mode: ' + mode); + logWarn('getMet() Illegal mode: ' + mode); return defaultIfMissing; //So the calorie sum does not break with wrong return type } for (var i in currentMETs[mode]) { if (between(mpstomph(speed), currentMETs[mode][i].range[0], currentMETs[mode][i].range[1])) { return currentMETs[mode][i].mets; } else if (mpstomph(speed) < 0) { - console.log('getMet() Negative speed: ' + mpstomph(speed)); + logWarn('getMet() Negative speed: ' + mpstomph(speed)); return 0; } } From 6233af5b96a356d0cc50d00dff491bb67c8e781a Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 14:50:00 -0700 Subject: [PATCH 33/40] logWarn instead of console.warn logWarn in footprintHelper --- www/js/metrics/footprintHelper.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index 8b3a5de9e..b42f5364a 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -1,4 +1,4 @@ -import { displayErrorMsg, logDebug } from '../plugin/logger'; +import { displayErrorMsg, logDebug, logWarn } from '../plugin/logger'; import { getCustomFootprint } from './customMetricsHelper'; //variables for the highest footprint in the set and if using custom @@ -67,11 +67,10 @@ export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = 6) * mtokm(userMetrics[i].values); } else { - console.warn( - 'WARNING getFootprintFromMetrics() was requested for an unknown mode: ' + - mode + - ' metrics JSON: ' + - JSON.stringify(userMetrics), + logWarn( + `WARNING getFootprintFromMetrics() was requested for an unknown mode: ${mode} metrics JSON: ${JSON.stringify( + userMetrics, + )}`, ); result += defaultIfMissing * mtokm(userMetrics[i].values); } From 5918b6be7ac3ab8497706b1e2e1135fb74efb542 Mon Sep 17 00:00:00 2001 From: Abby Wheelis <54848919+Abby-Wheelis@users.noreply.github.com> Date: Fri, 17 Nov 2023 14:55:47 -0700 Subject: [PATCH 34/40] Rename CustomMetricsHelper.ts to customMetricsHelper.ts --- www/js/metrics/{CustomMetricsHelper.ts => customMetricsHelper.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename www/js/metrics/{CustomMetricsHelper.ts => customMetricsHelper.ts} (100%) diff --git a/www/js/metrics/CustomMetricsHelper.ts b/www/js/metrics/customMetricsHelper.ts similarity index 100% rename from www/js/metrics/CustomMetricsHelper.ts rename to www/js/metrics/customMetricsHelper.ts From 125faffa88d166f6123599c0774a803cf1b85a13 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 15:06:57 -0700 Subject: [PATCH 35/40] cleaning up customMetrics making sure the log statements here are thoughtful, renaming the input params to label options in the initialization code --- www/js/metrics/customMetricsHelper.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/www/js/metrics/customMetricsHelper.ts b/www/js/metrics/customMetricsHelper.ts index 3b479f8a2..3a2f551ca 100644 --- a/www/js/metrics/customMetricsHelper.ts +++ b/www/js/metrics/customMetricsHelper.ts @@ -1,6 +1,6 @@ import angular from 'angular'; import { getLabelOptions } from '../survey/multilabel/confirmHelper'; -import { displayError, displayErrorMsg, logDebug } from '../plugin/logger'; +import { displayError, displayErrorMsg, logDebug, logWarn } from '../plugin/logger'; import { standardMETs } from './metDataset'; //variables to store values locally @@ -52,9 +52,7 @@ const populateCustomMETs = function () { } return [opt.value, currMET]; } else { - console.warn( - 'Did not find either met_equivalent or met for ' + opt.value + ' ignoring entry', - ); + logWarn(`Did not find either met_equivalent or met for ${opt.value} ignoring entry`); return undefined; } } @@ -79,7 +77,7 @@ const populateCustomFootprints = function () { ); } _range_limited_motorized = opt; - console.log('Found range limited motorized mode', _range_limited_motorized); + logDebug(`Found range limited motorized mode - ${_range_limited_motorized}`); } if (angular.isDefined(opt.kgCo2PerKm)) { return [opt.value, opt.kgCo2PerKm]; @@ -100,9 +98,9 @@ const populateCustomFootprints = function () { export const initCustomDatasetHelper = async function (newConfig) { try { logDebug('initializing custom datasets with config' + newConfig); - getLabelOptions(newConfig).then((inputParams) => { - console.log('Input params = ', inputParams); - _labelOptions = inputParams; + getLabelOptions(newConfig).then((labelOptions) => { + console.log('In custom metrics, label options: ', labelOptions); + _labelOptions = labelOptions; populateCustomMETs(); populateCustomFootprints(); }); From e626f6e36c5b5cdadfb16968f03204cf024b2066 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 15:42:02 -0700 Subject: [PATCH 36/40] clarify "in vehicle" calculations The divisor being on it's own line was confusing, so now we are adding the vehicle modes and then dividing this should make the fact that it's an average more clear, and is one line shorter! --- www/js/metrics/footprintHelper.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index b42f5364a..6e4bd46d9 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -57,15 +57,14 @@ export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = if (mode in footprint) { result += footprint[mode] * mtokm(userMetrics[i].values); } else if (mode == 'IN_VEHICLE') { - result += - ((footprint['CAR'] + - footprint['BUS'] + - footprint['LIGHT_RAIL'] + - footprint['TRAIN'] + - footprint['TRAM'] + - footprint['SUBWAY']) / - 6) * - mtokm(userMetrics[i].values); + const sum = + footprint['CAR'] + + footprint['BUS'] + + footprint['LIGHT_RAIL'] + + footprint['TRAIN'] + + footprint['TRAM'] + + footprint['SUBWAY']; + result += (sum / 6) * mtokm(userMetrics[i].values); } else { logWarn( `WARNING getFootprintFromMetrics() was requested for an unknown mode: ${mode} metrics JSON: ${JSON.stringify( From b040691e4db8270c1a2ad7a45104fefa0a4630ab Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Fri, 17 Nov 2023 15:54:27 -0700 Subject: [PATCH 37/40] all mets are custom similar to the footprint, we will always use the custom METs, since the labels are custom (either deployer specified or our sample set) The custom mets are set up in the initialization of the custom dataset helper if the custom mets return undefined, will fall back to the standard mets --- www/__tests__/customMetricsHelper.test.ts | 2 -- www/__tests__/metHelper.test.ts | 7 +------ www/js/metrics/metHelper.ts | 15 +++------------ 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/www/__tests__/customMetricsHelper.test.ts b/www/__tests__/customMetricsHelper.test.ts index 251c1d977..173fbc97f 100644 --- a/www/__tests__/customMetricsHelper.test.ts +++ b/www/__tests__/customMetricsHelper.test.ts @@ -4,7 +4,6 @@ import { getCustomMETs, initCustomDatasetHelper, } from '../js/metrics/customMetricsHelper'; -import { setUseCustomMET } from '../js/metrics/metHelper'; import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; import fakeLabels from '../__mocks__/fakeLabels.json'; @@ -27,7 +26,6 @@ global.fetch = (url: string) => it('gets the custom mets', async () => { initCustomDatasetHelper(getConfig()); - setUseCustomMET(true); await new Promise((r) => setTimeout(r, 800)); expect(getCustomMETs()).toMatchObject({ walk: {}, diff --git a/www/__tests__/metHelper.test.ts b/www/__tests__/metHelper.test.ts index ea36ec87f..a06034a61 100644 --- a/www/__tests__/metHelper.test.ts +++ b/www/__tests__/metHelper.test.ts @@ -1,4 +1,4 @@ -import { getMet, setUseCustomMET } from '../js/metrics/metHelper'; +import { getMet } from '../js/metrics/metHelper'; import { mockBEMUserCache } from '../__mocks__/cordovaMocks'; import { mockLogger } from '../__mocks__/globalMocks'; import fakeLabels from '../__mocks__/fakeLabels.json'; @@ -21,10 +21,6 @@ global.fetch = (url: string) => ); }) as any; -beforeEach(() => { - setUseCustomMET(false); -}); - it('gets met for mode and speed', () => { expect(getMet('WALKING', 1.47523, 0)).toBe(4.3); expect(getMet('BICYCLING', 4.5, 0)).toBe(6.8); @@ -34,7 +30,6 @@ it('gets met for mode and speed', () => { it('gets custom met for mode and speed', async () => { initCustomDatasetHelper(getConfig()); - setUseCustomMET(true); await new Promise((r) => setTimeout(r, 500)); expect(getMet('walk', 1.47523, 0)).toBe(4.3); expect(getMet('bike', 4.5, 0)).toBe(6.8); diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index 377352a9b..28765d193 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -2,23 +2,14 @@ import { logDebug, logWarn } from '../plugin/logger'; import { getCustomMETs } from './customMetricsHelper'; import { standardMETs } from './metDataset'; -let useCustom = false; - -/** - * @function sets boolean to use custom mets - * @param {boolean} val - */ -export const setUseCustomMET = function (val: boolean) { - useCustom = val; -}; - /** * @function gets the METs object * @returns {object} mets either custom or standard */ const getMETs = function () { - if (useCustom == true) { - return getCustomMETs(); + let custom_mets = getCustomMETs(); + if (custom_mets) { + return custom_mets; } else { return standardMETs; } From 51f6ece32cf73c110ce9b17477224ea2c52f95b6 Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Sun, 19 Nov 2023 09:22:39 -0700 Subject: [PATCH 38/40] await instead of timeout in testing the initCustomDatasets function, I was calling it and then setting a timeout to wait for it to run, now I await getting the config, then await initialization double checked the Jest tests and running in the emulator - still going smoothly! --- www/__tests__/customMetricsHelper.test.ts | 8 ++++---- www/__tests__/footprintHelper.test.ts | 16 ++++++++-------- www/__tests__/metHelper.test.ts | 4 ++-- www/js/metrics/customMetricsHelper.ts | 11 +++++------ 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/www/__tests__/customMetricsHelper.test.ts b/www/__tests__/customMetricsHelper.test.ts index 173fbc97f..572fc0c27 100644 --- a/www/__tests__/customMetricsHelper.test.ts +++ b/www/__tests__/customMetricsHelper.test.ts @@ -25,8 +25,8 @@ global.fetch = (url: string) => }) as any; it('gets the custom mets', async () => { - initCustomDatasetHelper(getConfig()); - await new Promise((r) => setTimeout(r, 800)); + const appConfig = await getConfig(); + await initCustomDatasetHelper(appConfig); expect(getCustomMETs()).toMatchObject({ walk: {}, bike: {}, @@ -38,8 +38,8 @@ it('gets the custom mets', async () => { }); it('gets the custom footprint', async () => { - initCustomDatasetHelper(getConfig()); - await new Promise((r) => setTimeout(r, 800)); + const appConfig = await getConfig(); + await initCustomDatasetHelper(appConfig); expect(getCustomFootprint()).toMatchObject({ walk: {}, bike: {}, diff --git a/www/__tests__/footprintHelper.test.ts b/www/__tests__/footprintHelper.test.ts index 3f6883bbe..842442153 100644 --- a/www/__tests__/footprintHelper.test.ts +++ b/www/__tests__/footprintHelper.test.ts @@ -39,25 +39,25 @@ const custom_metrics = [ ]; it('gets footprint for metrics (custom, fallback 0)', async () => { - initCustomDatasetHelper(getConfig()); - await new Promise((r) => setTimeout(r, 500)); + const appConfig = await getConfig(); + await initCustomDatasetHelper(appConfig); expect(getFootprintForMetrics(custom_metrics, 0)).toBe(2.4266); }); it('gets footprint for metrics (custom, fallback 0.1)', async () => { - initCustomDatasetHelper(getConfig()); - await new Promise((r) => setTimeout(r, 500)); + const appConfig = await getConfig(); + await initCustomDatasetHelper(appConfig); expect(getFootprintForMetrics(custom_metrics, 0.1)).toBe(2.4266 + 0.5); }); it('gets the highest footprint from the dataset, custom', async () => { - initCustomDatasetHelper(getConfig()); - await new Promise((r) => setTimeout(r, 500)); + const appConfig = await getConfig(); + await initCustomDatasetHelper(appConfig); expect(getHighestFootprint()).toBe(0.30741); }); it('gets the highest footprint for distance, custom', async () => { - initCustomDatasetHelper(getConfig()); - await new Promise((r) => setTimeout(r, 500)); + const appConfig = await getConfig(); + await initCustomDatasetHelper(appConfig); expect(getHighestFootprintForDistance(12345)).toBe(0.30741 * (12345 / 1000)); }); diff --git a/www/__tests__/metHelper.test.ts b/www/__tests__/metHelper.test.ts index a06034a61..bc477daa0 100644 --- a/www/__tests__/metHelper.test.ts +++ b/www/__tests__/metHelper.test.ts @@ -29,8 +29,8 @@ it('gets met for mode and speed', () => { }); it('gets custom met for mode and speed', async () => { - initCustomDatasetHelper(getConfig()); - await new Promise((r) => setTimeout(r, 500)); + const appConfig = await getConfig(); + await initCustomDatasetHelper(appConfig); expect(getMet('walk', 1.47523, 0)).toBe(4.3); expect(getMet('bike', 4.5, 0)).toBe(6.8); expect(getMet('unicycle', 100, 0)).toBe(0); diff --git a/www/js/metrics/customMetricsHelper.ts b/www/js/metrics/customMetricsHelper.ts index 3a2f551ca..ac345e39c 100644 --- a/www/js/metrics/customMetricsHelper.ts +++ b/www/js/metrics/customMetricsHelper.ts @@ -98,12 +98,11 @@ const populateCustomFootprints = function () { export const initCustomDatasetHelper = async function (newConfig) { try { logDebug('initializing custom datasets with config' + newConfig); - getLabelOptions(newConfig).then((labelOptions) => { - console.log('In custom metrics, label options: ', labelOptions); - _labelOptions = labelOptions; - populateCustomMETs(); - populateCustomFootprints(); - }); + const labelOptions = await getLabelOptions(newConfig); + console.log('In custom metrics, label options: ', labelOptions); + _labelOptions = labelOptions; + populateCustomMETs(); + populateCustomFootprints(); } catch (e) { setTimeout(() => { displayError(e, 'Error while initializing custom dataset helper'); From 3dee26f69f6a54f5265d938ac0897e713bb385cc Mon Sep 17 00:00:00 2001 From: Abby Wheelis Date: Sun, 19 Nov 2023 09:29:11 -0700 Subject: [PATCH 39/40] Use expect.any(Type) for stricter testing https://github.com/e-mission/e-mission-phone/pull/1098#discussion_r1398327203 --- www/__tests__/customMetricsHelper.test.ts | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/www/__tests__/customMetricsHelper.test.ts b/www/__tests__/customMetricsHelper.test.ts index 572fc0c27..0ae025bff 100644 --- a/www/__tests__/customMetricsHelper.test.ts +++ b/www/__tests__/customMetricsHelper.test.ts @@ -28,12 +28,12 @@ it('gets the custom mets', async () => { const appConfig = await getConfig(); await initCustomDatasetHelper(appConfig); expect(getCustomMETs()).toMatchObject({ - walk: {}, - bike: {}, - bikeshare: {}, - 'e-bike': {}, - scootershare: {}, - drove_alone: {}, + walk: expect.any(Object), + bike: expect.any(Object), + bikeshare: expect.any(Object), + 'e-bike': expect.any(Object), + scootershare: expect.any(Object), + drove_alone: expect.any(Object), }); }); @@ -41,11 +41,11 @@ it('gets the custom footprint', async () => { const appConfig = await getConfig(); await initCustomDatasetHelper(appConfig); expect(getCustomFootprint()).toMatchObject({ - walk: {}, - bike: {}, - bikeshare: {}, - 'e-bike': {}, - scootershare: {}, - drove_alone: {}, + walk: expect.any(Number), + bike: expect.any(Number), + bikeshare: expect.any(Number), + 'e-bike': expect.any(Number), + scootershare: expect.any(Number), + drove_alone: expect.any(Number), }); }); From 9a5753bf2e95e74494c248672b8aa2068aebc5d4 Mon Sep 17 00:00:00 2001 From: Jack Greenlee Date: Mon, 20 Nov 2023 11:01:42 -0500 Subject: [PATCH 40/40] code style cleanup There is no right or wrong here, but it's a bit cleaner and more consistent with the rest of the codebase if we generally follow this: i) for short one-liners, use const and declare an arrow function ii) for longer functions, use the traditional 'function' declaration also replaced some uses of 'var' with 'const' or 'let' added AppConfig type to initCustomDatasetHelper and replaced a console statement with logDebug --- www/js/metrics/customMetricsHelper.ts | 23 +++++++++-------- www/js/metrics/footprintHelper.ts | 36 ++++++++++++--------------- www/js/metrics/metHelper.ts | 18 ++++++-------- 3 files changed, 35 insertions(+), 42 deletions(-) diff --git a/www/js/metrics/customMetricsHelper.ts b/www/js/metrics/customMetricsHelper.ts index ac345e39c..317113327 100644 --- a/www/js/metrics/customMetricsHelper.ts +++ b/www/js/metrics/customMetricsHelper.ts @@ -2,6 +2,7 @@ import angular from 'angular'; import { getLabelOptions } from '../survey/multilabel/confirmHelper'; import { displayError, displayErrorMsg, logDebug, logWarn } from '../plugin/logger'; import { standardMETs } from './metDataset'; +import { AppConfig } from '../types/appConfigTypes'; //variables to store values locally let _customMETs; @@ -13,25 +14,25 @@ let _labelOptions; * @function gets custom mets, must be initialized * @returns the custom mets stored locally */ -export const getCustomMETs = function () { +export function getCustomMETs() { logDebug('Getting custom METs ' + JSON.stringify(_customMETs)); return _customMETs; -}; +} /** * @function gets the custom footprint, must be initialized * @returns custom footprint */ -export const getCustomFootprint = function () { +export function getCustomFootprint() { logDebug('Getting custom footprint ' + JSON.stringify(_customPerKmFootprint)); return _customPerKmFootprint; -}; +} /** * @function stores custom mets in local var * needs _labelOptions, stored after gotten from config */ -const populateCustomMETs = function () { +function populateCustomMETs() { let modeOptions = _labelOptions['MODE']; let modeMETEntries = modeOptions.map((opt) => { if (opt.met_equivalent) { @@ -59,13 +60,13 @@ const populateCustomMETs = function () { }); _customMETs = Object.fromEntries(modeMETEntries.filter((e) => angular.isDefined(e))); logDebug('After populating, custom METs = ' + JSON.stringify(_customMETs)); -}; +} /** * @function stores custom footprint in local var * needs _inputParams which is stored after gotten from config */ -const populateCustomFootprints = function () { +function populateCustomFootprints() { let modeOptions = _labelOptions['MODE']; let modeCO2PerKm = modeOptions .map((opt) => { @@ -88,18 +89,18 @@ const populateCustomFootprints = function () { .filter((modeCO2) => angular.isDefined(modeCO2)); _customPerKmFootprint = Object.fromEntries(modeCO2PerKm); logDebug('After populating, custom perKmFootprint' + JSON.stringify(_customPerKmFootprint)); -}; +} /** * @function initializes the datasets based on configured label options * calls popuplateCustomMETs and populateCustomFootprint * @param newConfig the app config file */ -export const initCustomDatasetHelper = async function (newConfig) { +export async function initCustomDatasetHelper(newConfig: AppConfig) { try { logDebug('initializing custom datasets with config' + newConfig); const labelOptions = await getLabelOptions(newConfig); - console.log('In custom metrics, label options: ', labelOptions); + logDebug('In custom metrics, label options = ' + JSON.stringify(labelOptions)); _labelOptions = labelOptions; populateCustomMETs(); populateCustomFootprints(); @@ -108,4 +109,4 @@ export const initCustomDatasetHelper = async function (newConfig) { displayError(e, 'Error while initializing custom dataset helper'); }, 1000); } -}; +} diff --git a/www/js/metrics/footprintHelper.ts b/www/js/metrics/footprintHelper.ts index 6e4bd46d9..24677feaf 100644 --- a/www/js/metrics/footprintHelper.ts +++ b/www/js/metrics/footprintHelper.ts @@ -9,17 +9,15 @@ let highestFootprint = 0; * @param {number} v value in meters to be converted * @returns {number} converted value in km */ -const mtokm = function (v) { - return v / 1000; -}; +const mtokm = (v) => v / 1000; /** * @function clears the stored highest footprint */ -export const clearHighestFootprint = function () { +export function clearHighestFootprint() { //need to clear for testing highestFootprint = undefined; -}; +} /** * @function gets the footprint @@ -27,7 +25,7 @@ export const clearHighestFootprint = function () { * fallback is json/label-options.json.sample, with MET and kgCO2 defined * @returns the footprint or undefined */ -const getFootprint = function () { +function getFootprint() { let footprint = getCustomFootprint(); if (footprint) { return footprint; @@ -35,7 +33,7 @@ const getFootprint = function () { displayErrorMsg('failed to use custom labels', 'Error in Footprint Calculatons'); return undefined; } -}; +} /** * @function calculates footprint for given metrics @@ -44,12 +42,12 @@ const getFootprint = function () { * @param {number} defaultIfMissing optional, carbon intensity if mode not in footprint * @returns {number} the sum of carbon emissions for userMetrics given */ -export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = 0) { - var footprint = getFootprint(); +export function getFootprintForMetrics(userMetrics, defaultIfMissing = 0) { + const footprint = getFootprint(); logDebug('getting footprint for ' + userMetrics + ' with ' + footprint); - var result = 0; - for (var i in userMetrics) { - var mode = userMetrics[i].key; + let result = 0; + for (let i in userMetrics) { + let mode = userMetrics[i].key; if (mode == 'ON_FOOT') { mode = 'WALKING'; } @@ -75,29 +73,27 @@ export const getFootprintForMetrics = function (userMetrics, defaultIfMissing = } } return result; -}; +} /** * @function gets highest co2 intensity in the footprint * @returns {number} the highest co2 intensity in the footprint */ -export const getHighestFootprint = function () { +export function getHighestFootprint() { if (!highestFootprint) { - var footprint = getFootprint(); + const footprint = getFootprint(); let footprintList = []; - for (var mode in footprint) { + for (let mode in footprint) { footprintList.push(footprint[mode]); } highestFootprint = Math.max(...footprintList); } return highestFootprint; -}; +} /** * @function gets highest theoretical footprint for given distance * @param {number} distance in meters to calculate max footprint * @returns max footprint for given distance */ -export const getHighestFootprintForDistance = function (distance) { - return getHighestFootprint() * mtokm(distance); -}; +export const getHighestFootprintForDistance = (distance) => getHighestFootprint() * mtokm(distance); diff --git a/www/js/metrics/metHelper.ts b/www/js/metrics/metHelper.ts index 28765d193..25bcc2e7e 100644 --- a/www/js/metrics/metHelper.ts +++ b/www/js/metrics/metHelper.ts @@ -6,14 +6,14 @@ import { standardMETs } from './metDataset'; * @function gets the METs object * @returns {object} mets either custom or standard */ -const getMETs = function () { +function getMETs() { let custom_mets = getCustomMETs(); if (custom_mets) { return custom_mets; } else { return standardMETs; } -}; +} /** * @function checks number agains bounds @@ -22,18 +22,14 @@ const getMETs = function () { * @param max upper bound * @returns {boolean} if number is within given bounds */ -const between = function (num, min, max) { - return num >= min && num <= max; -}; +const between = (num, min, max) => num >= min && num <= max; /** * @function converts meters per second to miles per hour * @param mps meters per second speed * @returns speed in miles per hour */ -const mpstomph = function (mps) { - return 2.23694 * mps; -}; +const mpstomph = (mps) => 2.23694 * mps; /** * @function gets met for a given mode and speed @@ -42,7 +38,7 @@ const mpstomph = function (mps) { * @param {number} defaultIfMissing default MET if mode not in METs * @returns */ -export const getMet = function (mode, speed, defaultIfMissing) { +export function getMet(mode, speed, defaultIfMissing) { if (mode == 'ON_FOOT') { logDebug("getMet() converted 'ON_FOOT' to 'WALKING'"); mode = 'WALKING'; @@ -52,7 +48,7 @@ export const getMet = function (mode, speed, defaultIfMissing) { logWarn('getMet() Illegal mode: ' + mode); return defaultIfMissing; //So the calorie sum does not break with wrong return type } - for (var i in currentMETs[mode]) { + for (let i in currentMETs[mode]) { if (between(mpstomph(speed), currentMETs[mode][i].range[0], currentMETs[mode][i].range[1])) { return currentMETs[mode][i].mets; } else if (mpstomph(speed) < 0) { @@ -60,4 +56,4 @@ export const getMet = function (mode, speed, defaultIfMissing) { return 0; } } -}; +}