268 lines
No EOL
7.9 KiB
Markdown
268 lines
No EOL
7.9 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
REmodel is a Python calculation engine + FastAPI backend + Next.js frontend for Indian renewable energy (Solar + Wind + BESS) project finance modeling. It computes optimal flat tariff and full 25-year project financials for hybrid RTC RE projects.
|
|
|
|
## Prerequisites
|
|
|
|
- Python ≥ 3.12, Poetry ≥ 2.0
|
|
- Node.js ≥ 20, pnpm ≥ 10
|
|
- Docker (for Redis)
|
|
|
|
## Common Commands
|
|
|
|
```bash
|
|
# Full stack
|
|
make setup # Install all deps (Poetry + pnpm)
|
|
make dev # Start Redis, API, Arq worker, web dev server
|
|
make test # Run pytest + jest
|
|
make lint # ruff + mypy + tsc + eslint
|
|
make clean # Remove build artefacts
|
|
|
|
# Single test (Python)
|
|
cd packages/engine && poetry run pytest tests/unit/test_xxx.py::test_name -v
|
|
|
|
# Individual packages
|
|
cd packages/engine && poetry run mypy src/
|
|
cd packages/api && poetry run uvicorn remodel_api.main:app --reload --port 8000
|
|
cd packages/web && pnpm dev
|
|
```
|
|
|
|
## Architecture
|
|
|
|
```
|
|
packages/web (Next.js App Router)
|
|
│ REST + SSE
|
|
packages/api (FastAPI + Arq + SQLite)
|
|
│ Python import
|
|
packages/engine (Pydantic + NumPy + SciPy)
|
|
```
|
|
|
|
## Key Context
|
|
|
|
- **Domain**: Indian RE bidding — PPA tariff bids, SECI/DISCOM auctions, 15-20% target equity IRR, D:E ≤ 75:25, DSCR ≥ 1.20
|
|
- **Hybrid RTC**: Solar + Wind + BESS (battery firming), 57.64% RTC CUF commitment, DSM penalties
|
|
- **Tax**: Section 115BAA → 22% + cess = 25.17%
|
|
- **Currency**: INR Crore (Cr) = 10 million, Lakh = 100 thousand
|
|
|
|
## Working Agreements
|
|
|
|
- Always read PROJECT.md and active SPRINT_XX.md at session start
|
|
- One task per commit, format: `[S2-T03] Implement IDC fixed-point solver`
|
|
- Excel parity is sacred — debug diffs, don't bump tolerances
|
|
- Type strict (mypy strict), no Any except at JSON boundaries
|
|
- Pydantic for all I/O, no raw dicts crossing module boundaries
|
|
- No magic numbers — defaults in catalog/defaults.py
|
|
- Comments explain WHY, not WHAT
|
|
|
|
## Engine Structure
|
|
|
|
Key modules (read in this order for domain understanding):
|
|
|
|
1. **schemas/** — Pydantic models (single source of truth)
|
|
2. **solver/** — Three nested iterations: tariff (brentq) → debt sizing → IDC
|
|
3. **generation/** — Solar, wind, BESS simulation
|
|
4. **dispatch/** — Hybrid RTC scheduling, MCP settlement
|
|
5. **commercial/** — PPA revenue, DSM, charges, losses
|
|
6. **capex/** — CostItem catalog + IDC calculation
|
|
7. **financial/** — P&L, cash flow, balance sheet
|
|
8. **debt/** — Sizing, sculpting, schedule, DSCR compliance
|
|
9. **irr/** — Equity/project IRR metrics
|
|
|
|
## API Structure
|
|
|
|
- **routers/** — REST endpoints (scenarios, sensitivities, templates)
|
|
- **workers/** — Arq async tasks (run via Redis queue)
|
|
- **db/** — SQLAlchemy models + migrations
|
|
- **main.py** — FastAPI app factory
|
|
|
|
## Deployment (Dokploy)
|
|
|
|
### Docker Configuration
|
|
|
|
**API Dockerfile** (`packages/api/Dockerfile`):
|
|
```dockerfile
|
|
FROM python:3.12-slim
|
|
|
|
WORKDIR /app
|
|
|
|
RUN pip install poetry
|
|
|
|
COPY packages /app/packages
|
|
|
|
WORKDIR /app/packages/api
|
|
RUN poetry install --no-interaction
|
|
|
|
ENV VENV_PATH=/root/.cache/pypoetry/virtualenvs/remodel-api-cufy8KWC-py3.12/bin
|
|
ENV PATH=$VENV_PATH:$PATH
|
|
ENV PYTHONPATH=/app/packages/engine/src:/app/packages/api/src
|
|
|
|
WORKDIR /app/packages/api
|
|
|
|
EXPOSE 8000
|
|
|
|
CMD ["uvicorn", "remodel_api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
```
|
|
|
|
**Web Dockerfile** (`packages/web/Dockerfile`):
|
|
```dockerfile
|
|
FROM node:22-alpine AS builder
|
|
|
|
WORKDIR /app
|
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
|
|
COPY package.json pnpm-lock.yaml ./
|
|
COPY . .
|
|
RUN test -f .env.local || echo "# placeholder" > .env.local
|
|
RUN pnpm install --frozen-lockfile --ignore-scripts
|
|
RUN pnpm build
|
|
|
|
FROM node:22-alpine
|
|
WORKDIR /app
|
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
|
|
|
COPY --from=builder /app/.next ./.next
|
|
COPY --from=builder /app/public ./public
|
|
COPY --from=builder /app/package.json .
|
|
COPY --from=builder /app/node_modules ./node_modules
|
|
COPY --from=builder /app/.env.local ./.env.local
|
|
|
|
ENV NODE_ENV=production
|
|
ENV NEXT_TELEMETRY_DISABLED=1
|
|
|
|
EXPOSE 3000
|
|
|
|
CMD ["/app/node_modules/.bin/next", "start"]
|
|
```
|
|
|
|
### docker-compose.yml (Dokploy)
|
|
|
|
```yaml
|
|
services:
|
|
redis:
|
|
image: redis:7-alpine
|
|
restart: unless-stopped
|
|
volumes:
|
|
- redis_data:/data
|
|
networks:
|
|
- internal
|
|
|
|
api:
|
|
build:
|
|
context: .
|
|
dockerfile: packages/api/Dockerfile
|
|
restart: unless-stopped
|
|
environment:
|
|
- DATABASE_URL=sqlite+aiosqlite:///./remodel.db
|
|
- REDIS_URL=redis://redis:6379
|
|
depends_on:
|
|
- redis
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=web"
|
|
- "traefik.http.routers.api.rule=Host(`model.manohargupta.com`) && PathPrefix(`/api`)"
|
|
- "traefik.http.routers.api.entrypoints=websecure"
|
|
- "traefik.http.routers.api.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.api.loadbalancer.server.port=8000"
|
|
networks:
|
|
- internal
|
|
- web
|
|
|
|
worker:
|
|
build:
|
|
context: .
|
|
dockerfile: packages/api/Dockerfile
|
|
command: python -m arq remodel_api.workers.main.WorkerSettings
|
|
restart: unless-stopped
|
|
environment:
|
|
- DATABASE_URL=sqlite+aiosqlite:///./remodel.db
|
|
- REDIS_URL=redis://redis:6379
|
|
depends_on:
|
|
- redis
|
|
networks:
|
|
- internal
|
|
|
|
web:
|
|
build:
|
|
context: ./packages/web
|
|
dockerfile: Dockerfile
|
|
restart: unless-stopped
|
|
environment:
|
|
- NEXT_PUBLIC_API_URL=https://model.manohargupta.com/api
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=web"
|
|
- "traefik.http.routers.web.rule=Host(`model.manohargupta.com`)"
|
|
- "traefik.http.routers.web.entrypoints=websecure"
|
|
- "traefik.http.routers.web.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.web.loadbalancer.server.port=3000"
|
|
networks:
|
|
- web
|
|
|
|
networks:
|
|
internal:
|
|
internal: true
|
|
web:
|
|
name: dokploy-network
|
|
external: true
|
|
|
|
volumes:
|
|
redis_data:
|
|
```
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Description | Required |
|
|
|----------|-------------|----------|
|
|
| `DATABASE_URL` | SQLite with async driver | `sqlite+aiosqlite:///./remodel.db` |
|
|
| `REDIS_URL` | Redis connection string | `redis://redis:6379` |
|
|
| `NEXT_PUBLIC_API_URL` | Public API URL for web | `https://model.manohargupta.com/api` |
|
|
|
|
### CORS Configuration
|
|
|
|
In `packages/api/src/remodel_api/main.py`:
|
|
```python
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["http://localhost:3000", "https://model.manohargupta.com"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
```
|
|
|
|
### Config Settings
|
|
|
|
In `packages/api/src/remodel_api/config.py`:
|
|
```python
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(env_prefix="", extra="ignore")
|
|
database_url: str = "sqlite+aiosqlite:///./remodel.db"
|
|
redis_url: str = "redis://localhost:6379"
|
|
```
|
|
|
|
**Important**: No prefix for env vars - they must match docker-compose exactly.
|
|
|
|
### Network Requirements
|
|
|
|
- `dokploy-network` must exist on Dokploy server (external network)
|
|
- Traefik uses `websecure` endpoint (HTTPS with Let's Encrypt TLS)
|
|
|
|
## Deployment Issues & Fixes
|
|
|
|
| Issue | Cause | Fix |
|
|
|-------|-------|-----|
|
|
| Poetry pyproject.toml not found | COPY syntax wrong in Dockerfile | Copy entire `packages/` directory |
|
|
| `--no-venv-seeding` flag error | Old Poetry version | Remove the flag |
|
|
| Missing `.env.local` | Not in git | Create placeholder in builder stage |
|
|
| `uvicorn` not in PATH | Poetry venv not in PATH | Set `ENV PATH=$VENV_PATH:$PATH` |
|
|
| `next` not found | pnpm stores bins differently | Use absolute path `/app/node_modules/.bin/next` |
|
|
| 504 Gateway Timeout | Redis/DB not accessible | Check network, restart containers |
|
|
| Worker can't connect to Redis | Wrong env prefix | Use `REDIS_URL` not `REMODEL_REDIS_URL` |
|
|
| SQLite async error | Wrong driver | Use `sqlite+aiosqlite://` not `sqlite://` |
|
|
| CORS blocked | Origin mismatch | Add production domain to allow_origins | |