Skip to content

Commit 4403286

Browse files
Merge pull request #673 from GIScience/get_template
refactor(indicators): separate templates from metadata and merge all metadata files
2 parents c2db3ea + f341864 commit 4403286

33 files changed

+282
-269
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99
### Other Changes
1010

1111
- a new regression test suite has been added to support safer deployments of new versions ([#820])
12+
- refactor(indicators): separate templates from metadata and merge all metadata files ([#673])
1213
- use Pydantic model from the `geojson-pydantic` library as request model for `bpolys` ([#824])
1314

14-
15+
[#673]: https://github.com/GIScience/ohsome-quality-api/issues/673
1516
[#818]: https://github.com/GIScience/ohsome-quality-api/pull/818
1617
[#820]: https://github.com/GIScience/ohsome-quality-api/issues/820
1718
[#824]: https://github.com/GIScience/ohsome-quality-api/issues/824

ohsome_quality_api/api/api.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,12 @@
4242
)
4343
from ohsome_quality_api.attributes.definitions import get_attributes, load_attributes
4444
from ohsome_quality_api.config import configure_logging
45-
from ohsome_quality_api.definitions import (
46-
ATTRIBUTION_URL,
47-
get_metadata,
48-
)
45+
from ohsome_quality_api.definitions import ATTRIBUTION_URL
4946
from ohsome_quality_api.indicators.definitions import (
5047
IndicatorEnum,
5148
IndicatorEnumRequest,
5249
get_coverage,
50+
get_indicator,
5351
get_indicator_metadata,
5452
)
5553
from ohsome_quality_api.projects.definitions import (
@@ -75,7 +73,6 @@
7573
)
7674
from ohsome_quality_api.utils.helper import (
7775
get_class_from_key,
78-
hyphen_to_camel,
7976
json_serialize,
8077
)
8178
from ohsome_quality_api.utils.validators import (
@@ -450,7 +447,7 @@ async def metadata_indicators(project: ProjectEnum = DEFAULT_PROJECT) -> Any:
450447
)
451448
async def metadata_indicators_by_key(key: IndicatorEnum) -> Any:
452449
"""Get metadata of an indicator by key."""
453-
metadata = get_metadata("indicators", hyphen_to_camel(key.value))
450+
metadata = get_indicator(key.value)
454451
return {"result": {key.value: metadata}}
455452

456453

ohsome_quality_api/definitions.py

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
"""Global Variables and Functions."""
22

33
import glob
4-
import logging
54
from enum import Enum
65
from types import MappingProxyType
7-
from typing import Iterable, Literal
6+
from typing import Literal
87

98
import yaml
109

1110
from ohsome_quality_api.indicators.models import IndicatorMetadata
12-
from ohsome_quality_api.topics.definitions import load_topic_presets
1311
from ohsome_quality_api.utils.helper import (
14-
camel_to_hyphen,
1512
get_module_dir,
1613
)
1714

@@ -77,31 +74,6 @@ def load_metadata(
7774
return metadata
7875

7976

80-
def get_metadata(
81-
module_name: Literal["indicators"], class_name: str
82-
) -> IndicatorMetadata:
83-
"""Get metadata of an indicator based on its class name.
84-
85-
This is implemented outside the metadata class to be able to access metadata of all
86-
indicators without instantiating of those.
87-
88-
Args:
89-
module_name: indicators.
90-
class_name: Class name of an indicator (camel case).
91-
"""
92-
metadata = load_metadata(module_name)
93-
try:
94-
return metadata[camel_to_hyphen(class_name)]
95-
except KeyError:
96-
logging.error("Invalid class name: " + class_name)
97-
raise
98-
99-
100-
# TODO: duplicate of func with the same name in projects/definition.py ?
101-
def get_project_keys() -> Iterable[str]:
102-
return set(t.project for t in load_topic_presets().values())
103-
104-
10577
def get_attribution(data_keys: list) -> str:
10678
"""Return attribution text. Individual attributions are separated by semicolons."""
10779
assert set(data_keys) <= {"OSM", "GHSL", "VNL", "EUBUCCO", "Microsoft Buildings"}

ohsome_quality_api/indicators/attribute_completeness/indicator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def calculate(self) -> None:
6767
self.result.value = None
6868
if self.result.value is None:
6969
return
70-
description = Template(self.metadata.result_description).substitute(
70+
description = Template(self.templates.result_description).substitute(
7171
result=round(self.result.value, 2),
7272
all=round(self.absolute_value_1, 1),
7373
matched=round(self.absolute_value_2, 1),
@@ -79,17 +79,17 @@ def calculate(self) -> None:
7979
if self.result.value >= self.threshold_yellow:
8080
self.result.class_ = 5
8181
self.result.description = (
82-
description + self.metadata.label_description["green"]
82+
description + self.templates.label_description["green"]
8383
)
8484
elif self.threshold_yellow > self.result.value >= self.threshold_red:
8585
self.result.class_ = 3
8686
self.result.description = (
87-
description + self.metadata.label_description["yellow"]
87+
description + self.templates.label_description["yellow"]
8888
)
8989
else:
9090
self.result.class_ = 1
9191
self.result.description = (
92-
description + self.metadata.label_description["red"]
92+
description + self.templates.label_description["red"]
9393
)
9494

9595
def create_figure(self) -> None:

ohsome_quality_api/indicators/attribute_completeness/metadata.yaml

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
label_description:
3+
red: >-
4+
Less than 25% of the features match the expected tags.
5+
yellow: >-
6+
Around 25-75% of the features match the expected tags.
7+
green: >-
8+
More than 75% of the features match the expected tags.
9+
undefined: >-
10+
The quality level could not be calculated for this indicator.
11+
result_description: >-
12+
The ratio of the features (all: $all) compared to features with
13+
expected tags (matched: $matched) is $result.

ohsome_quality_api/indicators/base.py

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import json
2+
import os
23
from abc import ABCMeta, abstractmethod
34

45
import plotly.graph_objects as go
6+
import yaml
57
from geojson import Feature, Polygon
68

7-
from ohsome_quality_api.definitions import get_attribution, get_metadata
8-
from ohsome_quality_api.indicators.models import IndicatorMetadata, Result
9+
from ohsome_quality_api.definitions import get_attribution
10+
from ohsome_quality_api.indicators.definitions import get_indicator
11+
from ohsome_quality_api.indicators.models import (
12+
IndicatorMetadata,
13+
IndicatorTemplates,
14+
Result,
15+
)
916
from ohsome_quality_api.topics.models import BaseTopic as Topic
10-
from ohsome_quality_api.utils.helper import json_serialize
17+
from ohsome_quality_api.utils.helper import (
18+
camel_to_hyphen,
19+
camel_to_snake,
20+
get_module_dir,
21+
json_serialize,
22+
)
1123

1224

1325
class BaseIndicator(metaclass=ABCMeta):
@@ -18,13 +30,14 @@ def __init__(
1830
topic: Topic,
1931
feature: Feature,
2032
) -> None:
21-
self.metadata: IndicatorMetadata = get_metadata(
22-
"indicators", type(self).__name__
33+
self.metadata: IndicatorMetadata = get_indicator(
34+
camel_to_hyphen(type(self).__name__)
2335
)
36+
self.templates: IndicatorTemplates = self.get_template()
2437
self.topic: Topic = topic
2538
self.feature: Feature = feature
2639
self.result: Result = Result(
27-
description=self.metadata.label_description["undefined"],
40+
description=self.templates.label_description["undefined"],
2841
)
2942
self._get_default_figure()
3043

@@ -34,10 +47,7 @@ def as_dict(self, include_data: bool = False, exclude_label: bool = False) -> di
3447
else:
3548
result = self.result.model_dump(by_alias=True)
3649
raw_dict = {
37-
"metadata": self.metadata.model_dump(
38-
by_alias=True,
39-
exclude={"result_description", "label_description"},
40-
),
50+
"metadata": self.metadata.model_dump(by_alias=True),
4151
"topic": self.topic.model_dump(
4252
by_alias=True,
4353
exclude={"ratio_filter"},
@@ -86,6 +96,7 @@ def data(self) -> dict:
8696
data = vars(self).copy()
8797
data.pop("result")
8898
data.pop("metadata")
99+
data.pop("templates")
89100
data.pop("topic")
90101
data.pop("feature")
91102
return json.loads(json.dumps(data, default=json_serialize).encode())
@@ -167,3 +178,13 @@ def _get_default_figure(self) -> None:
167178
raw = fig.to_dict()
168179
raw["layout"].pop("template") # remove boilerplate
169180
self.result.figure = raw
181+
182+
def get_template(self) -> IndicatorTemplates:
183+
"""Get template for indicator."""
184+
indicator_key = camel_to_snake(type(self).__name__)
185+
dir = get_module_dir(f"ohsome_quality_api.indicators.{indicator_key}")
186+
file = os.path.join(dir, "templates.yaml")
187+
with open(file, "r") as file:
188+
raw = yaml.safe_load(file)
189+
templates = IndicatorTemplates(**raw)
190+
return templates

ohsome_quality_api/indicators/building_comparison/indicator.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def calculate(self) -> None:
111111
edge_case = self.check_minor_edge_cases(key)
112112
# ZeroDivisionError can not occur because of `check_major_edge_cases()`
113113
self.ratio[key] = self.area_osm[key] / self.area_ref[key]
114-
template = Template(self.metadata.result_description)
114+
template = Template(self.templates.result_description)
115115
description = template.substitute(
116116
ratio=round(self.ratio[key] * 100, 2),
117117
coverage=round(self.area_cov[key] * 100, 2),
@@ -130,13 +130,13 @@ def calculate(self) -> None:
130130
self.result.class_ = 3
131131
elif self.th_low > self.result.value >= 0:
132132
self.result.class_ = 1
133-
label_description = self.metadata.label_description[self.result.label]
133+
label_description = self.templates.label_description[self.result.label]
134134
self.result.description = " ".join((label_description, result_description))
135135
elif major_edge_case:
136-
label_description = self.metadata.label_description[self.result.label]
136+
label_description = self.templates.label_description[self.result.label]
137137
self.result.description = " ".join((label_description, result_description))
138138
else:
139-
label_description = self.metadata.label_description[self.result.label]
139+
label_description = self.templates.label_description[self.result.label]
140140
edge_case = (
141141
"OSM has substantivly more buildings than the reference datasets. The "
142142
"reference dataset is likely to miss many buildings."

ohsome_quality_api/indicators/building_comparison/metadata.yaml

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
label_description:
3+
red: >-
4+
The completeness of OSM buildings in your area-of-interest is low.
5+
yellow: >-
6+
The completeness of OSM buildings in your area-of-interest is medium.
7+
green: >-
8+
The completeness of OSM buildings in your area-of-interest is high.
9+
undefined: >-
10+
Comparison could not be made.
11+
result_description: >-
12+
The completeness in comparison to $dataset is $ratio%.

0 commit comments

Comments
 (0)