From a0c120379a873e973d9be75aafac2834b1b574c6 Mon Sep 17 00:00:00 2001 From: Luis Zenteno Date: Tue, 12 Nov 2024 12:12:52 -0600 Subject: [PATCH] feat(sentences): update dashboard sentences for natural forest --- services/analysis-cached.js | 102 ++++++++++++++++++++ services/get-where-query.js | 10 +- services/sentences.js | 181 ++++++++++++++---------------------- 3 files changed, 181 insertions(+), 112 deletions(-) diff --git a/services/analysis-cached.js b/services/analysis-cached.js index ad6c353114..f4d3693e86 100644 --- a/services/analysis-cached.js +++ b/services/analysis-cached.js @@ -21,6 +21,7 @@ const SQL_QUERIES = { lossTsc: 'SELECT tsc_tree_cover_loss_drivers__driver, umd_tree_cover_loss__year, SUM(umd_tree_cover_loss__ha) AS umd_tree_cover_loss__ha, SUM("gfw_gross_emissions_co2e_all_gases__Mg") AS "gfw_gross_emissions_co2e_all_gases__Mg" FROM data {WHERE} GROUP BY tsc_tree_cover_loss_drivers__driver, umd_tree_cover_loss__year', loss: 'SELECT {select_location}, umd_tree_cover_loss__year, SUM(umd_tree_cover_loss__ha) AS umd_tree_cover_loss__ha, SUM("gfw_gross_emissions_co2e_all_gases__Mg") AS "gfw_gross_emissions_co2e_all_gases__Mg" FROM data {WHERE} GROUP BY umd_tree_cover_loss__year, {location} ORDER BY umd_tree_cover_loss__year, {location}', + lossNaturalForest: `SELECT {select_location}, sbtn_natural_forests__class, umd_tree_cover_loss__year, SUM(umd_tree_cover_loss__ha) AS umd_tree_cover_loss__ha, SUM("gfw_gross_emissions_co2e_all_gases__Mg") AS gfw_gross_emissions_co2e_all_gases__Mg FROM data {WHERE} GROUP BY sbtn_natural_forests__class, umd_tree_cover_loss__year, {location}`, lossFires: 'SELECT {select_location}, umd_tree_cover_loss__year, SUM(umd_tree_cover_loss__ha) AS umd_tree_cover_loss__ha, SUM(umd_tree_cover_loss_from_fires__ha) AS "umd_tree_cover_loss_from_fires__ha" FROM data {WHERE} GROUP BY umd_tree_cover_loss__year, {location} ORDER BY umd_tree_cover_loss__year, {location}', lossFiresOTF: @@ -36,6 +37,7 @@ const SQL_QUERIES = { carbonFluxOTF: `SELECT SUM("gfw_forest_carbon_net_flux__Mg_CO2e"), SUM("gfw_forest_carbon_gross_removals__Mg_CO2e"), SUM("gfw_forest_carbon_gross_emissions__Mg_CO2e") FROM data WHERE umd_tree_cover_density_2000__threshold >= {threshold} OR is__umd_tree_cover_gain = 'true'&geostore_origin={geostoreOrigin}&geostore_id={geostoreId}`, extent: 'SELECT {select_location}, SUM(umd_tree_cover_extent_{extentYear}__ha) AS umd_tree_cover_extent_{extentYear}__ha, SUM(area__ha) AS area__ha FROM data {WHERE} GROUP BY {location} ORDER BY {location}', + extentNaturalForest: `SELECT {select_location}, sbtn_natural_forests__class, SUM(area__ha) AS area__ha FROM data {WHERE} GROUP BY iso, sbtn_natural_forests__class, {location} ORDER BY {location}`, gain: 'SELECT {select_location}, SUM("umd_tree_cover_gain__ha") AS "umd_tree_cover_gain__ha", SUM(umd_tree_cover_extent_2000__ha) AS umd_tree_cover_extent_2000__ha FROM data {WHERE} GROUP BY {location} ORDER BY {location}', areaIntersection: 'SELECT {select_location}, SUM(area__ha) AS area__ha {intersection} FROM data {WHERE} GROUP BY {location} {intersection} ORDER BY area__ha DESC', @@ -378,6 +380,53 @@ export const getTreeCoverLossByDriverType = (params) => { })); }; +export const getLossNaturalForest = (params) => { + const { forestType, landCategory, ifl, download } = params || {}; + + const requestUrl = getRequestUrl({ + ...params, + dataset: 'annual', + datasetType: 'change', + version: 'v20240815', + }); + + if (!requestUrl) { + return new Promise(() => {}); + } + + const url = encodeURI( + `${requestUrl}${SQL_QUERIES.lossNaturalForest}` + .replace( + /{select_location}/g, + getLocationSelect({ ...params, cast: false }) + ) + .replace(/{location}/g, getLocationSelect(params)) + .replace('{WHERE}', getWHEREQuery({ ...params, dataset: 'annual' })) + ); + + if (download) { + const indicator = getIndicator(forestType, landCategory, ifl); + return { + name: `loss_natural_forest${ + indicator ? `_in_${snakeCase(indicator.label)}` : '' + }__ha`, + url: getDownloadUrl(url), + }; + } + + return dataRequest.get(url).then((response) => ({ + ...response, + data: { + data: response?.data?.map((d) => ({ + ...d, + year: d.umd_tree_cover_loss__year, + area: d.umd_tree_cover_loss__ha, + emissions: d.gfw_gross_emissions_co2e_all_gases__mg, + })), + }, + })); +}; + // summed loss for single location export const getLoss = (params) => { const { forestType, landCategory, ifl, download } = params || {}; @@ -1138,6 +1187,59 @@ export const getNetChange = async (params) => { }; }; +export const getExtentNaturalForest = (params) => { + const { forestType, landCategory, ifl, download } = params || {}; + + const requestUrl = getRequestUrl({ + ...params, + dataset: 'annual', + datasetType: 'summary', + version: 'v20240815', + }); + + if (!requestUrl) { + return new Promise(() => {}); + } + + const url = encodeURI( + `${requestUrl}${SQL_QUERIES.extentNaturalForest}` + .replace( + /{select_location}/g, + getLocationSelect({ ...params, cast: false }) + ) + .replace(/{location}/g, getLocationSelect({ ...params })) + .replace('{WHERE}', getWHEREQuery({ ...params, dataset: 'annual' })) + ); + + if (download) { + const indicator = getIndicator(forestType, landCategory, ifl); + return { + name: `natural_forest_${ + indicator ? `_in_${snakeCase(indicator.label)}` : '' + }__ha`, + url: getDownloadUrl(url), + }; + } + + return dataRequest.get(url).then((response) => { + return { + ...response, + data: { + data: response?.data?.map((d) => { + return { + ...d, + extent: + d.sbtn_natural_forests__class === 'Natural Forest' + ? d.area__ha + : 0, + total_area: d.area__ha, + }; + }), + }, + }; + }); +}; + // summed extent for single location export const getExtent = (params) => { const { forestType, landCategory, ifl, download, extentYear } = params || {}; diff --git a/services/get-where-query.js b/services/get-where-query.js index b00d854c68..d95df2cc2f 100644 --- a/services/get-where-query.js +++ b/services/get-where-query.js @@ -8,7 +8,9 @@ const isNumber = (value) => !!(typeof value === 'number' || !isNaN(value)); // build {where} statement for query export const getWHEREQuery = (params = {}) => { - const { type, dataset } = params || {}; + // umd_tree_cover_loss__year is being added for the dashboard sentences for natural forest + const { type, dataset, umd_tree_cover_loss__year, isNaturalForest } = + params || {}; const allFilterOptions = forestTypes.concat(landCategories); const allowedParams = ALLOWED_PARAMS[params.dataset || 'annual']; @@ -98,7 +100,11 @@ export const getWHEREQuery = (params = {}) => { } if (isLastParameter) { - WHERE = `${WHERE} `; + if (isNaturalForest) { + WHERE = `${WHERE} AND umd_tree_cover_loss__year=${umd_tree_cover_loss__year}`; + } else { + WHERE = `${WHERE} `; + } } else { WHERE = `${WHERE} AND `; } diff --git a/services/sentences.js b/services/sentences.js index 61461470c6..e6fb6e90a7 100644 --- a/services/sentences.js +++ b/services/sentences.js @@ -1,16 +1,11 @@ -import { all, spread } from 'axios'; import { formatNumber } from 'utils/format'; - -import sortBy from 'lodash/sortBy'; -import groupBy from 'lodash/groupBy'; -import sumBy from 'lodash/sumBy'; -import max from 'lodash/max'; -import reverse from 'lodash/reverse'; import isEmpty from 'lodash/isEmpty'; - import tropicalIsos from 'data/tropical-isos.json'; -import { getExtent, getLoss } from 'services/analysis-cached'; +import { + getExtentNaturalForest, + getLossNaturalForest, +} from 'services/analysis-cached'; const ADMINS = { adm0: null, @@ -27,13 +22,13 @@ const GLOBAL_LOCATION = { export const adminSentences = { default: - 'In 2010, {location} had {extent} of tree cover, extending over {percentage} of its land area.', + 'In 2020, {location} had {extent} of natural forest, extending over {percentage} of its land area.', withLoss: - 'In 2010, {location} had {extent} of tree cover, extending over {percentage} of its land area. In {year}, it lost {loss} of tree cover', + 'In 2020, {location} had {extent} of natural forest, extending over {percentage} of its land area. In {year}, it lost {loss} of natural forest', globalInitial: - 'In 2010, {location} had {extent} of tree cover, extending over {percentage} of its land area. In {year}, it lost {loss} of tree cover.', + 'In 2020, {location} had {extent} of natural forest, extending over {percentage} of its land area. In {year}, it lost {loss} of natural forest', withPlantationLoss: - 'In 2010, {location} had {naturalForest} of natural forest, extending over {percentage} of its land area. In {year}, it lost {naturalLoss} of natural forest', + 'In 2020, {location} had {naturalForest} of natural forest, extending over {percentage} of its land area. In {year}, it lost {naturalLoss} of natural forest', countrySpecific: { IDN: 'In 2001, {location} had {primaryForest} of primary forest*, extending over {percentagePrimaryForest} of its land area. In {year}, it lost {primaryLoss} of primary forest*, equivalent to {emissionsPrimary} of CO₂ emissions.', }, @@ -41,105 +36,71 @@ export const adminSentences = { end: '.', }; -export const getSentenceData = (params = GLOBAL_LOCATION) => - all([ - getExtent(params), - getExtent({ ...params, forestType: 'plantations' }), - getExtent({ +export const getSentenceData = async (params = GLOBAL_LOCATION) => { + try { + const extentNaturalForestResponse = await getExtentNaturalForest(params); + const lossNaturalForestResponse = await getLossNaturalForest({ ...params, - forestType: 'primary_forest', - extentYear: 2000, - }), - getLoss(params), - getLoss({ ...params, forestType: 'plantations' }), - getLoss({ ...params, forestType: 'primary_forest' }), - ]).then( - spread( - ( - totalExtent, - totalPlantationsExtent, - totalPrimaryExtent, - totalLoss, - totalPlantationsLoss, - totalPrimaryLoss - ) => { - const extent = totalExtent.data.data; - const loss = totalLoss.data.data; - const plantationsLoss = totalPlantationsLoss.data.data; - const plantationsExtent = totalPlantationsExtent.data.data; - const primaryExtent = totalPrimaryExtent.data.data; - - // group over years - const groupedLoss = plantationsLoss && groupBy(loss, 'year'); - const groupedPlantationsLoss = - plantationsLoss && groupBy(plantationsLoss, 'year'); - - const primaryLoss = totalPrimaryLoss.data.data; - const latestYear = max(Object.keys(groupedLoss)); - - const latestPlantationLoss = groupedPlantationsLoss[latestYear] || null; - const latestLoss = groupedLoss[latestYear] || null; - - // sum over different bound1 within year - const summedPlantationsLoss = - latestPlantationLoss && - latestPlantationLoss.length && - latestPlantationLoss[0].area - ? sumBy(latestPlantationLoss, 'area') - : 0; - const summedPlantationsEmissions = - latestPlantationLoss && - latestPlantationLoss.length && - latestPlantationLoss[0].emissions - ? sumBy(latestPlantationLoss, 'emissions') - : 0; - const summedLoss = - latestLoss && latestLoss.length && latestLoss[0].area - ? sumBy(latestLoss, 'area') - : 0; - const summedEmissions = - latestLoss && latestLoss.length && latestLoss[0].emissions - ? sumBy(latestLoss, 'emissions') - : 0; - - const data = { - totalArea: - extent && extent.length && extent[0].total_area - ? sumBy(extent, 'total_area') - : 0, - extent: - extent && extent.length && extent[0].extent - ? sumBy(extent, 'extent') - : 0, - plantationsExtent: - plantationsExtent && - plantationsExtent.length && - plantationsExtent[0].extent - ? sumBy(plantationsExtent, 'extent') - : 0, - primaryExtent: - primaryExtent && primaryExtent.length && primaryExtent[0].extent - ? sumBy(primaryExtent, 'extent') - : 0, - totalLoss: { - area: summedLoss || 0, - year: latestYear || 0, - emissions: summedEmissions || 0, - }, - plantationsLoss: { - area: summedPlantationsLoss || 0, - emissions: summedPlantationsEmissions || 0, - }, - primaryLoss: - primaryLoss && primaryLoss.length - ? reverse(sortBy(primaryLoss, 'year'))[0] - : {}, - }; - - return data; + umd_tree_cover_loss__year: 2023, + isNaturalForest: true, + }); + + let extent = 0; + let totalArea = 0; + + extentNaturalForestResponse.data.data.forEach((item) => { + totalArea += item.area__ha; + + if (item.sbtn_natural_forests__class === 'Natural Forest') + extent += item.area__ha; + }); + + let lossArea = 0; + let emissions = 0; + + lossNaturalForestResponse.data.data.forEach((item) => { + emissions += item.gfw_gross_emissions_co2e_all_gases__mg; + + if (item.sbtn_natural_forests__class === 'Natural Forest') { + lossArea += item.area; } - ) - ); + }); + + return { + totalArea, + extent, + plantationsExtent: 0, + primaryExtent: 0, + totalLoss: { + area: lossArea, + year: 2023, + emissions, + }, + plantationsLoss: { + area: 0, + emissions: 0, + }, + primaryLoss: {}, + }; + } catch (error) { + return { + totalArea: 0, + extent: 0, + plantationsExtent: 0, + primaryExtent: 0, + totalLoss: { + area: 0, + year: 0, + emissions: 0, + }, + plantationsLoss: { + area: 0, + emissions: 0, + }, + primaryLoss: {}, + }; + } +}; export const getContextSentence = (location, geodescriber, adminSentence) => { if (isEmpty(geodescriber)) return {};