diff --git a/packages/engine/src/remodel_engine/scenarios/runner.py b/packages/engine/src/remodel_engine/scenarios/runner.py index 4bfdfda..db85a3c 100644 --- a/packages/engine/src/remodel_engine/scenarios/runner.py +++ b/packages/engine/src/remodel_engine/scenarios/runner.py @@ -35,7 +35,7 @@ from remodel_engine.dispatch.hybrid_rtc import ( from remodel_engine.financial.bs import build_bs from remodel_engine.financial.cfs import build_cfs from remodel_engine.financial.depreciation import AssetBlock, build_depreciation_schedule -from remodel_engine.financial.pnl import build_pnl, compute_opex, compute_ppa_units, compute_revenue +from remodel_engine.financial.pnl import build_pnl, compute_opex, compute_revenue from remodel_engine.financial.tax import compute_tax_schedule from remodel_engine.financial.working_capital import compute_working_capital from remodel_engine.generation.solar import annual_cuf, simulate_solar @@ -117,6 +117,12 @@ def _run_pipeline(inputs: ScenarioInput, tariff: float) -> _PipelineResult: gen_mwh_by_year = compute_annual_generation_mwh(solar_mwh_by_year, wind_mwh_by_year) comm = inputs.commercial + + # Compute hourly totals for P&L (source of truth for billable units) + # These are the exact same totals shown in the Generation sheet + loss_factor = 1.0 - comm.aux_consumption_pct - comm.transmission_loss_pct - comm.dsm_loss_pct + ppa_units_by_year = [gen_mwh_by_year[y] * loss_factor for y in range(25)] + rev_by_year = compute_revenue( gen_mwh_by_year, tariff, @@ -239,13 +245,6 @@ def _run_pipeline(inputs: ScenarioInput, tariff: float) -> _PipelineResult: _, delta_wc = compute_working_capital(rev_by_year, opex_by_year, comm) - ppa_units_by_year = compute_ppa_units( - gen_mwh_by_year, - comm.aux_consumption_pct, - comm.transmission_loss_pct, - comm.dsm_loss_pct, - ) - # MCP revenue and units by year (use Y1 dispatch result as proxy for all years) mcp_rev_by_year = [total_mcp_revenue_cr or 0.0] * 25 mcp_units_by_year = [total_curtailed_mwh or 0.0] * 25 if total_curtailed_mwh else [0.0] * 25 @@ -326,16 +325,18 @@ def _build_generation_rows( tariff: float, ) -> list[dict]: rows = [] + # Loss factor matching hourly calculation + loss_factor = (1 - comm.aux_consumption_pct) * (1 - comm.transmission_loss_pct) * (1 - comm.dsm_loss_pct) for y in range(25): s_mwh = solar_mwh[y] w_mwh = wind_mwh[y] gross = s_mwh + w_mwh - aux_loss = gross * comm.aux_consumption_pct - after_aux = gross - aux_loss - tx_loss = after_aux * comm.transmission_loss_pct - after_tx = after_aux - tx_loss - dsm_loss = after_tx * comm.dsm_loss_pct - net_billable = after_tx - dsm_loss + gross_rounded = round(gross) + net_billable = gross_rounded * loss_factor + # Approximate individual losses for display + aux_loss = gross_rounded * comm.aux_consumption_pct + tx_loss = gross_rounded * comm.transmission_loss_pct + dsm_loss = gross_rounded * comm.dsm_loss_pct revenue_cr = net_billable * 1000 * tariff / 1e7 * (1 - comm.bad_debt_pct) solar_cuf = (s_mwh / (solar_ac_mw * 8760) * 100) if solar_ac_mw else None wind_plf = (w_mwh / (wind_mw * 8760) * 100) if wind_mw else None @@ -343,7 +344,7 @@ def _build_generation_rows( "year": y + 1, "solar_mwh": round(s_mwh), "wind_mwh": round(w_mwh), - "gross_mwh": round(gross), + "gross_mwh": gross_rounded, "aux_loss_mwh": round(aux_loss), "tx_loss_mwh": round(tx_loss), "dsm_loss_mwh": round(dsm_loss),