diff --git a/app/migrations/20250127093803-add-source-relation-to-formula-input.cjs b/app/migrations/20250127093803-add-source-relation-to-formula-input.cjs new file mode 100644 index 000000000..4c09c47d2 --- /dev/null +++ b/app/migrations/20250127093803-add-source-relation-to-formula-input.cjs @@ -0,0 +1,16 @@ +"use strict"; + +/** @type {import('sequelize-cli').Migration} */ +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.addColumn("FormulaInput", "source", { + type: Sequelize.STRING, + allowNull: true, + field: "source", + }); + }, + + async down(queryInterface, Sequelize) { + await queryInterface.removeColumn("FormulaInput", "source"); + }, +}; diff --git a/app/package-lock.json b/app/package-lock.json index 711357a3c..a680ff758 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -59,6 +59,7 @@ "i18next-resources-to-backend": "^1.2.1", "js-cookie": "^3.0.5", "jsonwebtoken": "^9.0.2", + "k6": "^0.0.0", "lodash.groupby": "^4.6.0", "lodash.sumby": "^4.6.0", "lodash.uniqby": "^4.7.0", @@ -20015,6 +20016,12 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/k6": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/k6/-/k6-0.0.0.tgz", + "integrity": "sha512-GAQSWayS2+LjbH5bkRi+pMPYyP1JSp7o+4j58ANZ762N/RH/SdlAT3CHHztnn8s/xgg8kYNM24Gd2IPo9b5W+g==", + "license": "AGPL-3.0" + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", diff --git a/app/package.json b/app/package.json index 54e6157de..83ed662b4 100644 --- a/app/package.json +++ b/app/package.json @@ -85,6 +85,7 @@ "i18next-resources-to-backend": "^1.2.1", "js-cookie": "^3.0.5", "jsonwebtoken": "^9.0.2", + "k6": "^0.0.0", "lodash.groupby": "^4.6.0", "lodash.sumby": "^4.6.0", "lodash.uniqby": "^4.7.0", diff --git a/app/seed-data/formula_values/data_processed/C40_CIRIS/Methodology.csv b/app/seed-data/formula_values/data_processed/C40_CIRIS/Methodology.csv index e820e5c1c..896e21910 100644 --- a/app/seed-data/formula_values/data_processed/C40_CIRIS/Methodology.csv +++ b/app/seed-data/formula_values/data_processed/C40_CIRIS/Methodology.csv @@ -1,5 +1,5 @@ -methodology_id,methodology,methodology_url,datasource_id -4025beed-cbe5-301d-8cd6-f706531cba42,wastewater-inside-domestic-calculator-activity,,0b054501-056f-33f4-98d0-1253f5681cbf -b0da6b28-c5e0-3f8a-859f-96bac5ff7f6c,wastewater-outside-domestic-calculator-activity,,0b054501-056f-33f4-98d0-1253f5681cbf -a6c1fa15-4405-3900-b649-966c8c7aeae6,wastewater-inside-industrial-calculator-activity,,0b054501-056f-33f4-98d0-1253f5681cbf -946471de-43e5-39f4-ae8a-befa344bfaee,wastewater-outside-industrial-calculator-activity,,0b054501-056f-33f4-98d0-1253f5681cbf +methodology_id,methodology,methodology_url,datasource_id +4025beed-cbe5-301d-8cd6-f706531cba42,wastewater-inside-domestic-calculator-activity,,0b054501-056f-33f4-98d0-1253f5681cbf +b0da6b28-c5e0-3f8a-859f-96bac5ff7f6c,wastewater-outside-domestic-calculator-activity,,0b054501-056f-33f4-98d0-1253f5681cbf +a6c1fa15-4405-3900-b649-966c8c7aeae6,wastewater-inside-industrial-calculator-activity,,0b054501-056f-33f4-98d0-1253f5681cbf +946471de-43e5-39f4-ae8a-befa344bfaee,wastewater-outside-industrial-calculator-activity,,0b054501-056f-33f4-98d0-1253f5681cbf diff --git a/app/seed-data/formula_values/data_processed/FAOSTAT/Methodology.csv b/app/seed-data/formula_values/data_processed/FAOSTAT/Methodology.csv index 89e0b762d..ad8b1986c 100644 --- a/app/seed-data/formula_values/data_processed/FAOSTAT/Methodology.csv +++ b/app/seed-data/formula_values/data_processed/FAOSTAT/Methodology.csv @@ -1,3 +1,3 @@ -methodology_id,methodology,methodology_url,datasource_id -4025beed-cbe5-301d-8cd6-f706531cba42,wastewater-inside-domestic-calculator-activity,,15d25b18-13d2-373f-bf15-7f5652825dba -b0da6b28-c5e0-3f8a-859f-96bac5ff7f6c,wastewater-outside-domestic-calculator-activity,,15d25b18-13d2-373f-bf15-7f5652825dba +methodology_id,methodology,methodology_url,datasource_id +4025beed-cbe5-301d-8cd6-f706531cba42,wastewater-inside-domestic-calculator-activity,,15d25b18-13d2-373f-bf15-7f5652825dba +b0da6b28-c5e0-3f8a-859f-96bac5ff7f6c,wastewater-outside-domestic-calculator-activity,,15d25b18-13d2-373f-bf15-7f5652825dba diff --git a/app/seeders/20241011011356-formula-inputs.cjs b/app/seeders/20241011011356-formula-inputs.cjs index 50961d778..b36ec89a9 100644 --- a/app/seeders/20241011011356-formula-inputs.cjs +++ b/app/seeders/20241011011356-formula-inputs.cjs @@ -3,7 +3,7 @@ const fs = require("node:fs"); const { parse } = require("csv-parse"); const { bulkUpsert } = require("./util/util.cjs"); -const folders = ["EFDB_2006_IPCC_guidelines"]; +const folders = ["EFDB_2006_IPCC_guidelines", "C40_CIRIS", "FAOSTAT"]; const toJson = ({ transformation_description, diff --git a/app/src/backend/formulas.ts b/app/src/backend/formulas.ts index 2b92fe2c2..cd03424d6 100644 --- a/app/src/backend/formulas.ts +++ b/app/src/backend/formulas.ts @@ -106,8 +106,6 @@ const TEXTILES_FACTOR = 0.24; const INDUSTRIAL_WASTE_FACTOR = 0.15; const DEFAULT_METHANE_PRODUCTION_CAPACITY = 0.25; // kg CH4/kg COD -const DEFAULT_METHANE_CORRECTION_FACTOR = 1.0; // TODO get correct one from FormulaInputs/ FormulaValues once that is loaded -const DEFAULT_BOD_PER_CAPITA = 40; // TODO this is a placeholder, get the actual value from IPCC!!! // TODO get actual values for each contry from IPCC const DEFAULT_INCOME_GROUP_FRACTIONS: Record = { @@ -600,15 +598,9 @@ export async function handleIndustrialWasteWaterFormula( const totalIndustrialProduction = data["total-industry-production"]; const industryType = data[`${prefixKey}-industry-type`]; - const treatmentType = data[`${prefixKey}-treatment-type-collected-treated`]; + const treatmentType = data[`${prefixKey}-treatment-type`]; const treatmentStatus = data[`${prefixKey}-treatment-status`]; - const dischargePathway = data[`${prefixKey}-discharge-pathway-untreated`]; - const treatmentTypeMetaDataFilter = - treatmentStatus === "treatment-status-type-wastewater-untreated" - ? dischargePathway - : treatmentType; let wastewaterGenerated = data[`${prefixKey}-wastewater-generated`]; // should this be gotten from UI or - const country = inventoryValue.inventory.city.country as string; const countryCode = inventoryValue.inventory.city.countryLocode; const formulaInputsDOC = await db.models.FormulaInput.findOne({ where: { @@ -659,7 +651,7 @@ export async function handleIndustrialWasteWaterFormula( const formulaInputMCF = await db.models.FormulaInput.findOne({ where: { - [`metadata.treatment-type`]: treatmentTypeMetaDataFilter as string, + [`metadata.treatment-type`]: treatmentType as string, [`metadata.treatment-status`]: treatmentStatus as string, gas: "CH4", parameterCode: "MCF", @@ -733,23 +725,19 @@ export async function handleDomesticWasteWaterFormula( const collectionStatus = data[`${prefixKey}-collection-status`]; const isCollectedWasteWater = collectionStatus === "collection-status-type-wastewater-collected"; - const industrialBodFactor = isCollectedWasteWater ? 1.0 : 1.25; + const industrialBodFactor = isCollectedWasteWater ? 1.25 : 1.0; const treatmentStatus = data[`${prefixKey}-treatment-status`]; - const treatmentType = data[`${prefixKey}-treatment-type-collected-treated`]; - const dischargePathway = data[`${prefixKey}-discharge-pathway-untreated`]; + const treatmentName = data[`${prefixKey}-treatment-name`] as string; + const treatmentType = data[`${prefixKey}-treatment-type`]; const incomeGroup = data[`${prefixKey}-income-group`]; const methaneProductionCapacity = 0.6; // Bo takes default value of 0.6 for domestic - const country = inventoryValue.inventory.city.country as string; const countryCode = inventoryValue.inventory.city.countryLocode; - const treatmentTypeMetaDataFilter = - treatmentStatus === "treatment-status-type-wastewater-untreated" - ? dischargePathway - : treatmentType; + // where clause filter const formulaInputMCF = await db.models.FormulaInput.findOne({ where: { - [`metadata.treatment-type`]: treatmentTypeMetaDataFilter as string, + [`metadata.treatment-type`]: treatmentType as string, [`metadata.treatment-status`]: treatmentStatus as string, gas: "CH4", parameterCode: "MCF", @@ -769,11 +757,15 @@ export async function handleDomesticWasteWaterFormula( ], ], }); - const MethaneCorrectionFactor = formulaInputMCF?.formulaInputValue || 0.3; // TODO confirm if a non zero default is okay + const MethaneCorrectionFactor = formulaInputMCF?.formulaInputValue || 0; // TODO confirm if a non zero default is okay const formulaInputDOU = await db.models.FormulaInput.findOne({ where: { [`metadata.income-group`]: incomeGroup as string, + [`metadata.treatment-name`]: treatmentName.includes("latrine") + ? "latrine" + : treatmentName, gas: "CH4", + formulaInputValue: { [Op.ne]: 0 }, parameterCode: "U*T", methodologyName: `${prefixKey}-activity`, [Op.or]: [ @@ -821,8 +813,7 @@ export async function handleDomesticWasteWaterFormula( ], }); - const bodPerCapita = - formulaInputBOD?.formulaInputValue ?? DEFAULT_BOD_PER_CAPITA; // TODO what is the best default value + const bodPerCapita = (formulaInputBOD?.formulaInputValue as number) / 1000; // default unit is in g/person/day divide by 1000 to get kg/person/day const formulaInputUI = await db.models.FormulaInput.findOne({ where: { @@ -860,8 +851,56 @@ export async function handleDomesticWasteWaterFormula( .mul(EFj) .sub(methaneRecovered); - const amount = totalMethaneProduction.round(); // TODO round right or is ceil/ floor more correct? - return [{ gas: "CH4", amount }]; + const ch4amount = totalMethaneProduction.round(); + + // calculate the total n20 emissions + const garbageDisposalType = data[`${prefixKey}-garbage-disposal`]; + const f_non_con = + garbageDisposalType === "garbage-disposal-type-garbage-disposals" + ? 1.4 + : 1.1; + + const f_ind_com = 1.25; + + const formulaInputProtein = await db.models.FormulaInput.findOne({ + where: { + parameterCode: "protein", + gas: "N2O", + methodologyName: `${prefixKey}-activity`, + [Op.or]: [ + { actorId: { [Op.iLike]: `%${countryCode}%` } }, + { actorId: { [Op.iLike]: "%world%" } }, + ], + }, + order: [ + // Prioritize specific country matches first + [ + literal( + `CASE WHEN actor_id ILIKE '%${countryCode}%' THEN 1 ELSE 2 END`, + ), + "ASC", + ], + ], + }); + + const proteinValue = formulaInputProtein?.formulaInputValue || 0; + + const n2oValueFirstTerm = + cityPopulationByIncomegroup * proteinValue * 0.16 * f_non_con * f_ind_com; + const ef_fluent = 0.005; + + const n20Emission = new Decimal(n2oValueFirstTerm) + .sub(removedSludge) + .mul(ef_fluent) + .mul(44 / 28); + + return [ + { gas: "CH4", amount: ch4amount }, + { + gas: "N2O", + amount: n20Emission.round(), + }, + ]; // TODO include N20 calculations } diff --git a/app/src/components/Modals/activity-modal/activity-modal-body.tsx b/app/src/components/Modals/activity-modal/activity-modal-body.tsx index b0649c85a..dfa1b9413 100644 --- a/app/src/components/Modals/activity-modal/activity-modal-body.tsx +++ b/app/src/components/Modals/activity-modal/activity-modal-body.tsx @@ -334,7 +334,7 @@ const ActivityModalBody = ({ required: f.required === false ? false - : t("value-required"), + : t("option-required"), })} > {f.units?.map((item: string) => ( @@ -361,7 +361,7 @@ const ActivityModalBody = ({ "" )} {(errors?.activity?.[`${f.id}-unit`] as any) && - !errors?.activity?.[`${f.id}-unit`] ? ( + !errors?.activity?.[`${f.id}`] ? ( + {" "} {errors?.activity?.[`${f.id}-unit`]?.message}{" "} diff --git a/app/src/models/FormulaInput.ts b/app/src/models/FormulaInput.ts index 8140e8bca..36f7b33cf 100644 --- a/app/src/models/FormulaInput.ts +++ b/app/src/models/FormulaInput.ts @@ -8,6 +8,7 @@ export interface FormulaInputAttributes { parameterName: string; gpcRefno: string; year?: number; + source?: string; formulaInputValue: number; formulaInputUnits: string; formulaName: string; @@ -43,6 +44,7 @@ export class FormulaInput parameterName!: string; gpcRefno!: string; year?: number; + source?: string; formulaInputValue!: number; formulaInputUnits!: string; formulaName!: string; @@ -120,6 +122,11 @@ export class FormulaInput allowNull: false, field: "formula_input_units", }, + source: { + type: DataTypes.STRING, + allowNull: true, + field: "source", + }, formulaName: { type: DataTypes.STRING, allowNull: false, diff --git a/app/src/util/form-schema/manual-input-hierarchy.json b/app/src/util/form-schema/manual-input-hierarchy.json index 11a2e7092..d0628d12a 100644 --- a/app/src/util/form-schema/manual-input-hierarchy.json +++ b/app/src/util/form-schema/manual-input-hierarchy.json @@ -4470,12 +4470,12 @@ { "id": "wastewater-inside-domestic-calculator-treatment-status", "options": [ - "collection-status-type-wastewater-treated", - "collection-status-type-wastewater-untreated" + "treatment-status-type-wastewater-treated", + "treatment-status-type-wastewater-untreated" ] }, { - "id": "wastewater-inside-domestic-calculator-treatment-type", + "id": "wastewater-inside-domestic-calculator-treatment-name", "dependsOn": "wastewater-inside-domestic-calculator-treatment-status", "dependentOptions": { "treatment-status-type-wastewater-treated": [ @@ -4490,8 +4490,8 @@ } }, { - "id": "wastewater-inside-domestic-calculator-treatment-name", - "dependsOn": "wastewater-inside-domestic-calculator-treatment-type", + "id": "wastewater-inside-domestic-calculator-treatment-type", + "dependsOn": "wastewater-inside-domestic-calculator-treatment-name", "dependentOptions": { "treatment-name-septic-system": [ "treatment-type-septic-system" @@ -4504,9 +4504,9 @@ ], "treatment-name-other": [ "treatment-type-centralized-aerobic-treatment-plan-well-managed", - "treatment-type-anaerobic-digester-for-sludge", + "treatment-type-anaerobic-digester-for-sludge", "treatment-type-anaerobic-reactor", - "treatment-type-anaerobic-shallow-lagoon", + "treatment-type-anaerobic-shallow-lagoon", "treatment-type-anaerobic-deep-lagoon" ], "treatment-name-none": [ @@ -4544,6 +4544,7 @@ "extra-fields": [ { "id": "wastewater-inside-industrial-calculator-industry-type", + "required": true, "options": [ "industry-type-alcohol-refining", "industry-type-coffee", @@ -4603,12 +4604,12 @@ { "id": "wastewater-inside-industrial-calculator-treatment-status", "options": [ - "collection-status-type-wastewater-treated", - "collection-status-type-wastewater-untreated" + "treatment-status-type-wastewater-treated", + "treatment-status-type-wastewater-untreated" ] }, { - "id": "wastewater-inside-industrial-calculator-treatment-type", + "id": "wastewater-inside-industrial-calculator-treatment-name", "dependsOn": "wastewater-inside-industrial-calculator-treatment-status", "dependentOptions": { "treatment-status-type-wastewater-treated": [ @@ -4623,8 +4624,8 @@ } }, { - "id": "wastewater-inside-industrial-calculator-treatment-name", - "dependsOn": "wastewater-inside-industrial-calculator-treatment-type", + "id": "wastewater-inside-industrial-calculator-treatment-type", + "dependsOn": "wastewater-inside-industrial-calculator-treatment-name", "dependentOptions": { "treatment-name-septic-system": [ "treatment-type-septic-system" @@ -4637,9 +4638,9 @@ ], "treatment-name-other": [ "treatment-type-centralized-aerobic-treatment-plan-well-managed", - "treatment-type-anaerobic-digester-for-sludge", + "treatment-type-anaerobic-digester-for-sludge", "treatment-type-anaerobic-reactor", - "treatment-type-anaerobic-shallow-lagoon", + "treatment-type-anaerobic-shallow-lagoon", "treatment-type-anaerobic-deep-lagoon" ], "treatment-name-none": [ @@ -4805,9 +4806,9 @@ ], "treatment-name-other": [ "treatment-type-centralized-aerobic-treatment-plan-well-managed", - "treatment-type-anaerobic-digester-for-sludge", + "treatment-type-anaerobic-digester-for-sludge", "treatment-type-anaerobic-reactor", - "treatment-type-anaerobic-shallow-lagoon", + "treatment-type-anaerobic-shallow-lagoon", "treatment-type-anaerobic-deep-lagoon" ], "treatment-name-none": [ @@ -4938,9 +4939,9 @@ ], "treatment-name-other": [ "treatment-type-centralized-aerobic-treatment-plan-well-managed", - "treatment-type-anaerobic-digester-for-sludge", + "treatment-type-anaerobic-digester-for-sludge", "treatment-type-anaerobic-reactor", - "treatment-type-anaerobic-shallow-lagoon", + "treatment-type-anaerobic-shallow-lagoon", "treatment-type-anaerobic-deep-lagoon" ], "treatment-name-none": [