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

PWL Impact Calculation Metric #589

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/cplus_plugin/definitions/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,12 @@

DEFAULT_BASE_COMPARISON_REPORT_NAME = "Scenario Comparison Report"
MAXIMUM_COMPARISON_REPORTS = 10

PWL_IMPACT_EXPRESSION_DESCRIPTION = (
"Calculates the impact of the "
"current activity by multiplying "
"the area of the activity (in hectares) "
"by a user-defined number of jobs created per "
"hectare. The activity area will be "
"automatically populated during the computation."
)
89 changes: 88 additions & 1 deletion src/cplus_plugin/lib/reports/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
QgsExpressionContextGenerator,
QgsExpressionContextScope,
QgsExpressionContextUtils,
QgsExpressionNodeFunction,
QgsProject,
QgsScopedExpressionFunction,
)

from ...definitions.defaults import BASE_PLUGIN_NAME
from ...definitions.defaults import BASE_PLUGIN_NAME, PWL_IMPACT_EXPRESSION_DESCRIPTION
from ...models.report import ActivityContextInfo, MetricEvalResult
from ...utils import function_help_to_html, log, tr

Expand All @@ -27,6 +28,86 @@
VAR_ACTIVITY_NAME = "cplus_activity_name"
VAR_ACTIVITY_ID = "cplus_activity_id"

# Function names
FUNC_PWL_IMPACT = "pwl_impact"


class ActivityPwlImpactFunction(QgsScopedExpressionFunction):
"""Calculates the PWL impact an activity."""

def __init__(self):
arg_name = "custom_impact"
example_intro = (
f"For an activity with an area of 20,000 ha, "
f"{FUNC_PWL_IMPACT}(1.5) will return"
)
help_html = function_help_to_html(
FUNC_PWL_IMPACT,
tr(PWL_IMPACT_EXPRESSION_DESCRIPTION),
[
(
arg_name,
tr(
"An integer or float representing the "
"number of jobs created per hectare."
),
False,
)
],
[(tr(example_intro), "30,000")],
)
super().__init__(
FUNC_PWL_IMPACT, 1, BASE_PLUGIN_NAME, help_html, isContextual=True
)

def func(
self,
values: typing.List[typing.Any],
context: QgsExpressionContext,
parent: QgsExpression,
node: QgsExpressionNodeFunction,
) -> typing.Any:
"""Returns the result of evaluating the function.

:param values: List of values passed to the function
:type values: typing.Iterable[typing.Any]

:param context: Context expression is being evaluated against
:type context: QgsExpressionContext

:param parent: Parent expression
:type parent: QgsExpression

:param node: Expression node
:type node: QgsExpressionNodeFunction

:returns: The result of the function.
:rtype: typing.Any
"""
if len(values) == 0:
return -1.0

if not context.hasVariable(VAR_ACTIVITY_AREA):
return -1.0

num_jobs = values[0]
activity_area = context.variable(VAR_ACTIVITY_AREA)

if not isinstance(activity_area, (float, int)) or not isinstance(
num_jobs, (float, int)
):
return -1.0

return activity_area * num_jobs

def clone(self) -> "ActivityPwlImpactFunction":
"""Gets a clone of this function.

:returns: A clone of this function.
:rtype: ActivityPwlImpactFunction
"""
return ActivityPwlImpactFunction()


def create_metrics_expression_scope() -> QgsExpressionContextScope:
"""Creates the expression context scope for activity metrics.
Expand Down Expand Up @@ -59,6 +140,8 @@ def create_metrics_expression_scope() -> QgsExpressionContextScope:
description=tr("The name of the activity being evaluated."),
)
)
# Add functions
expression_scope.addFunction(FUNC_PWL_IMPACT, ActivityPwlImpactFunction())

return expression_scope

Expand All @@ -67,6 +150,10 @@ def register_metric_functions():
"""Register our custom functions with the expression engine."""
# Add expression functions to be registered here

# PWL impact
activity_pwl_impact_function = ActivityPwlImpactFunction()
METRICS_LIBRARY.append(activity_pwl_impact_function)

for func in METRICS_LIBRARY:
QgsExpression.registerFunction(func)

Expand Down
19 changes: 19 additions & 0 deletions test/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from cplus_plugin.lib.reports.metrics import (
create_metrics_expression_context,
evaluate_activity_metric,
FUNC_PWL_IMPACT,
register_metric_functions,
unregister_metric_functions,
)
Expand Down Expand Up @@ -94,3 +95,21 @@ def test_metrics_scope_in_expression_context(self):
metrics_scope_index = context.indexOfScope(BASE_PLUGIN_NAME)

self.assertNotEqual(metrics_scope_index, -1)

def test_activity_pwl_impact_expression_function(self):
"""Test the calculation of the PWL impact of an activity
using an expression function.
"""
reference_area = 2000
custom_jobs_per_ha = 1.5

register_metric_functions()
context = create_metrics_expression_context()
activity_context_info = ActivityContextInfo(get_activity(), reference_area)

result = evaluate_activity_metric(
context, activity_context_info, f"{FUNC_PWL_IMPACT}({custom_jobs_per_ha!s})"
)

self.assertTrue(result.success)
self.assertEqual(result.value, reference_area * custom_jobs_per_ha)
Loading