diff --git a/packages/web/components/InputsTab.tsx b/packages/web/components/InputsTab.tsx index 736ea56..fbc956d 100644 --- a/packages/web/components/InputsTab.tsx +++ b/packages/web/components/InputsTab.tsx @@ -791,10 +791,16 @@ export function InputsTab({ scenarioId, inputsJson, onSaved }: Props) { const solarBOS = solarItems.filter(it => ["solar_inverter", "solar_dc_bos", "solar_ac_bos", "solar_hr"].includes(it.id)).reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); const solarModule = solarItems.filter(it => it.id === "solar_module").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); const windWTG = windItems.filter(it => ["wind_wtg", "wind_tower", "wind_bop", "wind_e_and_c"].includes(it.id)).reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); - const allLand = [...solarItems, ...windItems].filter(it => it.id.includes("land")).reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); + const solarLandCost = solarItems.filter(it => it.id.includes("land") && it.attribution === "SolarOnly").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); + const windLandCost = windItems.filter(it => it.id.includes("land") && it.attribution === "WindOnly").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); const bessAll = bessItems.reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); - const epcAll = [...solarItems, ...windItems, ...bessItems].filter(it => it.category === "EPCOverhead").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); - const contAll = [...solarItems, ...windItems].filter(it => it.category === "Contingency").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); + const solarEPC = solarItems.filter(it => it.category === "EPCOverhead").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); + const windEPC = windItems.filter(it => it.category === "EPCOverhead").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); + const solarCont = solarItems.filter(it => it.category === "Contingency" && it.attribution === "SolarOnly").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); + const windCont = windItems.filter(it => it.category === "Contingency" && it.attribution === "WindOnly").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); + // EPC Margins + const epcMarginWTG = [...windItems, ...bessItems].filter(it => it.category === "EPCMargin" && it.attribution === "WindOnly").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); + const epcMarginSolarBOS = solarItems.filter(it => it.category === "EPCMargin" && it.attribution === "SolarOnly").reduce((acc, it) => acc + (computeItemCr(it, solarDcMwp, windMw, bessMwh, 0, effectiveLandAcres) ?? 0), 0); // Financing const idcRate = gv(inputs, "capex", "interest_rate_annual", 0.09); const constrMonths = gv(inputs, "capex", "construction_months", 24); @@ -802,6 +808,15 @@ export function InputsTab({ scenarioId, inputsJson, onSaved }: Props) { const idcCost = totalCostCr * debtFrac * idcRate * constrMonths / 12; const upfrontPct = gv(inputs, "capex", "upfront_fee_pct", 0.01); const upfrontCost = totalCostCr * debtFrac * upfrontPct; + const bankGuaranteePct = gv(inputs, "capex", "bank_guarantee_pct", 0.001); + const bankGuaranteeCost = totalCostCr * bankGuaranteePct; + // DSRA (Debt Service Reserve Account) - typically 1-2 months of debt service + const dsraMonths = gv(inputs, "capex", "dsra_months", 2); + const annualDebtService = totalCostCr * debtFrac * 0.12; // approximate annual debt service + const dsraCost = annualDebtService * dsraMonths / 12; + + // Compute Total Hard Cost (sum of all component costs before financing) + const totalHardCost = solarModule + solarBOS + windWTG + solarLandCost + windLandCost + bessAll + dsraCost + solarEPC + windEPC + epcMarginWTG + epcMarginSolarBOS + solarCont + windCont; return (
@@ -813,13 +828,32 @@ export function InputsTab({ scenarioId, inputsJson, onSaved }: Props) { {solarModule > 0 && Solar Module{solarModule.toFixed(2)}} {solarBOS > 0 && Solar BoS{solarBOS.toFixed(2)}} {windEnabled && windWTG > 0 && Wind WTG{windWTG.toFixed(2)}} - {allLand > 0 && Land Cost{allLand.toFixed(2)}} - {bessEnabled && bessAll > 0 && Storage (BESS){bessAll.toFixed(2)}} - {epcAll > 0 && EPC Overheads{epcAll.toFixed(2)}} - {contAll > 0 && Contingency{contAll.toFixed(2)}} + {solarLandCost > 0 && Solar Land Cost{solarLandCost.toFixed(2)}} + {windEnabled && windLandCost > 0 && Wind Land Cost{windLandCost.toFixed(2)}} + {bessEnabled && bessAll > 0 && Storage{bessAll.toFixed(2)}} + {dsraCost > 0 && DSRA{dsraCost.toFixed(2)}} + {solarEPC > 0 && Solar EPC Overheads{solarEPC.toFixed(2)}} + {windEnabled && windEPC > 0 && Wind EPC Overheads{windEPC.toFixed(2)}} + {epcMarginWTG > 0 && EPC Margin - WTG Tower+BOP{epcMarginWTG.toFixed(2)}} + {epcMarginSolarBOS > 0 && EPC Margin - Solar BOS{epcMarginSolarBOS.toFixed(2)}} + {solarCont > 0 && Contingency - Solar{solarCont.toFixed(2)}} + {windEnabled && windCont > 0 && Contingency - Wind (in WTG cost){windCont.toFixed(2)}} + + Total Hard Cost + {totalHardCost.toFixed(2)} + + + Upfront Financing Fee{upfrontCost.toFixed(2)} + + + Interest During Construction{idcCost.toFixed(2)} + + + Bank Guarantee Cost{bankGuaranteeCost.toFixed(2)} + Total Project Cost - {(totalCostCr + idcCost + upfrontCost).toFixed(2)} + {(totalHardCost + upfrontCost + idcCost + bankGuaranteeCost).toFixed(2)}