Skip to content

Latest commit

 

History

History
133 lines (96 loc) · 5.42 KB

File metadata and controls

133 lines (96 loc) · 5.42 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project

DealLens — real estate underwriting platform. 120-month DCF engine calibrated against industry-standard Excel proformas. Two validated reference deals: Maple Avenue (10.99% IRR) and Riverside Crossing (12.04% IRR).

Commands

Backend (backend/)

# Dev server (port 8001 — do NOT use 8000, reserved for other services)
uvicorn app.main:app --reload --port 8001

# All tests (25 unit pass, 8 integration need live Supabase JWT)
python3 -m pytest tests/ -v

# Unit tests only (skip integration)
python3 -m pytest tests/ -v --ignore=tests/test_integration.py

# Single test file
python3 -m pytest tests/test_validation_regression.py -v

# Single test
python3 -m pytest tests/test_validation_regression.py::test_riverside_regression -v

# Docker
docker build -t deallens-backend . && docker run -p 8001:8000 deallens-backend

Frontend (frontend/)

npm run dev      # port 3000
npm run build    # Turbopack
npm run lint

Architecture

Engine Pipeline

orchestrator.py is the entry point. build_120_month_cash_flow(ProformaModel) calls services in this order:

  1. budget.py — land + soft + hard + financing cost aggregation → total_budget
  2. calculations.pycalculate_egi() and calculate_opex() from rent roll → stabilized NOI
  3. loans.pycalculate_construction_loan(), calculate_permanent_loan(), calculate_mezzanine_loan() — constraint-based sizing (LTV/LTC/DSCR/DebtYield), lender_quoted_amount overrides sizing
  4. 120-month loop: monthly EGI/OpEx with growth + vacancy ramp, S-curve construction draws, construction interest accrual, perm loan funding at stabilization (pays off construction + mezz), IO→amortizing perm debt service, sale proceeds at exit
  5. GP/LP waterfall — 2-tier return-of-capital structure using PromoteStructure.hurdles (Tier 1 = first active hurdle splits, Tier 2 = last active hurdle splits, fallback = equity split)
  6. Annual rollup → cash_flow.py for statement + returns.py for XIRR/equity multiple

API Layer

All routes in app/api/routes/deals.py, prefixed /api/v1/deals. Auth via get_current_user (JWT decode) and get_supabase (RLS-enforced client) dependency injection.

Endpoint Purpose
POST / Create deal
GET / List user's deals (RLS filtered)
GET /{id} Get deal
POST /{id}/calculate Run engine (rate limited: 10/min)
GET /{id}/viability Viability flags only
DELETE /{id} Soft delete (status='deleted')

Frontend

Next.js App Router. (auth)/login for Supabase auth, (app)/ for protected routes. lib/api.ts injects Bearer token from Supabase session. Results page calls /calculate live on every load.

Key Models

app/models/proforma.py contains the full input schema:

  • ProformaModelDashboardInputs (150+ fields) + RentRollInput
  • DashboardInputs contains BudgetInputs, DebtAssumptions (x3: construction, permanent, mezzanine), PhaseTimeline[], PromoteStructurePromoteHurdle[]
  • @model_validator(mode='before') merges legacy assumptions block into dashboard

Testing

  • Regression tests (test_validation_regression.py): assert project_irr for both reference deals. Dual-deal rule — no engine change valid unless both pass.
  • Maple Avenue payload: imported via get_exact_maple_avenue_proforma() from test_payloads.py
  • Riverside Crossing payload: imported as p_data from riverside_payload.py
  • Integration tests (test_integration.py) require live Supabase — expect 401s locally

Environment Variables

Backend .env:

SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_JWT_SECRET

Frontend .env.local:

NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_API_URL=http://localhost:8001

Conventions

  • Python: async def routes, Pydantic v2 models, type hints, dependency injection
  • Conventional commits: feat:, fix:, docs:, refactor:
  • Never commit to main directly — always branch
  • Supabase RLS enforces multi-tenancy; never bypass with service key in app code
  • Soft deletes only — database blocks physical DELETE to protect audit trail
  • python3 not python (macOS)

Token Efficiency & Sub-Agent Routing

Use sub-agents with cheaper models for simple tasks. Opus is expensive — reserve it for reasoning-heavy work.

Use Sonnet sub-agents for:

  • File reads and grep searches
  • Running tests and capturing output
  • Formatting, linting, import sorting
  • Writing or updating documentation and comments
  • Simple find-and-replace edits where the change is obvious
  • Git operations (commit, branch, diff, log)
  • Generating boilerplate code from clear specs
  • CSS/styling tweaks

Keep on Opus for:

  • Engine logic changes (orchestrator, loans, returns, cash_flow)
  • Debugging failing tests where root cause is unclear
  • Architectural decisions and multi-file refactors
  • Anything touching IRR, waterfall, or debt sizing calculations
  • Root cause classification (Bucket 1/2/3 analysis)
  • Planning and verification protocols

General rules:

  • Parallelize independent sub-tasks when possible
  • Batch file reads into single operations instead of sequential calls
  • When running verification (pytest + live API checks), delegate the execution to a sub-agent and review results
  • Never use Opus tokens just to read a file and report its contents