- Pydantic schemas: SolarConfig, WindConfig, BessConfig, GenerationResult - Catalog: synthetic 8760h profiles for RJ/KA/GJ solar and wind; LRU-cached loader - generation/solar.py: 25yr hourly simulation (DC losses, inverter, clipping, soiling, degradation) - generation/wind.py: power-law shear, piecewise power curve, wake/electrical losses, degradation - generation/bess_state.py: SOH-based capacity degradation with augmentation schedule - CLI: remodel --input scenario.json --output gen.parquet (Typer upgraded to 0.25.1 for Click 8.3 compat) - 43 unit tests, 97.4% coverage; mypy strict + ruff clean - S1-T10 parity gate: placeholder fixture + skipped integration tests (awaiting Nagasamudra data) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.3 KiB
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:BessConfigwith nameplate MWh, design_cycles, eol_soh, augmentation_schedulecum_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
- Create
catalog/profiles/solar/<ID>_8760.csvandcatalog/profiles/wind/<ID>_8760.csv. - Add the ID to
catalog/loader._VALID_LOCATIONS. - Add a test in
tests/unit/test_catalog.py.
CLI
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.