Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3203 Forest area as proportion of total land area SDG - should be estimated bases on the latest available NDP after 2020 #3427

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { ExpressionFunction } from '@openforis/arena-core/dist/expression/function'

import { Context } from 'meta/expressionEvaluator/context'

type Year = number
type Data = Record<Year, Record<'extentOfForest' | 'totalLandArea', number>>

const baseYear = 2020
const maxYear = 2025

// ---------- Helper utility functions

// Helper utility to create a data object
const _getData = (
forestAreaData: Array<string> | undefined,
totalLandAreaData: Array<string> | undefined
): { data: Data; latestYear: Year } => {
const baseYear = 2020
let latestYear = null
const data = [0, 1, 2, 3].reduce<Data>((acc, i) => {
const key = baseYear + i + 1
// eslint-disable-next-line no-param-reassign
acc[key] = {
extentOfForest: parseFloat(forestAreaData[i]),
totalLandArea: parseFloat(totalLandAreaData[i]),
}

if (forestAreaData[i]) latestYear = key

return acc
}, {} as Data)

return { data, latestYear }
}

const _getValueForYear = (data: Data, year: Year): Record<'extentOfForest' | 'totalLandArea', number> => {
return data[year]
}

const _getProportion = (a: number, b: number): number => {
return (a / b) * 100
}

const _getBaseProportion = (data: Data): number => {
const { extentOfForest, totalLandArea } = data[baseYear]
return _getProportion(extentOfForest, totalLandArea)
}

/**
* @name calculatorForestAreaAsProportionOfTotalLandArea
* @description
* Calculates the forest area as a proportion of the total land area.
* Primarily used for Section 8 Table SDG 15.1.1.
*
* @param {string} year - The year of the calculation.
* @param {Array<string>} valuesExtentOfForest - The values of the extent of forest subcategories for years 2020...2025
* @param {Array<string>} valuesTotalLandArea - The values of the total land area subcategories for years 2020...2025
*/
export const calculatorForestAreaAsProportionOfTotalLandArea: ExpressionFunction<Context> = {
name: 'calculatorForestAreaAsProportionOfTotalLandArea',
minArity: 2,
executor: () => {
return (
year: Year | undefined,
forestAreaData: Array<string> | undefined,
totalLandAreaData: Array<string> | undefined
// TODO: Arena-core/JSEP Doesn't support object format, see issue #3426
// data: Record<Year, Record<'extentOfForest' | 'totalLandArea', string>> | undefined
): number => {
if (!year || !forestAreaData?.length || !totalLandAreaData?.length) return null

const { data, latestYear } = _getData(forestAreaData, totalLandAreaData)

const baseProportion = _getBaseProportion(data)

if (latestYear && latestYear <= year) {
const { extentOfForest: forestAreaX, totalLandArea: totalForestAreaX } = _getValueForYear(data, latestYear)
const proportionX = _getProportion(forestAreaX, totalForestAreaX)

return baseProportion + ((proportionX - baseProportion) / (latestYear - baseYear)) * (year - baseYear)
}

const { extentOfForest: forestArea2025, totalLandArea: totalLandArea2025 } = _getValueForYear(data, maxYear)
const proportion2025 = _getProportion(forestArea2025, totalLandArea2025)

if (baseProportion > 0 && proportion2025 > 0) {
return baseProportion + ((proportion2025 - baseProportion) / (maxYear - baseYear)) * (year - baseYear)
}

return null
}
},
}
3 changes: 3 additions & 0 deletions src/meta/expressionEvaluator/functions/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ExpressionFunction } from '@openforis/arena-core/dist/expression/function'

import { Context } from '../context'
import { calculatorForestAreaAsProportionOfTotalLandArea } from './calculations/calculatorForestAreaAsProportionOfTotalLandArea'
import { validatorSumSubCategoriesNotEqualToParent } from './subcategories/validatorSumSubCategoriesNotEqualToParent'
import { validatorSumSubCategoriesNotGreaterThanParent } from './subcategories/validatorSumSubCategoriesNotGreaterThanParent'
import { equalsWithTolerance } from './equalsWithTolerance'
Expand Down Expand Up @@ -42,6 +43,8 @@ import { validatorSumNotGreaterThanForest } from './validatorSumNotGreaterThanFo
import { validatorTotalForest } from './validatorTotalForest'

export const functions: Array<ExpressionFunction<Context>> = [
calculatorForestAreaAsProportionOfTotalLandArea,

NWFPProductHasCategory,
equalsWithTolerance,
maxForestArea,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as pgPromise from 'pg-promise'

import { Col } from 'meta/assessment'

import { AssessmentController } from 'server/controller/assessment'
import { BaseProtocol, Schemas } from 'server/db'

import { updateCalculatedVariable } from 'test/migrations/steps/utils/updateCalculatedVariable'

const _years = [2020, 2021, 2022, 2023, 2024, 2025]
const _getCalcFormula = (year: string) => {
const eofString = _years.map((y) => `extentOfForest.forestArea['${y}']`).join(', ')
const tlaString = _years.map((y) => `extentOfForest.totalLandArea['${y}']`).join(', ')

return `calculatorForestAreaAsProportionOfTotalLandArea(${year}, [${eofString}], [${tlaString}])`
}

export default async (client: BaseProtocol) => {
const { assessment, cycle } = await AssessmentController.getOneWithCycle(
{
assessmentName: 'fra',
cycleName: '2025',
},
client
)

const schemaName = Schemas.getName(assessment)

const nodeMetadata = await client.map(
`
select c.*
from ${schemaName}.table t
left join ${schemaName}.row r on t.id = r.table_id
left join ${schemaName}.col c on r.id = c.row_id
where
t.props ->> 'name' = 'sustainableDevelopment15_1_1'
and r.props ->> 'variableName' = 'forestAreaProportionLandArea2015'
and c.props ->> 'colName' in ('2020', '2021', '2022', '2023', '2024')
`,
[],
(column) => {
// eslint-disable-next-line no-param-reassign
column.props.calculateFn[cycle.uuid] = _getCalcFormula(column.props.colName)
return column
}
)

const pgp = pgPromise()
const cs = new pgp.helpers.ColumnSet<Col>(
[
{
name: 'props',
cast: 'jsonb',
},
{
name: 'id',
cast: 'bigint',
cnd: true,
},
],
{
table: { table: 'col', schema: schemaName },
}
)

const query = `${pgp.helpers.update(nodeMetadata, cs)} WHERE v.id = t.id;`
await client.query(query)

// Update cache
await AssessmentController.generateMetaCache(client)
await AssessmentController.generateMetadataCache({ assessment }, client)

// Update calculated variables
const sectionName = 'sustainableDevelopment'
const tableName = 'sustainableDevelopment15_1_1'
const variableName = 'forestAreaProportionLandArea2015'
const updateCalculatedVariableProps = { assessment, cycle, sectionName, tableName, variableName }
sorja marked this conversation as resolved.
Show resolved Hide resolved
await updateCalculatedVariable(updateCalculatedVariableProps, client)
sorja marked this conversation as resolved.
Show resolved Hide resolved
}
Loading