|
14 | 14 | from libecalc.common.time_utils import Frequency, Period, Periods |
15 | 15 | from libecalc.common.utils.rates import RateType |
16 | 16 | from libecalc.common.variables import ExpressionEvaluator, VariablesMap |
17 | | -from libecalc.domain.process.value_objects.fluid_stream.fluid_model import FluidModel, EoSModel, FluidComposition |
| 17 | +from libecalc.domain.process.value_objects.fluid_stream.fluid_model import EoSModel, FluidComposition, FluidModel |
18 | 18 | from libecalc.domain.regularity import Regularity |
19 | 19 | from libecalc.domain.resource import Resource |
20 | 20 | from libecalc.examples import advanced, drogon, simple |
|
29 | 29 | from libecalc.presentation.yaml.domain.expression_time_series_pressure import ExpressionTimeSeriesPressure |
30 | 30 | from libecalc.presentation.yaml.domain.time_series_expression import TimeSeriesExpression |
31 | 31 | from libecalc.presentation.yaml.domain.time_series_resource import TimeSeriesResource |
32 | | -from libecalc.presentation.yaml.mappers.fluid_mapper import MEDIUM_MW_19P4, RICH_MW_21P4, DRY_MW_18P3 |
| 32 | +from libecalc.presentation.yaml.mappers.fluid_mapper import DRY_MW_18P3, MEDIUM_MW_19P4, RICH_MW_21P4 |
33 | 33 | from libecalc.presentation.yaml.model import YamlModel |
34 | 34 | from libecalc.presentation.yaml.resource_service import ResourceService, TupleWithError |
35 | 35 | from libecalc.presentation.yaml.yaml_entities import MemoryResource, ResourceStream |
@@ -132,6 +132,97 @@ def advanced_yaml_path(): |
132 | 132 | return valid_example_cases["advanced"] |
133 | 133 |
|
134 | 134 |
|
| 135 | +@pytest.fixture(scope="session") |
| 136 | +def advanced_yaml_sampled_path(tmp_path_factory): |
| 137 | + """Create advanced example with sampled timesteps for faster testing. |
| 138 | +
|
| 139 | + Reduces test time by ~75% (from ~40s to ~10s per test) by sampling 5 key timesteps |
| 140 | + that preserve numerical range, temporal model periods, and edge cases from the |
| 141 | + original 21-year model: |
| 142 | + - 2020: Stable baseline + high flare period (flare_a=5000, flare_b=10000) |
| 143 | + - 2023: First production change (OIL_PROD: 1000 → 2500) |
| 144 | + - 2030: Peak production (OIL_PROD: 9000) + low flare period (flare_a=2000, flare_b=7000) |
| 145 | + - 2040: Late decline phase |
| 146 | + - 2041: Final timestep (all zeros) |
| 147 | +
|
| 148 | + Returns: |
| 149 | + Path: Temporary directory containing the advanced model with sampled base_profile.csv |
| 150 | +
|
| 151 | + Note: |
| 152 | + This fixture uses session scope for efficiency - the directory is created once and |
| 153 | + shared across all tests (read-only). The fixture copies the advanced example directory |
| 154 | + to avoid modifying original files used by documentation. Temporal variables (flare rates) |
| 155 | + have changes at 2020-06-01 and 2030-01-01, so we keep at least one timestep per period. |
| 156 | + """ |
| 157 | + import csv |
| 158 | + import shutil |
| 159 | + |
| 160 | + # Configuration: Sampled years covering key phases and temporal model periods |
| 161 | + SAMPLED_YEARS = {2020, 2023, 2030, 2040, 2041} |
| 162 | + |
| 163 | + # Copy the entire advanced directory to preserve all resources and relative paths |
| 164 | + original_path = valid_example_cases["advanced"] |
| 165 | + advanced_dir = original_path.parent |
| 166 | + |
| 167 | + tmp_dir = tmp_path_factory.mktemp("yaml_configs") |
| 168 | + temp_advanced_dir = tmp_dir / "advanced" |
| 169 | + # Session-scoped: Copy happens once per test session, not per test |
| 170 | + shutil.copytree(advanced_dir, temp_advanced_dir) |
| 171 | + |
| 172 | + # Sample base_profile.csv to reduce computation while preserving numerical range |
| 173 | + base_profile_path = temp_advanced_dir / "base_profile.csv" |
| 174 | + if not base_profile_path.exists(): |
| 175 | + raise FileNotFoundError(f"Expected base_profile.csv not found at {base_profile_path}") |
| 176 | + |
| 177 | + with open(base_profile_path) as f: |
| 178 | + reader = csv.DictReader(f) |
| 179 | + rows = list(reader) |
| 180 | + |
| 181 | + if not rows: |
| 182 | + raise ValueError("base_profile.csv is empty") |
| 183 | + |
| 184 | + # Filter rows to sampled years |
| 185 | + sampled_rows = [row for row in rows if any(str(year) in row["DATE"] for year in SAMPLED_YEARS)] |
| 186 | + |
| 187 | + if not sampled_rows: |
| 188 | + raise ValueError(f"No rows matched sampled years {SAMPLED_YEARS} in base_profile.csv") |
| 189 | + |
| 190 | + # Write sampled CSV back |
| 191 | + with open(base_profile_path, "w", newline="") as f: |
| 192 | + writer = csv.DictWriter(f, fieldnames=rows[0].keys()) |
| 193 | + writer.writeheader() |
| 194 | + writer.writerows(sampled_rows) |
| 195 | + |
| 196 | + return temp_advanced_dir |
| 197 | + |
| 198 | + |
| 199 | +@pytest.fixture(scope="session") |
| 200 | +def advanced_yaml_sampled(advanced_yaml_sampled_path): |
| 201 | + """Create YamlCase for advanced example with sampled timesteps. |
| 202 | +
|
| 203 | + This fixture wraps the sampled advanced directory in a YamlCase object for use |
| 204 | + in integration tests that call model.evaluate_energy_usage() directly. |
| 205 | +
|
| 206 | + For CLI tests, use advanced_yaml_sampled_path directly to get the file path. |
| 207 | +
|
| 208 | + Session-scoped for efficiency since tests only read from the model. |
| 209 | +
|
| 210 | + Returns: |
| 211 | + YamlCase: Loaded model with all resources and sampled timesteps |
| 212 | + """ |
| 213 | + from libecalc.fixtures.case_utils import YamlCaseLoader |
| 214 | + |
| 215 | + # All resource files needed by the advanced model |
| 216 | + resource_names = [ |
| 217 | + "base_profile.csv", |
| 218 | + "compressor_chart.csv", |
| 219 | + "compressor_sampled.csv", |
| 220 | + "genset.csv", |
| 221 | + "pump_chart.csv", |
| 222 | + ] |
| 223 | + return YamlCaseLoader.load(advanced_yaml_sampled_path, "model.yaml", resource_names) |
| 224 | + |
| 225 | + |
135 | 226 | @pytest.fixture(scope="session") |
136 | 227 | def drogon_yaml_path(): |
137 | 228 | return valid_example_cases["drogon"] |
|
0 commit comments