Remodel/CLAUDE.md
Mannu b184d74ad4
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
Update CLAUDE.md with Dokploy deployment docs and fixes
2026-05-15 09:45:37 +05:30

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 |