Skip to content

Commit

Permalink
feat: [161] Connect koswat workflow with infrastructures cost export (#…
Browse files Browse the repository at this point in the history
…167)

* chore: Slight refactoring into dataclasses. Added new test to check with/out all types of surroundings

* test: Added checks to validate correct generation of summaries.

* test: Small test correction

* chore: corrections to remove the (unnecessary) use of `math.nan` for costs

* chore: Updated dataclass to have no `nan` during initialization

* test: Added coverage test to guarantee intialization of default values

* test: Added missing coverage tests

* chore: processed review remarks
  • Loading branch information
Carsopre authored Sep 20, 2024
1 parent 35eb820 commit 86630ec
Show file tree
Hide file tree
Showing 17 changed files with 314 additions and 101 deletions.
1 change: 0 additions & 1 deletion koswat/configuration/io/koswat_run_settings_importer.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging
import math
from pathlib import Path
from typing import Any

from koswat.configuration.io.ini import KoswatGeneralIniFom
from koswat.configuration.io.ini.koswat_general_ini_fom import (
Expand Down
34 changes: 12 additions & 22 deletions koswat/configuration/settings/costs/dike_profile_costs_settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import math
from dataclasses import dataclass

from koswat.configuration.koswat_config_protocol import KoswatConfigProtocol

Expand All @@ -7,29 +8,18 @@ def _valid_float_prop(config_property: float) -> bool:
return config_property is not None and not math.isnan(config_property)


@dataclass
class DikeProfileCostsSettings(KoswatConfigProtocol):
added_layer_grass_m3: float
added_layer_clay_m3: float
added_layer_sand_m3: float
reused_layer_grass_m3: float
reused_layer_core_m3: float
disposed_material_m3: float
profiling_layer_grass_m2: float
profiling_layer_clay_m2: float
profiling_layer_sand_m2: float
bewerken_maaiveld_m2: float

def __init__(self) -> None:
self.added_layer_grass_m3 = math.nan
self.added_layer_clay_m3 = math.nan
self.added_layer_sand_m3 = math.nan
self.reused_layer_grass_m3 = math.nan
self.reused_layer_core_m3 = math.nan
self.disposed_material_m3 = math.nan
self.profiling_layer_grass_m2 = math.nan
self.profiling_layer_clay_m2 = math.nan
self.profiling_layer_sand_m2 = math.nan
self.bewerken_maaiveld_m2 = math.nan
added_layer_grass_m3: float = math.nan
added_layer_clay_m3: float = math.nan
added_layer_sand_m3: float = math.nan
reused_layer_grass_m3: float = math.nan
reused_layer_core_m3: float = math.nan
disposed_material_m3: float = math.nan
profiling_layer_grass_m2: float = math.nan
profiling_layer_clay_m2: float = math.nan
profiling_layer_sand_m2: float = math.nan
bewerken_maaiveld_m2: float = math.nan

def is_valid(self) -> bool:
return all(_valid_float_prop(_prop) for _prop in self.__dict__.values())
19 changes: 7 additions & 12 deletions koswat/configuration/settings/costs/koswat_costs_settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import math
from dataclasses import dataclass

from koswat.configuration.koswat_config_protocol import KoswatConfigProtocol
from koswat.configuration.settings.costs.construction_costs_settings import (
Expand All @@ -15,19 +16,13 @@
)


@dataclass
class KoswatCostsSettings(KoswatConfigProtocol):
price_year: int
dike_profile_costs: DikeProfileCostsSettings
infrastructure_costs: InfrastructureCostsSettings
surtax_costs: SurtaxCostsSettings
construction_costs: ConstructionCostsSettings

def __init__(self) -> None:
self.price_year = math.nan
self.dike_profile_costs = None
self.infrastructure_costs = None
self.surtax_costs = None
self.construction_costs = None
price_year: int = math.nan
dike_profile_costs: DikeProfileCostsSettings = None
infrastructure_costs: InfrastructureCostsSettings = None
surtax_costs: SurtaxCostsSettings = None
construction_costs: ConstructionCostsSettings = None

def is_valid(self) -> bool:
return (
Expand Down
14 changes: 8 additions & 6 deletions koswat/configuration/settings/koswat_run_scenario_settings.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from dataclasses import dataclass
from pathlib import Path

from koswat.configuration.koswat_config_protocol import KoswatConfigProtocol
Expand All @@ -12,13 +13,14 @@
from koswat.dike.surroundings.wrapper.surroundings_wrapper import SurroundingsWrapper


@dataclass
class KoswatRunScenarioSettings(KoswatConfigProtocol):
scenario: KoswatScenario
reinforcement_settings: KoswatReinforcementSettings
surroundings: SurroundingsWrapper
costs_setting: KoswatCostsSettings
output_dir: Path
input_profile_case: KoswatProfileBase
scenario: KoswatScenario = None
reinforcement_settings: KoswatReinforcementSettings = None
surroundings: SurroundingsWrapper = None
costs_setting: KoswatCostsSettings = None
output_dir: Path = None
input_profile_case: KoswatProfileBase = None

@property
def name(self) -> str:
Expand Down
7 changes: 3 additions & 4 deletions koswat/core/io/ini/koswat_ini_reader.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import configparser
from dataclasses import dataclass
from pathlib import Path
from typing import Type

from koswat.core.io.ini.koswat_ini_fom_protocol import KoswatIniFomProtocol
from koswat.core.io.koswat_reader_protocol import KoswatReaderProtocol


@dataclass
class KoswatIniReader(KoswatReaderProtocol):

koswat_ini_fom_type: Type[KoswatIniFomProtocol]

def __init__(self) -> None:
self.koswat_ini_fom_type = None
koswat_ini_fom_type: Type[KoswatIniFomProtocol] = None

def supports_file(self, file_path: Path) -> bool:
return isinstance(file_path, Path) and file_path.suffix == ".ini"
Expand Down
17 changes: 11 additions & 6 deletions koswat/cost_report/infrastructure/infrastructure_location_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,18 @@ class InfrastructureLocationCosts:

infrastructure: SurroundingsInfrastructure = None
location: PointSurroundings = None
zone_a: float = math.nan
zone_a_costs: float = math.nan
zone_a: float = 0
zone_a_costs: float = 0

zone_b: float = math.nan
zone_b_costs: float = math.nan
surtax: float = math.nan
zone_b: float = 0
zone_b_costs: float = 0
surtax: float = 0

@property
def total_cost(self) -> float:
return self.zone_a_costs + self.zone_b_costs
def valid_cost(cost: float) -> float:
if math.isnan(cost):
return 0
return cost

return valid_cost(self.zone_a_costs) + valid_cost(self.zone_b_costs)
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import math
from dataclasses import dataclass

from koswat.cost_report.cost_report_protocol import CostReportProtocol
Expand Down Expand Up @@ -32,7 +31,7 @@ def location(self) -> PointSurroundings | None:
@property
def total_cost(self) -> float:
if not self.infrastructure_location_costs:
return math.nan
return 0
return (
self.infrastructure_location_costs.zone_a_costs
+ self.infrastructure_location_costs.zone_b_costs
Expand All @@ -41,5 +40,5 @@ def total_cost(self) -> float:
@property
def total_cost_with_surtax(self) -> float:
if not self.infrastructure_location_costs:
return math.nan
return 0
return self.total_cost * self.infrastructure_location_costs.surtax
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class InfrastructureProfileCostsCalculator:

infrastructure: SurroundingsInfrastructure = None
surtax: float = math.nan
zone_a_costs: float = math.nan
zone_b_costs: float = math.nan
zone_a_costs: float = 0
zone_b_costs: float = 0

def calculate(
self, zone_a_width: float, zone_b_width: float
Expand All @@ -49,6 +49,7 @@ def calculate(
location=_location,
)
for _location in self.infrastructure.points
if any(_location.surroundings_matrix.items())
]

def _calculate_at_location(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def _calculate_zone_b(self) -> tuple[float, float]:
self.reinforced_profile.old_profile
)
_zone_b = self.reinforced_profile.points[-1].x - _left_limit
return (math.nan, _zone_b)
return (0, _zone_b)

def calculate(self) -> tuple[float, float]:
"""
Expand Down
7 changes: 3 additions & 4 deletions koswat/cost_report/summary/koswat_summary_builder.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import math
from dataclasses import dataclass

from koswat.configuration.settings.koswat_run_scenario_settings import (
KoswatRunScenarioSettings,
Expand Down Expand Up @@ -29,11 +30,9 @@
from koswat.strategies.strategy_input import StrategyInput


@dataclass
class KoswatSummaryBuilder(BuilderProtocol):
run_scenario_settings: KoswatRunScenarioSettings

def __init__(self) -> None:
self.run_scenario_settings = None
run_scenario_settings: KoswatRunScenarioSettings = None

@staticmethod
def _get_corrected_koswat_scenario(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class ObstacleSurroundingsWrapper(BaseSurroundingsWrapper):
apply_buildings: bool = False
apply_railways: bool = False
apply_waters: bool = False
apply_infrastructure: bool = True

reinforcement_min_separation: float = math.nan
reinforcement_min_buffer: float = math.nan
Expand Down
5 changes: 3 additions & 2 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

from pytest import FixtureRequest

test_data = Path(__file__).parent / "test_data"
test_results = Path(__file__).parent / "test_results"
test_data = Path(__file__).parent.joinpath("test_data")
test_data_acceptance = test_data.joinpath("acceptance")
test_results = Path(__file__).parent.joinpath("test_results")

if not test_results.is_dir():
test_results.mkdir(parents=True)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from koswat.cost_report.infrastructure.infrastructure_location_costs import (
InfrastructureLocationCosts,
)


class TestInfrastructureLocationCosts:
def test_initialization(self):
# 1. Initialize class
_infra_location_costs = InfrastructureLocationCosts()

# 2. Verify expectations.
assert isinstance(_infra_location_costs, InfrastructureLocationCosts)
assert not _infra_location_costs.infrastructure
assert not _infra_location_costs.location

# Verify default values are not `math.nan``.
# (Otherwise the `.csv` files could become invalid)
assert _infra_location_costs.zone_a == 0
assert _infra_location_costs.zone_b == 0
assert _infra_location_costs.zone_a_costs == 0
assert _infra_location_costs.zone_b_costs == 0
assert _infra_location_costs.surtax == 0
assert _infra_location_costs.total_cost == 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from koswat.cost_report.cost_report_protocol import CostReportProtocol
from koswat.cost_report.infrastructure.infrastructure_location_profile_cost_report import (
InfrastructureLocationProfileCostReport,
)


class TestInfrastructureLocationProfileCostReport:
def test_initialize(self):
# 1. Define test data.
_report = InfrastructureLocationProfileCostReport(
reinforced_profile=None,
infrastructure=None,
infrastructure_location_costs=None,
)

# 2. Verify expectations.
assert isinstance(_report, InfrastructureLocationProfileCostReport)
assert isinstance(_report, CostReportProtocol)

# Verify fallback values.
assert not _report.location
assert _report.total_cost == 0
assert _report.total_cost_with_surtax == 0
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ def test_initialize(self):
assert isinstance(_calculator, InfrastructureProfileCostsCalculator)
assert not _calculator.infrastructure
assert math.isnan(_calculator.surtax)
assert math.isnan(_calculator.zone_a_costs)
assert math.isnan(_calculator.zone_b_costs)
assert _calculator.zone_a_costs == 0
assert _calculator.zone_b_costs == 0

def test_given_infrastructure_fixture_calculates_costs(
self,
Expand Down
10 changes: 5 additions & 5 deletions tests/cost_report/infrastructure/test_profile_zone_calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,31 +87,31 @@ def test_given_no_base_profile_calculate_returns_nan_tuple(
pytest.param(
_waterside_reinforced_points
+ [(3, 7), (13, 7), (40.87, -0.6), (68.87, -0.6), (74, -2)],
(math.nan, 74),
(0, 74),
id="With dh0 increase to 1 - Soil reinforcement",
),
pytest.param(
_waterside_reinforced_points
+ [(3, 7), (13, 7), (44.17, -1.5), (54.17, -1.5), (56, -2)],
(math.nan, 56),
(0, 56),
id="With dh0 increase to 1 - Vertical Piping Solution",
),
pytest.param(
_waterside_reinforced_points
+ [(3, 7), (13, 7), (46, -2), (46, -2), (46, -2)],
(math.nan, 46),
(0, 46),
id="With dh0 increase to 1 - Piping Wall reinforcement",
),
pytest.param(
_waterside_reinforced_points
+ [(3, 7), (13, 7), (34, -2), (34, -2), (34, -2)],
(math.nan, 34),
(0, 34),
id="With dh0 increase to 1 - Stability Wall reinforcement",
),
pytest.param(
_waterside_reinforced_points
+ [(0, 7), (10, 7), (34, -2), (34, -2), (34, -2)],
(math.nan, 34),
(0, 34),
id="With dh0 increase to 1- Cofferdam reinforcement",
),
],
Expand Down
Loading

0 comments on commit 86630ec

Please sign in to comment.