Remodel/CODEBASE_INVESTIGATION.md
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

534 lines
No EOL
20 KiB
Markdown
Raw Permalink 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.

# REmodel Codebase Investigation Report
**Date:** 2026-05-07
**Investigator:** Claude (Agentic exploration)
---
## 1. Executive Summary
REmodel is a **full-stack hybrid renewable energy (Solar + Wind + BESS) project finance modeling platform** built in Python with a FastAPI backend and Next.js frontend. The project is designed to replace an Excel-macro workflow used for bid preparation at ReNew Power in India, targeting computation of optimal flat tariff and full 25-year project financials in under 30 seconds per scenario.
### Key Architecture
```
┌─────────────────────────────────────────────────────────────────────────┐
│ packages/engine │
│ Python calculation engine (pip-installable) │
│ • Generation (solar, wind, BESS) │
│ • Capex + IDC calculation │
│ • Financial model (P&L, CFS, BS) │
│ • Debt sizing + scheduling │
│ • Tariff solver (brentq) │
│ • CLI driver (Typer) │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ packages/api │
│ FastAPI + Arq async workers (Redis-backed) │
│ • REST endpoints (scenarios, templates) │
│ • Background task processing │
│ • SQLite + Parquet storage │
│ • SSE for real-time progress │
│ • Excel export │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ packages/web │
│ Next.js 14 App Router + shadcn/ui + Tailwind │
│ • Scenario list + wizard │
│ • Results dashboard │
│ • KPI visualizations (Recharts) │
│ • Compare scenarios view │
│ • DataGrid (AG Grid) │
└─────────────────────────────────────────────┘
```
---
## 2. Codebase Structure
### 2.1 Root Directory
```
/Users/manohar_air/MyProjects/REModel/
├── PROJECT.md # Master specification document
├── README.md # Quick reference
├── docker-compose.yml # Redis service
├── Makefile # Common commands
├── .pre-commit-config.yaml
├── .github/workflows/ # CI/CD
├── packages/ # Monorepo structure
│ ├── engine/ # Python calculation engine
│ ├── api/ # FastAPI backend
│ └── web/ # Next.js frontend
└── sprints/ # Sprint documentation (SPRINT_00-08)
```
---
## 3. Packages/Engine Analysis
### 3.1 Module Structure (42 Python files)
```
packages/engine/src/remodel_engine/
├── __init__.py
├── cli.py # Typer CLI (simulate-gen, compute-idc, solve-tariff)
├── schemas/ # Pydantic models (single source of truth)
│ ├── __init__.py
│ ├── scenario.py # ScenarioInput, ScenarioResult, KpiSummary
│ ├── capex.py # CostItem, CapexConfig, PhasingCurve, DrawdownCurve
│ ├── debt.py # DebtConfig, DebtYearRow, IRRMetrics
│ ├── financial.py # CommercialConfig, OpexConfig, TaxConfig, Financials
│ └── generation.py # SolarConfig, WindConfig, BessConfig
├── catalog/ # Defaults and profile loaders
│ ├── __init__.py
│ ├── defaults.py # Constants: PROJECT_LIFE=25, HOURS=8760
│ ├── loader.py # Solar/wind profile CSV loader (RJ, KA, GJ)
│ └── profiles/ # Bundled 8760-hour CSV files
├── generation/ # 25-year generation simulation
│ ├── __init__.py
│ ├── solar.py # DC→AC→clipping→availability→soiling→degradation
│ ├── wind.py # Power curve lookup→shear→wake→availability
│ └── bess_state.py # Degradation + augmentation capacity model
├── dispatch/ # Hybrid RTC dispatch
│ ├── __init__.py
│ ├── hybrid_rtc.py # Per-hour charge/discharge logic
│ └── mcp_settlement.py # Merchant sale at MCP
├── capex/ # Capital expenditure
│ ├── __init__.py
│ ├── cost_items.py # CostItem catalog → total capex
│ ├── phasing.py # Construction phasing matrix
│ └── idc.py # Interest During Construction (fixed-point)
├── commercial/ # Revenue and DSM
│ ├── __init__.py
│ └── ppa.py # Generation aggregation, receivables/payables
├── financial/ # 3-statement model
│ ├── __init__.py
│ ├── pnl.py # Revenue → OpEx → EBITDA → Depr → EBIT → Tax → PAT
│ ├── cfs.py # Cash Flow Statement (CFO/CFI/CFF)
│ ├── bs.py # Balance Sheet (reconciliation)
│ ├── depreciation.py # Book SLM + tax WDV schedules
│ ├── tax.py # 115BAA (India) tax computation
│ └── working_capital.py # Receivables, payables, inventory
├── debt/ # Debt financing
│ ├── __init__.py
│ ├── sizing.py # Fixed-point debt sizing (D:E, DSCR constraints)
│ └── schedule.py # Repayment shapes (equal principal, EMI, sculpted, balloon)
├── irr/ # Financial metrics
│ └── metrics.py # IRR, NPV, LCOE, DSCR, LLCR, PLCR, payback
├── solver/ # Solver logic
│ └── tariff.py # Brentq solver for tariff→IRR target
├── scenarios/ # Scenario orchestration
│ ├── __init__.py
│ ├── runner.py # Full pipeline (generation→financial→debt→IRR)
│ └── sweep.py # Sensitivity analysis
└── io/ # Data export
└── excel_export.py # .xlsx workbook generation
```
### 3.2 Key Schema Definitions
#### ScenarioInput (Top-level input)
```python
class ScenarioInput(BaseModel):
project: ProjectInfo # name, state, capacities, COD
solar: SolarConfig | None # location, DC/AC, losses, degradation
wind: WindConfig | None # location, MW, hub-height
bess: BessConfig | None # MWh, power, RTE, DoD
rtc: RtcConfig | None # contracted RTC MW, MCP toggle
commercial: CommercialConfig # tariff, losses, working capital
capex: CapexConfig # cost items, phasing curves, drawdowns
opex: OpexConfig # O&M, insurance, land lease
debt: DebtConfig # rate, tenor, DSCR constraints
tax: TaxConfig # 115BAA rates
solver: SolverConfig # solve_tariff or fixed_tariff mode
```
#### ScenarioResult (Full output)
```python
class ScenarioResult(BaseModel):
inputs: ScenarioInput
status: Literal["queued","running","success","failed"]
solved_tariff: float | None
kpis: KpiSummary # equity_irr, project_irr, DSCR, capex, LCOE, etc.
financials: Financials # pnl_25y, cfs_25y, bs_25y
debt_schedule: list[DebtYearRow]
irr_metrics: IRRMetrics
warnings: list[str]
runtime_s: float
generation_by_year: list[dict]
idc_phasing: dict
```
### 3.3 Financial Model Flow
The 3-statement model computes:
1. **P&L (Profit & Loss)**
- Revenue: generation_MWh × tariff × (1 - losses) / 10^7 → Cr
- OpEx: O&M + insurance + land_lease + AM_fee + misc
- EBITDA = Revenue - OpEx
- Depreciation: Book SLM by asset class
- EBIT = EBITDA - Depreciation
- Interest: From debt schedule
- PBT = EBIT - Interest
- Tax: 115BAA (25.17%) current + deferred
- PAT = PBT - Tax
2. **CFS (Cash Flow Statement)**
- CFO = PAT + Depreciation - ΔWC
- CFI = 0 (capex in year 0)
- CFF = Debt drawdown - Debt repayment + Equity injection
- Net = CFO + CFI + CFF
3. **BS (Balance Sheet)**
- Assets = Net block + Cash + Receivables
- Liabilities = Equity + Reserves + LT Debt + Payables + DTL
- Reconciliation enforced (≤₹0.05 Cr difference)
### 3.4 Debt Sizing Algorithm
The debt sizing module (`debt/sizing.py`) implements a fixed-point iteration that satisfies three constraints:
1. **D:E Ratio Cap:** `debt ≤ total_capex × de_ratio / (1 + de_ratio)`
2. **Min DSCR Constraint:** `debt ≤ max satisfying min(DSCR) ≥ min_dscr`
3. **Avg DSCR Constraint:** `debt ≤ max satisfying avg(DSCR) ≥ avg_dscr`
The binding constraint determines the final debt amount.
### 3.5 Repayment Schedule Shapes
Five debt schedule shapes are supported (in `debt/schedule.py`):
1. **equal_principal:** Fixed principal each year in repayment period
2. **equal_installment:** EMI (level annuity)
3. **dscr_sculpted:** Principal sculpted so DSCR = avg_dscr each year
4. **balloon:** Interest-only then principal at end
5. **custom_pct_vector:** Caller supplies repayment % per year
### 3.6 Tariff Solver
The solver (`solver/tariff.py`) uses Brent's method (scipy.optimize.brentq) to find the tariff that achieves target equity IRR:
- Bracket: [2.0, 8.0] INR/kWh
- Convergence tolerance: 1e-4
- Max iterations: 50
### 3.7 Generation Models
**Solar (`generation/solar.py`):**
- Input: irradiance × capacity_dc → DC power → DC losses
- Inverter efficiency → AC pre-clip
- Clipping at MW_AC (DC/AC ratio > 1)
- Availability × soiling × degradation
- Output: 25 years × 8760 hours = 219,000 rows
Model chain:
```
irradiance → DC power → DC losses → inverter → AC losses
→ clipping at MW_AC → availability → soiling → degradation
```
**Wind (`generation/wind.py`):**
- Wind speed at reference → hub-height correction (power law shear)
- Power curve lookup → normalized power (0-1)
- Wake losses × electrical losses × availability × degradation
**BESS (`generation/bess_state.py`):**
- Degradation: `SOH = max(eol_soh, 1 - cum_cycles/design_cycles × (1 - eol_soh))`
- Usable MWh = (nameplate + augmentation) × SOH
- Augmentation steps add capacity at specified years
### 3.8 Hybrid RTC Dispatch
The dispatch module (`dispatch/hybrid_rtc.py`) implements hourly charge/discharge:
```
For each hour h:
gen = solar[h] + wind[h]
surplus = gen - target (positive = excess, negative = deficit)
If surplus ≥ 0:
- Charge BESS with min(surplus, bess_mw, headroom)
- Curtail excess
Else:
- Discharge BESS with min(deficit, available)
- Shortfall = deficit - discharge
```
### 3.9 CLI Commands
```bash
# Simulate 25-year solar + wind generation
remodel simulate-gen --input scenario.json --output gen.parquet
# Compute IDC (Interest During Construction)
remodel compute-idc --input capex.json --output idc.json
# Run full scenario pipeline
remodel solve-tariff --input scenario.json --output result.json
```
---
## 4. Packages/API Analysis
### 4.1 Module Structure
```
packages/api/src/remodel_api/
├── __init__.py
├── main.py # FastAPI app, CORS, lifespan
├── config.py # Settings (environment config)
├── db/
│ ├── __init__.py
│ ├── session.py # SQLAlchemy async session
│ └── models.py # Scenario ORM model
├── routers/
│ ├── __init__.py
│ ├── scenarios.py # CRUD + run + results + SSE + Excel export
│ └── templates.py # Scenario templates
└── workers/
├── __init__.py
├── main.py # Arq worker setup
└── tasks.py # Background scenario execution
```
### 4.2 API Endpoints
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/scenarios` | Create scenario, queue for execution |
| GET | `/api/scenarios` | List scenarios |
| GET | `/api/scenarios/{id}` | Get scenario details |
| PATCH | `/api/scenarios/{id}/inputs` | Update inputs, re-queue |
| DELETE | `/api/scenarios/{id}` | Archive scenario |
| GET | `/api/scenarios/{id}/kpis` | Get KPI results |
| GET | `/api/scenarios/{id}/statements` | Get P&L, CFS, BS |
| GET | `/api/scenarios/{id}/export/excel` | Export .xlsx |
| GET | `/api/scenarios/{id}/events` | SSE progress events |
### 4.3 Database Schema
```python
class Scenario(Base):
id: str (UUID, primary key)
name: str
status: str ("queued", "running", "success", "failed")
inputs_json: Text (JSON string of ScenarioInput)
kpis_json: Text (JSON string of KpiSummary)
statements_json: Text (JSON string of pnl/cfs/bs)
debt_schedule_json: Text
timeseries_path: str
error_message: str
runtime_s: float
created_at: datetime
archived_at: datetime | None
```
### 4.4 Background Processing
- Uses **Arq** (async Redis-backed task queue)
- **ThreadPoolExecutor** runs CPU-bound engine in separate thread
- Progress published via Redis pub/sub
- SSE endpoint polls progress
---
## 5. Packages/WEB Analysis
### 5.1 Module Structure
```
packages/web/
├── app/
│ ├── page.tsx # Scenario list + wizard
│ ├── layout.tsx # Root layout
│ ├── providers.tsx # React Query, etc.
│ ├── globals.css # Tailwind
│ ├── scenarios/
│ │ └── [id]/
│ │ └── page.tsx # Scenario detail view
│ ├── compare/
│ │ └── page.tsx # Scenario comparison
│ └── api-types/ # Auto-generated OpenAPI types
├── components/
│ ├── ui/ # shadcn/ui components
│ ├── DataGrid/ # AG Grid wrapper
│ ├── ScenarioWizard/ # Input wizard form
│ ├── Charts/ # Recharts visualizations
│ └── KpiCard/ # KPI display card
└── lib/
└── api.ts # TypeScript API client
```
### 5.2 Frontend Tech Stack
- **Next.js 14** App Router
- **TypeScript** (strict mode)
- **shadcn/ui** + Tailwind CSS
- **TanStack Query** (React Query)
- **AG Grid Community** (DataGrid)
- **Recharts** (KPIs)
- **Zustand** (lightweight state)
---
## 6. Domain Context (Indian RE Bidding)
### 6.1 Key Concepts
- **PPA:** Power Purchase Agreement at tariff (₹/kWh)
- **RTC CUF:** Round-the-clock capacity factor developer commits to
- **DSM:** Deviation Settlement Mechanism penalties
- **IDC:** Interest During Construction
- **115BAA:** Indian tax regime (22% + cess = 25.17%)
- **DSCR:** Debt Service Coverage Ratio
- **D:E:** Debt-to-Equity ratio (typically 75:25)
### 6.2 Currency
- **Crore (Cr)** = 10 million INR (1 Cr = 1,00,00,000)
- **Lakh** = 100 thousand INR
- Capex quoted in Cr/MW or INR/Wp
---
## 7. Sprint Status
| Sprint | Goal | Status |
|--------|------|--------|
| S0 | Repo setup, CI, API/Web skeleton | Complete |
| S1 | Solar + Wind + BESS generation | Complete |
| S2 | Capex + IDC calculation | In Progress |
| S3 | 3-statement financial model | Pending |
| S4 | Debt sizing + scheduling | Pending |
| S5 | IRR/tariff solver | Pending |
| S6 | Full scenario runner | Pending |
| S7 | Excel parity gate | Pending |
| S8 | Sensitivity sweeps | Pending |
---
## 8. Technical Constraints & Decisions
### 8.1 Working Agreements (from PROJECT.md)
1. Engine is UI-agnostic (pip-installable package)
2. CostItem table model (not flat fields)
3. Three nested iterations: tariff → debt → IDC
4. IDC has independent equity/debt drawdowns
5. Two DSCR compliance knobs
6. Sync-async hybrid: all runs through Arq queue
7. SQLite v0, Parquet for timeseries
8. **Excel parity is sacred** — must match user Excel within 0.1%
9. Coverage ≥85%
10. mypy strict mode
11. One DataGrid component (shared across 5+ places)
### 8.2 Testing
- Unit tests for every public function
- Integration tests for modules
- **Parity gate** — tests against user's Excel gold scenarios
- Location: `packages/engine/tests/`
- Fixtures: `packages/engine/tests/fixtures/`
---
## 9. Observations & Potential Issues
### 9.1 Strong Points
1. **Clean architecture** — engine/API/web separation well-enforced
2. **Pydantic schemas** — single source of truth for types
3. **Comprehensive financial model** — 3-statement + depreciation + tax
4. **Multiple debt schedule shapes** — flexible repayment
5. **Production-ready** — Ruff, mypy, pytest, coverage
6. **Async backend** — Redis queue for scaling
7. **TypeScript types** — auto-generated from OpenAPI
### 9.2 Potential Concerns
1. **IDC fixed-point** — may need more robust convergence handling
2. **Parity gate** — not yet validated against user Excel
3. **Multi-user** — SQLite v0, needs Postgres for v2
4. **Test coverage gaps** — some modules newly added
5. **Wind power curve** — generic, may need calibration
6. **Solar profiles** — only 3 locations (RJ, KA, GJ)
### 9.3 Missing/Incomplete (from git status)
The git status shows many new untracked files:
- `packages/engine/src/remodel_engine/capex/` (existing, modified)
- New modules for dispatch, commercial, debt, financial, irr, solver, scenarios, io
- `packages/web/app/compare/` (new page)
- Various test updates
---
## 10. Recommendations for Discussion
1. **Validation Priority:** Run the Excel parity gate with user-supplied gold scenario to verify model accuracy before proceeding to next sprints
2. **Test Coverage:** Some newly-added modules (dispatch, commercial, irr) have limited test coverage — prioritize before shipping
3. **DataGrid Usage:** Single AG Grid component exists but needs verification across all 5+ use cases
4. **Database Migration:** SQLite works for v0, but PostgreSQL planning would help architecture decisions
5. **Wind Profile Calibration:** Generic wind power curve may need location-specific calibration for Indian sites
6. **Performance:** 30-second target may be achievable, but needs benchmarking with realistic scenarios
---
## 11. File Locations Reference
### Key Source Files
| Component | Path |
|-----------|------|
| Scenario input schema | `packages/engine/src/remodel_engine/schemas/scenario.py` |
| Generation simulation | `packages/engine/src/remodel_engine/generation/solar.py` |
| Wind simulation | `packages/engine/src/remodel_engine/generation/wind.py` |
| BESS model | `packages/engine/src/remodel_engine/generation/bess_state.py` |
| RTC dispatch | `packages/engine/src/remodel_engine/dispatch/hybrid_rtc.py` |
| Capex calculation | `packages/engine/src/remodel_engine/capex/cost_items.py` |
| IDC calculation | `packages/engine/src/remodel_engine/capex/idc.py` |
| P&L | `packages/engine/src/remodel_engine/financial/pnl.py` |
| CFS | `packages/engine/src/remodel_engine/financial/cfs.py` |
| Balance Sheet | `packages/engine/src/remodel_engine/financial/bs.py` |
| Depreciation | `packages/engine/src/remodel_engine/financial/depreciation.py` |
| Tax | `packages/engine/src/remodel_engine/financial/tax.py` |
| Debt sizing | `packages/engine/src/remodel_engine/debt/sizing.py` |
| Debt schedule | `packages/engine/src/remodel_engine/debt/schedule.py` |
| IRR metrics | `packages/engine/src/remodel_engine/irr/metrics.py` |
| Tariff solver | `packages/engine/src/remodel_engine/solver/tariff.py` |
| Scenario runner | `packages/engine/src/remodel_engine/scenarios/runner.py` |
| CLI | `packages/engine/src/remodel_engine/cli.py` |
| API main | `packages/api/src/remodel_api/main.py` |
| Scenario endpoints | `packages/api/src/remodel_api/routers/scenarios.py` |
| Background tasks | `packages/api/src/remodel_api/workers/tasks.py` |
| Web API client | `packages/web/lib/api.ts` |
| Main page | `packages/web/app/page.tsx` |
---
*End of investigation report.*