Remodel/packages/engine/tests/unit/test_cli.py
Mannu 314127effc [S1-T01 through T11] Solar, Wind, BESS generation simulation + CLI
- 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>
2026-05-07 10:04:21 +05:30

77 lines
2.1 KiB
Python

"""CLI smoke tests."""
import json
from pathlib import Path
import pytest
from typer.testing import CliRunner
from remodel_engine.cli import app
runner = CliRunner()
@pytest.fixture()
def solar_scenario(tmp_path: Path) -> Path:
data = {
"solar": {
"location_id": "RJ",
"capacity_dc_mwp": 10.0,
"capacity_ac_mw": 8.0,
}
}
p = tmp_path / "scenario.json"
p.write_text(json.dumps(data))
return p
@pytest.fixture()
def wind_scenario(tmp_path: Path) -> Path:
data = {
"wind": {
"location_id": "RJ",
"capacity_mw": 10.0,
}
}
p = tmp_path / "scenario.json"
p.write_text(json.dumps(data))
return p
def _invoke(scenario: Path, out: Path) -> object:
# Single-command Typer app: invoke without the subcommand name
return runner.invoke(app, ["--input", str(scenario), "--output", str(out)])
def test_simulate_gen_solar(solar_scenario: Path, tmp_path: Path) -> None:
out = tmp_path / "gen.parquet"
result = _invoke(solar_scenario, out)
assert result.exit_code == 0, result.output # type: ignore[union-attr]
assert out.exists()
assert "Solar Y1 CUF" in result.output # type: ignore[union-attr]
def test_simulate_gen_wind(wind_scenario: Path, tmp_path: Path) -> None:
out = tmp_path / "gen.parquet"
result = _invoke(wind_scenario, out)
assert result.exit_code == 0, result.output # type: ignore[union-attr]
assert out.exists()
assert "Wind Y1 PLF" in result.output # type: ignore[union-attr]
def test_simulate_gen_empty_input(tmp_path: Path) -> None:
p = tmp_path / "empty.json"
p.write_text("{}")
out = tmp_path / "gen.parquet"
result = _invoke(p, out)
assert result.exit_code != 0 # type: ignore[union-attr]
def test_simulate_gen_parquet_has_rows(solar_scenario: Path, tmp_path: Path) -> None:
import pandas as pd
out = tmp_path / "gen.parquet"
result = _invoke(solar_scenario, out)
assert result.exit_code == 0, result.output # type: ignore[union-attr]
df = pd.read_parquet(out)
assert len(df) == 25 * 8760