-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
3203 Forest area as proportion of total land area SDG - should be est…
…imated bases on the latest available NDP after 2020 (#3427) * Initial commit for 3203 * Fix migration step * use left/right for proportion * deepscan * Fix update deps
- Loading branch information
Showing
3 changed files
with
201 additions
and
0 deletions.
There are no files selected for viewing
96 changes: 96 additions & 0 deletions
96
...essionEvaluator/functions/calculations/calculatorForestAreaAsProportionOfTotalLandArea.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { ExpressionFunction } from '@openforis/arena-core/dist/expression/function' | ||
import { Objects } from 'utils/objects' | ||
|
||
import { Context } from 'meta/expressionEvaluator/context' | ||
|
||
type Year = number | ||
type Data = Record<Year, Record<'extentOfForest' | 'totalLandArea', number>> | ||
|
||
const minYear = 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 } => { | ||
const data = [0, 1, 2, 3, 4, 5].reduce<Data>((acc, i) => { | ||
const key = minYear + i | ||
// eslint-disable-next-line no-param-reassign | ||
acc[key] = { | ||
extentOfForest: parseFloat(forestAreaData[i]), | ||
totalLandArea: parseFloat(totalLandAreaData[i]), | ||
} | ||
|
||
return acc | ||
}, {} as Data) | ||
|
||
return { data } | ||
} | ||
|
||
// Helper utility to get the range of years | ||
const _getRange = (data: Data, year: Year): { left: Year; right: Year } => { | ||
const years = Object.keys(data).reduce<Array<Year>>((acc, y) => { | ||
if (!Objects.isEmpty(data[Number(y)].extentOfForest)) acc.push(parseInt(y, 10)) | ||
return acc | ||
}, []) | ||
|
||
const left = [...years].reverse().find((y) => y < year) || minYear | ||
const right = years.find((y) => y > year) || maxYear | ||
|
||
return { left, right } | ||
} | ||
|
||
const _getProportion = (a: number, b: number): number => { | ||
return (a / b) * 100 | ||
} | ||
|
||
const _getYearProportion = (data: Data, year: Year): number => { | ||
const { extentOfForest, totalLandArea } = data[year] | ||
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 } = _getData(forestAreaData, totalLandAreaData) | ||
|
||
// if we have value for current year | ||
if (data[year].extentOfForest && data[year].totalLandArea) { | ||
const { extentOfForest, totalLandArea } = data[year] | ||
return _getProportion(extentOfForest, totalLandArea) | ||
} | ||
|
||
const { left, right } = _getRange(data, year) | ||
|
||
const proportionLeft = _getYearProportion(data, left) | ||
const proportionRight = _getYearProportion(data, right) | ||
|
||
const proportion = proportionLeft + ((proportionRight - proportionLeft) * (year - left)) / (right - left) | ||
|
||
return proportion | ||
} | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
src/test/migrations/steps/20240122134404-step-sdg-1511-fix-calculation.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import * as pgPromise from 'pg-promise' | ||
import { Objects } from 'utils/objects' | ||
|
||
import { Col } from 'meta/assessment' | ||
import { NodeUpdate } from 'meta/data' | ||
|
||
import { AssessmentController } from 'server/controller/assessment' | ||
import { BaseProtocol, Schemas } from 'server/db' | ||
|
||
import { updateDependencies } from 'test/migrations/steps/utils/updateDependencies' | ||
|
||
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', metaCache: true }, | ||
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 metacache | ||
await AssessmentController.generateMetaCache(client) | ||
|
||
// **** update metadata cache | ||
await AssessmentController.generateMetadataCache({ assessment }, client) | ||
|
||
const update = await AssessmentController.getOneWithCycle( | ||
{ assessmentName: 'fra', cycleName: '2025', metaCache: true }, | ||
client | ||
) | ||
|
||
// **** update calculated cols | ||
const nodes = await client.map<NodeUpdate>( | ||
`select s.props ->> 'name' as section_name | ||
, t.props ->> 'name' as table_name | ||
, r.props ->> 'variableName' as variable_name | ||
, c.props ->> 'colName' as col_name | ||
from ${schemaName}.col c | ||
left join ${schemaName}.row r on r.id = c.row_id | ||
left join ${schemaName}."table" t on t.id = r.table_id | ||
left join ${schemaName}.table_section ts on ts.id = t.table_section_id | ||
left join ${schemaName}.section s on s.id = ts.section_id | ||
where s.props ->> 'name' = 'sustainableDevelopment' | ||
and t.props ->> 'name' = 'sustainableDevelopment15_1_1' | ||
and r.props ->> 'variableName' = 'forestAreaProportionLandArea2015' | ||
and c.props ->> 'colName' in ('2020', '2021', '2022', '2023', '2024')`, | ||
[], | ||
(res) => Objects.camelize(res) | ||
) | ||
|
||
await updateDependencies( | ||
{ assessment: update.assessment, cycle: update.cycle, nodes, includeSourceNodes: true }, | ||
client | ||
) | ||
} |