Remodel/packages/web/lib/api.ts
Mannu 5e49926289
Some checks are pending
CI / Engine — lint / typecheck / test (push) Waiting to run
CI / API — lint / typecheck / test (push) Waiting to run
CI / Web — typecheck / lint / build (push) Waiting to run
feat: add hourly timeseries infrastructure with Total RE, Client End, Load columns
- Add client_load_mw to CommercialConfig (defaults to ppa_capacity_mw)
- Add hourly_timestamps, hourly_fy, hourly_proj_year, hourly_total_re,
  hourly_client_end, hourly_load to ScenarioResult
- Generate 25-year hourly data in runner.py with proper timestamps
- Update API worker to return new hourly fields
- Update WorkbookView HourlyGenerationSheet to show all 5 columns
  at each expandable level (Year > Month > Day > Hour)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-16 12:52:44 +05:30

366 lines
9.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
// Hourly generation: 25 years × 8760 hours = 219,000 values per technology
solar_hourly?: number[];
wind_hourly?: number[];
// Hourly timeseries
hourly_timestamps?: string[];
hourly_fy?: string[];
hourly_proj_year?: number[];
hourly_total_re?: number[];
hourly_client_end?: number[];
hourly_load?: number[];
}
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`;
}