# Generation Module `remodel_engine.generation` — 25-year hourly simulation for solar, wind, and BESS capacity. ## Functions ### `simulate_solar(config: SolarConfig) → pd.DataFrame` Runs the full solar model chain for 25 years at hourly resolution. **Input**: `SolarConfig` (see `schemas/generation.py`) **Output**: DataFrame with 219 000 rows (25 × 8 760), columns: | Column | Type | Description | |--------|------|-------------| | `year` | int16 | 1–25 | | `hour` | int16 | 0–8759 (within each year) | | `dc_power_mw` | float64 | DC output after wiring + mismatch losses | | `ac_power_mw_pre_clip` | float64 | After inverter + AC losses, before clipping | | `ac_power_mw` | float64 | Final AC output (clipped, availability, soiling, degradation) | | `degradation_factor` | float64 | Year-level multiplier (Y1 = 1−deg_y1, monotonically ↓) | **Model chain**: ``` irradiance_norm × capacity_dc_mwp × (1 − dc_loss) → dc_power_mw dc_power_mw × inverter_η × (1 − ac_loss) → ac_power_mw_pre_clip min(ac_power_mw_pre_clip, capacity_ac_mw) → clipped clipped × availability × (1 − soiling_monthly) → ac_soiled_base ac_soiled_base × degradation_factor → ac_power_mw ``` **Degradation**: - Year 1: `1 − degradation_y1` (LID + initial) - Year n: `(1 − degradation_y1) × (1 − degradation_annual)^(n−1)` --- ### `simulate_wind(config: WindConfig) → pd.DataFrame` **Input**: `WindConfig` **Output**: DataFrame with 219 000 rows, columns: | Column | Type | Description | |--------|------|-------------| | `year` | int16 | 1–25 | | `hour` | int16 | 0–8759 | | `wind_speed_hub` | float64 | Hub-height wind speed (m/s) | | `power_fraction` | float64 | Power curve output (0–1) | | `ac_power_mw` | float64 | Final AC output | | `degradation_factor` | float64 | 1.0 in Y1, `(1−deg_annual)^(n−1)` for Y_n | **Model chain**: ``` wind_speed_ref × (hub_h / ref_h)^shear_α → wind_speed_hub power_curve(wind_speed_hub) → power_fraction power_fraction × capacity_mw → gross_mw gross_mw × (1−wake) × (1−electrical) × availability → net_base net_base × degradation_factor → ac_power_mw ``` **Power curve**: piecewise-linear, cut-in 3 m/s, rated at 12.5 m/s, cut-out 25 m/s. Defined in `catalog/defaults.py::WIND_POWER_CURVE_POINTS`. --- ### `simulate_bess_capacity(config: BessConfig, cum_cycles_per_year: list[float]) → pd.Series` Models physical battery capacity over 25 years. Does **not** simulate dispatch. **Input**: - `config`: `BessConfig` with nameplate MWh, design_cycles, eol_soh, augmentation_schedule - `cum_cycles_per_year`: cumulative full-equivalent cycles at end of each year (from dispatch module, or a conservative default) **Output**: `pd.Series` indexed 1–25, values = usable MWh **Degradation**: ``` SOH = max(eol_soh, 1 − cum_cycles/design_cycles × (1 − eol_soh)) usable_mwh = (nameplate + augmentation_so_far) × SOH ``` --- ## Catalog profiles Bundled 8760-hour CSV files in `catalog/profiles/`: | Location | Solar CSV | Wind CSV | Notes | |----------|-----------|----------|-------| | RJ | `solar/RJ_8760.csv` | `wind/RJ_8760.csv` | Rajasthan ~27°N, desert | | KA | `solar/KA_8760.csv` | `wind/KA_8760.csv` | Karnataka ~13°N, coastal | | GJ | `solar/GJ_8760.csv` | `wind/GJ_8760.csv` | Gujarat ~23°N, Kutch | Solar CSVs: columns `hour, irradiance_norm` (0–1). Wind CSVs: columns `hour, wind_speed_ms` (m/s at 100 m reference height). Load via `catalog.loader.load_solar_profile(location_id)` / `load_wind_profile(location_id)`. Results are LRU-cached per process. ## Adding a new location profile 1. Create `catalog/profiles/solar/_8760.csv` and `catalog/profiles/wind/_8760.csv`. 2. Add the ID to `catalog/loader._VALID_LOCATIONS`. 3. Add a test in `tests/unit/test_catalog.py`. ## CLI ```bash remodel simulate-gen --input scenario.json --output gen.parquet ``` `scenario.json` must have a `"solar"` key (SolarConfig fields) and/or a `"wind"` key (WindConfig fields). ## Parity gate (S1-T10) See `tests/integration/test_parity.py`. Currently skipped pending user-supplied fixture values. To unlock: fill `tests/fixtures/nagasamudra_inputs.json._expected` and remove the `pytest.skip`.