Remodel/packages/web/lib/api.ts
Mannu e6dc39aa33 [S1-T12/T13] P&L revenue breakdown + collapsible rows + UI polish
- Engine: Add ppa_revenue_cr, mcp_revenue_cr, tariff, units to PnLRow
- Engine: Split PPA vs MCP revenue in P&L computation
- Web: Collapsible rows for PPA/MCP Revenue and Opex
- Web: Highlighted rows (Total Revenue, EBITDA, EBIT, PBT, PAT)
- Web: Units above Tariff in breakdown, bg-blue-50 highlight
- Fix sticky column z-index for horizontal scroll
- CLAUDE.md: Add project documentation

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 10:42:36 +05:30

356 lines
8.8 KiB
TypeScript

const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface Scenario {
id: string;
name: string;
status: string;
kpis_json: string | null;
created_at: string;
runtime_s?: number | null;
}
export interface ScenarioDetail extends Scenario {
inputs_json: string | null;
statements_json: string | null;
debt_schedule_json: string | null;
error_message: string | null;
}
export interface KpiSummary {
solved_tariff_inr_per_kwh?: number | null;
equity_irr?: number | null;
project_irr?: number | null;
min_dscr?: number | null;
avg_dscr?: number | null;
total_capex_cr?: number | null;
idc_cr?: number | null;
debt_cr?: number | null;
solar_y1_cuf?: number | null;
wind_y1_plf?: number | null;
lcoe_inr_per_kwh?: number | null;
payback_years?: number | null;
rtc_cuf_achieved?: number | null;
total_shortfall_mwh?: number | null;
total_curtailed_mwh?: number | null;
total_mcp_revenue_cr?: number | null;
}
export interface PnLRow {
year: number;
revenue_cr: number;
ppa_revenue_cr: number;
mcp_revenue_cr: number;
ppa_tariff_inr_per_kwh: number;
ppa_units_mwh: number;
mcp_units_mwh: number;
opex_total_cr: number;
om_cr: number;
insurance_cr: number;
land_lease_cr: number;
am_fee_cr: number;
misc_opex_cr: number;
ebitda_cr: number;
depreciation_book_cr: number;
ebit_cr: number;
interest_cr: number;
pbt_cr: number;
tax_cr: number;
pat_cr: number;
}
export interface CfsRow {
year: number;
pat_cr: number;
depreciation_cr: number;
delta_working_capital_cr: number;
cfo_cr: number;
capex_cr: number;
cfi_cr: number;
debt_drawdown_cr: number;
debt_repayment_cr: number;
equity_injection_cr: number;
cff_cr: number;
net_cash_flow_cr: number;
opening_cash_cr: number;
closing_cash_cr: number;
}
export interface BsRow {
year: number;
gross_block_cr: number;
accumulated_depr_cr: number;
net_block_cr: number;
cash_cr: number;
receivables_cr: number;
total_assets_cr: number;
equity_cr: number;
reserves_cr: number;
long_term_debt_cr: number;
payables_cr: number;
total_liabilities_cr: number;
}
export interface DebtYearRow {
year: number;
opening_balance_cr: number;
interest_cr: number;
principal_cr: number;
total_debt_service_cr: number;
closing_balance_cr: number;
dscr: number;
}
export interface GenerationRow {
year: number;
solar_mwh: number;
wind_mwh: number;
gross_mwh: number;
aux_loss_mwh: number;
tx_loss_mwh: number;
dsm_loss_mwh: number;
net_billable_mwh: number;
solar_cuf_pct: number | null;
wind_plf_pct: number | null;
revenue_cr: number;
}
export interface IdcMonthRow {
month: number;
equity_draw_cr: number;
debt_draw_cr: number;
idc_accrual_cr: number;
cum_equity_cr: number;
cum_debt_cr: number;
cum_idc_cr: number;
cum_tpc_cr: number;
}
export interface IdcPhasing {
construction_months: number;
base_capex_cr: number;
idc_cr: number;
total_capex_cr: number;
debt_cr: number;
equity_cr: number;
monthly: IdcMonthRow[];
}
export interface Statements {
pnl: PnLRow[];
cfs: CfsRow[];
bs: BsRow[];
generation?: GenerationRow[];
idc_phasing?: IdcPhasing;
}
export type CostBasis =
| "PER_WP_DC"
| "PER_MWP_DC"
| "PER_MW_AC"
| "PER_MW_WIND"
| "PER_MWH_BESS"
| "PER_ACRE"
| "PCT_OF_HARDCOST"
| "ABS_INR_CR";
export type DeprClass =
| "Plant"
| "BESS"
| "Building"
| "Land_NoDepr"
| "LandLease_Amortized"
| "Intangible"
| "Capitalized_NoDepr"
| "Expensed";
export type CostAttribution = "SolarOnly" | "WindOnly" | "BESSOnly" | "Common";
export interface CostItem {
id: string;
name: string;
category: "HardCost" | "SoftCost" | "EPCOverhead" | "EPCMargin" | "FinancingCost" | "Contingency";
basis: CostBasis;
value: number;
depr_class: DeprClass;
tax_pct?: number; // GST/tax rate, default 5% for modules
attribution: CostAttribution;
phasing_id?: string;
escalation_pct?: number;
}
export interface ScenarioInputPayload {
project?: {
name?: string;
state?: string | null;
capacity_solar_mwp?: number;
capacity_wind_mw?: number;
capacity_bess_mwh?: number;
capacity_bess_mw?: number;
land_acres?: number;
cod_year?: number;
cod_date?: string | null;
solar_cod_date?: string | null;
wind_cod_date?: string | null;
bess_cod_date?: string | null;
};
solar?: {
location_id: string;
capacity_dc_mwp: number;
capacity_ac_mw: number;
dc_ac_ratio?: number;
availability_fraction?: number;
dc_loss_fraction?: number;
soiling_fraction?: number;
degradation_y1?: number;
degradation_annual?: number;
stabilization_days?: number;
stabilization_energy_loss_frac?: number;
stabilization_dsm_addon_pct?: number;
} | null;
wind?: {
location_id: string;
capacity_mw: number;
hub_height_m?: number;
availability_fraction?: number;
wake_loss_fraction?: number;
stabilization_days?: number;
stabilization_energy_loss_frac?: number;
stabilization_dsm_addon_pct?: number;
} | null;
bess?: {
capacity_mwh: number;
power_mw: number;
rte?: number;
dod?: number;
} | null;
rtc?: {
rtc_mw?: number;
mcp_enabled?: boolean;
initial_soc_frac?: number;
} | null;
commercial?: {
tariff_inr_per_kwh?: number;
aux_consumption_pct?: number;
transmission_loss_pct?: number;
dsm_loss_pct?: number;
bad_debt_pct?: number;
receivable_days?: number;
payable_days?: number;
};
opex?: {
om_solar_cr_per_mw?: number;
om_wind_cr_per_mw?: number;
om_bess_cr_per_mwh?: number;
insurance_pct_of_capex?: number;
land_lease_cr?: number;
om_escalation_pct?: number;
om_solar_escalation_pct?: number;
om_solar_escalation_after_year?: number;
om_wind_escalation_pct?: number;
om_wind_escalation_after_year?: number;
om_bess_pct_of_capex?: number | null;
am_fee_pct_of_revenue?: number;
misc_cr?: number;
};
capex?: {
cost_items?: CostItem[];
debt_fraction?: number;
interest_rate_annual?: number;
construction_months?: number;
upfront_fee_pct?: number;
};
debt?: {
interest_rate_annual?: number;
tenor_years?: number;
moratorium_years?: number;
de_ratio?: number;
min_dscr?: number;
avg_dscr?: number;
schedule_shape?: string;
};
tax?: {
rate?: number;
wdv_plant_rate?: number;
wdv_bess_rate?: number;
wdv_building_rate?: number;
wdv_intangible_rate?: number;
};
solver?: {
mode: "solve_tariff" | "fixed_tariff";
target_equity_irr?: number;
fixed_tariff?: number | null;
};
}
export interface ProgressEvent {
stage: string;
pct: number;
}
// ---------------------------------------------------------------------------
// API helpers
// ---------------------------------------------------------------------------
async function apiFetch<T>(url: string, options?: RequestInit): Promise<T> {
const res = await fetch(`${API_BASE}${url}`, options);
if (!res.ok) throw new Error(`API error ${res.status}: ${await res.text()}`);
return res.json() as Promise<T>;
}
// ---------------------------------------------------------------------------
// Scenario functions
// ---------------------------------------------------------------------------
export async function createScenario(
name: string,
inputs?: ScenarioInputPayload,
): Promise<Scenario> {
return apiFetch<Scenario>("/api/scenarios", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, inputs }),
});
}
export async function getScenario(id: string): Promise<ScenarioDetail> {
return apiFetch<ScenarioDetail>(`/api/scenarios/${id}`);
}
export async function listScenarios(): Promise<Scenario[]> {
return apiFetch<Scenario[]>("/api/scenarios");
}
export async function getKpis(id: string): Promise<KpiSummary> {
return apiFetch<KpiSummary>(`/api/scenarios/${id}/kpis`);
}
export async function getStatements(id: string): Promise<Statements> {
return apiFetch<Statements>(`/api/scenarios/${id}/statements`);
}
export async function archiveScenario(id: string): Promise<void> {
await apiFetch(`/api/scenarios/${id}`, { method: "DELETE" });
}
export async function updateScenarioInputs(
id: string,
inputs: ScenarioInputPayload,
): Promise<Scenario> {
return apiFetch<Scenario>(`/api/scenarios/${id}/inputs`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ inputs }),
});
}
export function scenarioEventsUrl(id: string): string {
return `${API_BASE}/api/scenarios/${id}/events`;
}
export function scenarioExcelUrl(id: string): string {
return `${API_BASE}/api/scenarios/${id}/export/excel`;
}