From 9fd1822013c350c46003240571901e854f1b5003 Mon Sep 17 00:00:00 2001 From: Pablo de la Mora Lobaton <pdlmora@gfz-potsdam.de> Date: Tue, 5 Nov 2024 11:26:08 +0100 Subject: [PATCH] Add general year of construction rule for buildings --- .gitlab-ci.yml | 2 +- .../tabula/year_of_construction/rule.xml | 15 +++ .../year_of_construction.py | 113 +++++++++++++++++ .../boundary.txt | 0 .../rule.xml | 5 +- ...f_construction_from_valencian_cadaster.py} | 22 ++-- tests/conftest.py | 114 +++++++++++++++++- .../tests_valencia.txt | 7 ++ tests/test_height_and_floorspace_rule.py | 17 ++- ...nstruction_from_valencian_cadaster_rule.py | 42 +++++++ tests/test_year_of_construction_rule.py | 79 ++++++++++++ 11 files changed, 397 insertions(+), 19 deletions(-) create mode 100644 building/02_process/tabula/year_of_construction/rule.xml create mode 100644 building/02_process/tabula/year_of_construction/year_of_construction.py rename building/02_process/tabula/{date_from_valencian_cadaster => year_of_construction_from_valencian_cadaster}/boundary.txt (100%) rename building/02_process/tabula/{date_from_valencian_cadaster => year_of_construction_from_valencian_cadaster}/rule.xml (70%) rename building/02_process/tabula/{date_from_valencian_cadaster/date_from_valencian_cadaster.py => year_of_construction_from_valencian_cadaster/year_of_construction_from_valencian_cadaster.py} (69%) create mode 100644 tests/data/year_of_construction_from_valencian_cadaster/tests_valencia.txt create mode 100644 tests/test_year_of_construction_from_valencian_cadaster_rule.py create mode 100644 tests/test_year_of_construction_rule.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4277654..c5f9ca4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: python:3.9-slim +image: python:3.11.10 # Make pip cache the installed dependencies variables: diff --git a/building/02_process/tabula/year_of_construction/rule.xml b/building/02_process/tabula/year_of_construction/rule.xml new file mode 100644 index 0000000..df91dd1 --- /dev/null +++ b/building/02_process/tabula/year_of_construction/rule.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<rule name="YearOfConstructionRule"> + <input> + <param type="int">tags</param> + <param type="float">relations</param> + <param type="int">year_of_construction_cadaster</param> + </input> + <function filepath="year_of_construction.py"/> + <dependencies> + <dependency name="YearOfConstructionFromValencianCadasterRule"/> + </dependencies> + <output> + <param type="int">year_of_construction</param> + </output> +</rule> diff --git a/building/02_process/tabula/year_of_construction/year_of_construction.py b/building/02_process/tabula/year_of_construction/year_of_construction.py new file mode 100644 index 0000000..37efebe --- /dev/null +++ b/building/02_process/tabula/year_of_construction/year_of_construction.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2024: +# Helmholtz-Zentrum Potsdam Deutsches GeoForschungsZentrum GFZ +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero +# General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + +from rulelib import AbstractRule + + +class YearOfConstructionRule(AbstractRule): + def __call__( + self, + tags: dict, + relations: list, + year_of_construction_cadaster: int | None = None, + *args, + **kwargs + ) -> dict[str, int | None]: + """ + Obtain the year of construction of a building from any available source, presently only + OSM and cadaster data. In OSM, the time of construction is stored in the `start_date` + tag of which only an explicit four-digit year with or without a leading `~` (signifying + an approximation) are considered. Priority is given to exact years of construction from + OSM, followed by those from the cadaster data and finally approximate dates from OSM. + + Args: + tags (dict): + Building tags, such as the building levels or building type. + relations (list): + List of the attributes of all relations that the building is member of. + year_of_construction_cadaster (int, optional, default: None): + Year of construction taken from cadaster data. + + Returns: + dict[str, int | None]: + A dictionary with the construction year of the building as an integer or None. + """ + + all_building_tags = [tags] + [building["tags"] for building in relations] + + year_of_construction_string_approximate = None + + for tags in all_building_tags: + year_of_construction_string = self.get_start_date(tags) + if year_of_construction_string is None: + continue + else: + # Years with leading `~` are approximate dates, these values are set + # last in the priority list if other sources are available. + if ( + year_of_construction_string[0] == "~" + and year_of_construction_string_approximate is None + ): + year_of_construction_string_approximate = year_of_construction_string[1:5] + continue + # Years from OSM without a leading `~` are at the top of the priority list, + # and returned as soon as one is found. + else: + year_of_construction_string = year_of_construction_string[:4] + try: + year_of_construction = int(year_of_construction_string) + return {"year_of_construction": year_of_construction} + except ValueError: + continue + + # If no unambiguous year is found from the OSM tags, the cadaster year of construction + # is returned, second in the priority list. + if year_of_construction_cadaster is not None: + return {"year_of_construction": year_of_construction_cadaster} + # If the there is no construction year from the cadaster data, the approximate year is + # returned. + elif year_of_construction_string_approximate is not None: + try: + year_of_construction = int(year_of_construction_string_approximate) + return {"year_of_construction": year_of_construction} + except ValueError: + return {"year_of_construction": None} + # If no source has a valid year of construction, None is returned. + else: + return {"year_of_construction": None} + + @staticmethod + def get_start_date(tags: dict) -> str | None: + """ + Get the building year of construction based on the year in the `start_date` tag from + the OSM tags dictionary. + + Args: + tags (dict): + Dictionary with building tags from OSM, which may include `start_date`, `name`, + `type`, etc. + + Returns: + str | None: + String value of the tag `start_date` which represents the date in which a + building was completed, otherwise None. The date format may vary, as there are + many variations within the OSM `start_date` tag, for this rule, only the year is + relevant. + """ + + return tags.get("start_date", None) diff --git a/building/02_process/tabula/date_from_valencian_cadaster/boundary.txt b/building/02_process/tabula/year_of_construction_from_valencian_cadaster/boundary.txt similarity index 100% rename from building/02_process/tabula/date_from_valencian_cadaster/boundary.txt rename to building/02_process/tabula/year_of_construction_from_valencian_cadaster/boundary.txt diff --git a/building/02_process/tabula/date_from_valencian_cadaster/rule.xml b/building/02_process/tabula/year_of_construction_from_valencian_cadaster/rule.xml similarity index 70% rename from building/02_process/tabula/date_from_valencian_cadaster/rule.xml rename to building/02_process/tabula/year_of_construction_from_valencian_cadaster/rule.xml index 732737f..a9e9ceb 100644 --- a/building/02_process/tabula/date_from_valencian_cadaster/rule.xml +++ b/building/02_process/tabula/year_of_construction_from_valencian_cadaster/rule.xml @@ -1,18 +1,19 @@ <?xml version="1.0" encoding="UTF-8" ?> -<rule name="DateFromValencianCadasterRule"> +<rule name="YearOfConstructionFromValencianCadasterRule"> <input> <param type="str">geometry</param> <param type="PostGISDatabase">database</param> <param type="float">longitude</param> <param type="float">latitude</param> </input> - <function filepath="date_from_valencian_cadaster.py"/> + <function filepath="year_of_construction_from_valencian_cadaster.py"/> <database name="source"/> <dependencies> <dependency name="GeometryRule"/> <dependency name="SelectRule"/> </dependencies> <output> + <param type="int">year_of_construction_cadaster</param> </output> <filter filepath="boundary.txt"/> </rule> \ No newline at end of file diff --git a/building/02_process/tabula/date_from_valencian_cadaster/date_from_valencian_cadaster.py b/building/02_process/tabula/year_of_construction_from_valencian_cadaster/year_of_construction_from_valencian_cadaster.py similarity index 69% rename from building/02_process/tabula/date_from_valencian_cadaster/date_from_valencian_cadaster.py rename to building/02_process/tabula/year_of_construction_from_valencian_cadaster/year_of_construction_from_valencian_cadaster.py index f604a5e..4e363b7 100644 --- a/building/02_process/tabula/date_from_valencian_cadaster/date_from_valencian_cadaster.py +++ b/building/02_process/tabula/year_of_construction_from_valencian_cadaster/year_of_construction_from_valencian_cadaster.py @@ -17,15 +17,18 @@ from rulelib import AbstractRule -class DateFromValencianCadasterRule(AbstractRule): +class YearOfConstructionFromValencianCadasterRule(AbstractRule): from databaselib import PostGISDatabase - def __call__(self, database: PostGISDatabase, geometry: str, *args, **kwargs): + def __call__( + self, database: PostGISDatabase, geometry: str, *args, **kwargs + ) -> dict[str, int | None]: """ This rule provides an additional source for a building's construction year for the - Valencia province in Spain if it is not available in OSM. A building's geometry is - tested for intersection with a cadastral parcel in the source database. If such a parcel - exists, its construction date is taken from the related cadastral buildings table. + Valencia province in Spain in case the year of construction is not available in OSM. A + building's geometry is tested for intersection with a cadastral parcel in the source + database. If such a parcel exists, its construction year of construction is taken from + the related cadastral buildings table. Args: database (PostGISDatabase): @@ -36,12 +39,13 @@ class DateFromValencianCadasterRule(AbstractRule): formatted string wrapped in the `ST_GeomFromText()` function. Returns: - Integer that represents the building's construction year. + dict[str, int | None]: + A dictionary with the construction year as an int or None. """ sql_statement = f""" - WITH b (geom) AS (VALUES ({geometry}) + WITH b (geom) AS (VALUES ({geometry})) SELECT construction_year FROM Parcels INNER JOIN cadaster_buildings USING(parcel_id) @@ -53,6 +57,6 @@ class DateFromValencianCadasterRule(AbstractRule): construction_year = database.cursor.fetchone() if construction_year is None or construction_year == "": - return {"date_cadaster": None} + return {"year_of_construction_cadaster": None} else: - return {"date_cadaster": int(construction_year[0])} + return {"year_of_construction_cadaster": int(construction_year[0])} diff --git a/tests/conftest.py b/tests/conftest.py index f88e873..1739075 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,6 +21,7 @@ import shutil import tempfile import os +from databaselib import PostGISDatabase from rulelib import Rule @@ -32,17 +33,122 @@ def height_and_floorspace_rule(): fixture. """ - # Get the filepath of the rule + # Get the filepath of the rule. project_dir = os.getenv("CI_PROJECT_DIR", "") rule_dir = os.path.join(project_dir, "building/02_process/height_and_floorspace") # Create a temporary ZIP file from the `height_and_floorspace` rule. tmp_dir = tempfile.mkdtemp() - file_path = os.path.join(tmp_dir + "height_and_floorspace") - shutil.make_archive(file_path, "zip", rule_dir) + filepath = os.path.join(tmp_dir + "height_and_floorspace") + shutil.make_archive(filepath, "zip", rule_dir) # Yield rule. - yield Rule.load_rule_from_zip(open(file_path + ".zip", "rb")) + yield Rule.load_rule_from_zip(open(filepath + ".zip", "rb")) # Remove temporary ZIP file. shutil.rmtree(tmp_dir, ignore_errors=True) + + +@pytest.fixture +def year_of_construction_from_valencian_cadaster_rule(): + """ + Creates a temporary directory, where the `YearOfConstructionFromValencianCadasterRule` + rule is converted to a ZIP file. This ZIP file is parsed using rule-lib and the parsed + rule is provided as a fixture. + """ + + # Get the filepath of the rule. + project_dir = os.getenv("CI_PROJECT_DIR", "") + rule_dir = os.path.join( + project_dir, "building/02_process/tabula/year_of_construction_from_valencian_cadaster" + ) + + # Create a temporary ZIP file from the `height_and_floorspace` rule. + tmp_dir = tempfile.mkdtemp() + filepath = os.path.join(tmp_dir + "year_of_construction_from_valencian_cadaster") + shutil.make_archive(filepath, "zip", rule_dir) + + # Yield rule. + yield Rule.load_rule_from_zip(open(filepath + ".zip", "rb")) + + # Remove temporary ZIP file. + shutil.rmtree(tmp_dir, ignore_errors=True) + + +@pytest.fixture +def year_of_construction_rule(): + """ + Creates a temporary directory, where the `YearOfConstructionRule` rule is converted to a + ZIP file. This ZIP file is parsed using `rule-lib` and the parsed rule is provided as a + fixture. + """ + + # Get the file path of the rule. + project_dir = os.getenv("CI_PROJECT_DIR", "") + rule_dir = os.path.join(project_dir, "building/02_process/tabula/year_of_construction") + + # Create a temporary ZIP file from the `height_and_floorspace` rule. + tmp_dir = tempfile.mkdtemp() + filepath = os.path.join(tmp_dir + "year_of_construction") + shutil.make_archive(filepath, "zip", rule_dir) + + # Yield rule. + yield Rule.load_rule_from_zip(open(filepath + ".zip", "rb")) + + # Remove temporary ZIP file. + shutil.rmtree(tmp_dir, ignore_errors=True) + + +@pytest.fixture +def database(): + """ + Returns a database configuration to connect to a PostGIS database based on environment + variables provided either in the CI/CD pipeline variables or stored locally in the package + directory. The following environment variables are used: + `DB_NAME`: Name of the database the rule is connecting to. + `DB_HOST`: Host name (or IP address) of the database. + `DB_PORT`: Port (at the host computer) to connect to the database. + `DB_USERNAME`: Username of the account to access the database. + `DB_PASSWORD`: Password of the user account. + """ + + db_config = { + "dbname": os.getenv("DB_NAME", None), + "host": os.getenv("DB_HOST", None), + "port": os.getenv("DB_PORT", None), + "username": os.getenv("DB_USERNAME", None), + "password": os.getenv("DB_PASSWORD", None), + } + rule_database = PostGISDatabase(**db_config) + yield rule_database + + +@pytest.fixture +def test_data_valencia(): + """ + Returns building geometries for buildings in Montesa, Spain, part of the Valencian province, + in order to test the year_of_construction from the Valencian Cadaster Rule. + """ + + # Get the file path of the buildings. + project_dir = os.getenv("CI_PROJECT_DIR", "") + tests_filepath = os.path.join( + project_dir, + "tests/data/year_of_construction_from_valencian_cadaster/tests_valencia.txt", + ) + # Create a list of the building geometries and export. + test_data = [] + with open(tests_filepath, "r") as tests: + for test_set in tests: + test_set = test_set.strip() + lon, lat, year, geom = test_set.split(";") + test_data.append( + [ + float(lon), + float(lat), + int(year) if year != "None" else None, + f"ST_GeomFromText('{geom}', 4326)", + ] + ) + + yield test_data diff --git a/tests/data/year_of_construction_from_valencian_cadaster/tests_valencia.txt b/tests/data/year_of_construction_from_valencian_cadaster/tests_valencia.txt new file mode 100644 index 0000000..c39c071 --- /dev/null +++ b/tests/data/year_of_construction_from_valencian_cadaster/tests_valencia.txt @@ -0,0 +1,7 @@ +-0.41747;39.43838;1900;POLYGON((-0.653067226172624 38.95003797988812,-0.652991118491826 38.95006781946344,-0.652971504838405 38.95002456884305,-0.653009726316867 38.95001207980732,-0.652968235896168 38.94994359965841,-0.652957339422045 38.94992499183334,-0.652943006367593 38.94990227687575,-0.652937139035373 38.949890877487434,-0.652959853992996 38.94988299849845,-0.653067226172624 38.95003797988812)) +-0.41747;39.43838;1922;POLYGON((-0.652986508445082 38.95041927266351,-0.652947281138211 38.950421954872525,-0.652857762412339 38.95042798984281,-0.652773775742531 38.950433689536965,-0.652772769914151 38.95034978668619,-0.65291551372519 38.95030343476165,-0.65293504355958 38.95034199151624,-0.652871257276445 38.95035942587484,-0.652870838181286 38.95039186384011,-0.652967565343914 38.950387672888525,-0.652978042722879 38.95038691851724,-0.652986508445082 38.95041927266351)) +-0.41747;39.438388;1985;POLYGON((-0.65309010876831 38.94938159305062,-0.653016264201341 38.949438589992184,-0.652938312501817 38.949403972732085,-0.652898749918847 38.94937438461389,-0.652917525381952 38.94935837517883,-0.652886931435376 38.94933188836478,-0.652906461269765 38.94931722003423,-0.652956249774633 38.949280255841245,-0.65309010876831 38.94938159305062)) +-0.41747;39.43838;1900;POLYGON((-0.652926326380282 38.9494135281017,-0.652895816252737 38.94943808707799,-0.652885003597646 38.94942869934644,-0.652873017476111 38.9494359916022,-0.652846363024025 38.94940799604561,-0.652865809039383 38.94939492027666,-0.65288081264606 38.94938159305062,-0.652926326380282 38.9494135281017)) +-0.41747;39.43838;1900;POLYGON((-0.653117182315555 38.94960362966569,-0.653059766278801 38.94963707345934,-0.653055407689152 38.94963338542195,-0.653029004694162 38.94960865880759,-0.653021209524212 38.949602540018276,-0.652998578385649 38.949582423450664,-0.652947616414338 38.949534478964495,-0.652999248937903 38.94949768240957,-0.65306261612588 38.94955392497985,-0.653074434609351 38.949564318539785,-0.653056916431723 38.94957487973778,-0.653078625560966 38.94959457721026,-0.653093126253452 38.94958284254582,-0.653117182315555 38.94960362966569)) +-0.41747;39.438388;None;POLYGON((-0.655097323121254 38.94979834127639,-0.655012833537285 38.949965224968594,-0.654796915711529 38.949901690142525,-0.654876040877468 38.949731621327146,-0.655097323121254 38.94979834127639)) +-0.41747;39.43838;None;POLYGON((-0.640721604807396 38.93797273318404,-0.640656980333944 38.93801715727088,-0.640622363073817 38.937986647143305,-0.640540639517894 38.93804263825652,-0.640470902083479 38.937981282725275,-0.640649939535251 38.93785832020572,-0.640713222904225 38.93791414368087,-0.640680533481856 38.93793652336234,-0.640721604807396 38.93797273318404)) diff --git a/tests/test_height_and_floorspace_rule.py b/tests/test_height_and_floorspace_rule.py index c5ad79e..331207d 100644 --- a/tests/test_height_and_floorspace_rule.py +++ b/tests/test_height_and_floorspace_rule.py @@ -72,7 +72,11 @@ def test_height_and_floorspace_rule(height_and_floorspace_rule): ], # Check with building level tag and GHSL. [ - {"tags": {"building:levels": "5"}, "area": 100.0, "ghsl_characteristics_type": 11}, + { + "tags": {"building:levels": "5"}, + "area": 100.0, + "ghsl_characteristics_type": 11, + }, "H:5", # Expected return height. 450.0, # Expected return floorspace. ], @@ -116,14 +120,21 @@ def test_get_stories_and_floorspace_from_osm(height_and_floorspace_rule): # Check with only roof levels. [{"tags": {"roof:levels": "5"}, "area": 100.0}, "H:5", 225.0], # Check with only levels underground. - [{"tags": {"building:levels:underground": "5"}, "area": 100.0}, "HBEX:5", 450.0], + [ + {"tags": {"building:levels:underground": "5"}, "area": 100.0}, + "HBEX:5", + 450.0, + ], # Check with a wrong type as input. [{"tags": {"building:levels": "no"}, "area": 100.0}, None, None], # Check with a negative value. [{"tags": {"building:levels:underground": "-1"}, "area": 100.0}, None, None], # Check with a `building:min_level` value higher than the `building_levels` value. [ - {"tags": {"building:levels": "5", "building:min_level": "6"}, "area": 100.0}, + { + "tags": {"building:levels": "5", "building:min_level": "6"}, + "area": 100.0, + }, None, None, ], diff --git a/tests/test_year_of_construction_from_valencian_cadaster_rule.py b/tests/test_year_of_construction_from_valencian_cadaster_rule.py new file mode 100644 index 0000000..a85d5b1 --- /dev/null +++ b/tests/test_year_of_construction_from_valencian_cadaster_rule.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2024: +# Helmholtz-Zentrum Potsdam Deutsches GeoForschungsZentrum GFZ +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero +# General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + + +def test_year_of_construction_from_valencian_cadaster_rule( + year_of_construction_from_valencian_cadaster_rule, database, test_data_valencia +): + """ + Test the `year_of_construction_from_the_valencian_cadaster` rule with inputs from the test + database and test geometries. + """ + + database.connect() + + for lon, lat, expected_year, geometry in test_data_valencia: + result = year_of_construction_from_valencian_cadaster_rule( + longitude=lon, latitude=lat, **{"database": database, "geometry": geometry} + ) + + message = ( + f"The expected construction year is not correct, {expected_year} was " + f"expected and '{result['year_of_construction_cadaster']}' was returned." + ) + + assert result["year_of_construction_cadaster"] == expected_year, message + + database.close() diff --git a/tests/test_year_of_construction_rule.py b/tests/test_year_of_construction_rule.py new file mode 100644 index 0000000..de4f695 --- /dev/null +++ b/tests/test_year_of_construction_rule.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2024: +# Helmholtz-Zentrum Potsdam Deutsches GeoForschungsZentrum GFZ +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero +# General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. + + +import logging + +logger = logging.getLogger() + + +def test_year_of_construction_rule(year_of_construction_rule): + """ + Test the `year_of_construction` rule with valid construction years from each source, as well + as no dates and invalid ones. + """ + + # Test set with valid dates as an output. + test_data = [ + # Test a valid year of construction from the building tags. + [{"start_date": "1955"}, [], None, 1955], + # Test with an approximate year of construction from OSM and an exact + # year of construction from cadaster. + [{"start_date": "~1950"}, [], 1951, 1951], + # Test with no year of construction from the building tags, but one from its relations. + [{}, [{"osm_id": -8907330, "tags": {"start_date": "1960"}}], None, 1960], + # Test with an invalid year of construction from the building tags and a valid + # year of construction from its relations. + [{}, [{"osm_id": -8907330, "tags": {"start_date": "1960"}}], None, 1960], + # Test with only a cadaster year of construction. + [{"start_date": "before 1922"}, [], 2013, 2013], + # Test with an approximate year of construction from OSM. + [{"start_date": "~1992"}, [], None, 1992], + # Test an approximate year of construction from the building tags and an exact one + # from its relations. + [ + {"start_date": "~1992"}, + [{"osm_id": -8907330, "tags": {"start_date": "1960"}}], + None, + 1960, + ], + # Test with no year of construction from any source + [{}, [], None, None], + # Test with no usable input year of construction from sources. + [ + {"start_date": "before 1922"}, + [{"osm_id": -8907330, "tags": {"start_date": "early 1920s"}}], + None, + None, + ], + ] + + for tags, relations, year_of_construction_cadaster, expected_year in test_data: + result = year_of_construction_rule( + **{ + "tags": tags, + "relations": relations, + "year_of_construction_cadaster": year_of_construction_cadaster, + } + ) + message = ( + f"The expected construction year is not correct, {expected_year} was " + f"expected and '{result['year_of_construction']}' was returned." + ) + + assert result["year_of_construction"] == expected_year, message -- GitLab