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

7.9 KiB

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

# 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):

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):

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)

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:

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:

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