# Technical Architecture: CFR Command Center

**Version:** 1.0
**Date:** 2025-02-21
**Author:** Architect Agent

## 1. Overview

### 1.1 System Context

CFR Command Center is a self-hosted real estate investment operating system for CFR Investments. It runs on a single Linux server (fred) and is accessible exclusively via Tailscale VPN. The system consolidates property research, deal analysis, tenant management, and document organization into a single dashboard.

**Key Characteristics:**
- Single-tenant (Chad + Maranda)
- Self-hosted on existing hardware
- No external authentication (Tailscale is the auth layer)
- Extends existing PocketBase database
- Free data sources (county web scraping, Gemini API)
- Mobile-first responsive design for field use

### 1.2 Architecture Principles

1. **Simplicity First** — Single server, single database, single app. Avoid microservices complexity.
2. **Extend, Don't Replace** — Build on existing PocketBase collections; preserve data continuity.
3. **Graceful Degradation** — External services (county sites, Gemini) may fail; core features must work.
4. **Server-Side Keys** — API keys never leave the server; all Gemini calls via Next.js API routes.
5. **Tailscale-Only Access** — No auth code; bind to Tailscale IP, rely on network-level security.
6. **Mobile-First Responsive** — Chad uses phone in the field; design for 375px first.

---

## 2. Technology Stack

| Layer | Technology | Version | Rationale |
|-------|------------|---------|-----------|
| Runtime | Node.js | 22.x | Existing on fred, LTS stability |
| Framework | Next.js | 14.x (App Router) | React SSR, API routes, file-based routing |
| UI Library | React | 18.x | Component-based UI, Next.js integration |
| Styling | Tailwind CSS | 3.x | Utility-first, rapid development, responsive |
| Component Library | ShadCN UI | Latest | Accessible, customizable, Tailwind-native |
| Database | PocketBase | 0.22+ | Existing instance, embedded SQLite, realtime |
| AI Service | Gemini 3.0 Flash | API | Free tier available, fast responses |
| PDF Generation | React-PDF | 3.x | React components to PDF, no headless browser |
| Process Manager | PM2 | 5.x | Existing on fred, auto-restart, logs |
| Language | TypeScript | 5.x | Type safety, better DX, catch errors early |
| HTTP Client | Ky | 1.x | Lightweight, TypeScript-friendly fetch wrapper |
| Web Scraping | Cheerio + Axios | 1.x + 1.x | jQuery-like parsing, reliable HTTP |
| Date Handling | date-fns | 3.x | Tree-shakeable, immutable date utilities |
| Forms | React Hook Form | 7.x | Performant, minimal re-renders |
| Validation | Zod | 3.x | TypeScript schema validation |

---

## 3. Architecture Decisions

### ADR-001: Next.js App Router over Pages Router

**Status:** Accepted
**Date:** 2025-02-21

**Context:** Next.js 14 supports both App Router (new) and Pages Router (legacy). App Router uses React Server Components and a new file-based routing structure.

**Decision:** Use App Router for all new routes.

**Options Considered:**
| Option | Pros | Cons |
|--------|------|------|
| App Router | Server Components, streaming, better DX, future-proof | Breaking changes from Pages, newer ecosystem |
| Pages Router | Stable, familiar, large ecosystem | Legacy, no Server Components |

**Rationale:** App Router is the future of Next.js. Server Components reduce client-side JavaScript and improve initial load times. For a single-developer project, the DX improvements outweigh the learning curve.

**Consequences:**
- API routes live in `app/api/` directory
- Server Components by default, use `'use client'` directive for interactivity
- `getServerSideProps` replaced with async server components

---

### ADR-002: PocketBase over New Database

**Status:** Accepted
**Date:** 2025-02-21

**Context:** PocketBase already runs on fred with existing collections (properties, tenants, rentHistory, damages, rentArrears, propertyLedger, propertyValues, users).

**Decision:** Extend existing PocketBase instance rather than migrate to a new database.

**Options Considered:**
| Option | Pros | Cons |
|--------|------|------|
| Extend PocketBase | Preserves data, zero migration, already running | SQLite limitations, single-server |
| Migrate to PostgreSQL | Scalability, advanced features | Migration effort, new infrastructure |
| Supabase | Hosted, Postgres, realtime | External dependency, cost at scale |

**Rationale:** PocketBase works. The dataset is small (< 100 properties). SQLite handles this easily. Migration adds risk without benefit.

**Consequences:**
- New collections added via PocketBase admin UI or migrations
- Existing CFR Manager (port 3004) shares same database
- Database backups remain simple (copy SQLite file)

---

### ADR-003: React-PDF over Puppeteer

**Status:** Accepted
**Date:** 2025-02-21

**Context:** Lender packages and comp reports need PDF export. Options include headless browser (Puppeteer), React-to-PDF (React-PDF), or client-side (jsPDF).

**Decision:** Use React-PDF for server-side PDF generation.

**Options Considered:**
| Option | Pros | Cons |
|--------|------|------|
| React-PDF | No browser needed, React components, streaming | Limited CSS support, learning curve |
| Puppeteer | Full CSS support, accurate rendering | Heavy (Chromium), memory intensive |
| jsPDF | Client-side, lightweight | Poor formatting, no server control |

**Rationale:** React-PDF generates PDFs on the server without a headless browser. For lender documents with tables and structured content, it's sufficient. Avoids Puppeteer's memory overhead on a single-server setup.

**Consequences:**
- PDFs generated in Next.js API routes
- Limited to React-PDF's CSS subset (flexbox, basic styling)
- Complex layouts may need workarounds

---

### ADR-004: Web Scraping over Paid APIs

**Status:** Accepted
**Date:** 2025-02-21

**Context:** Property data comes from county appraiser sites. Options include paid APIs (Estated, Attom) or free web scraping.

**Decision:** Implement web scrapers for Polk and Hillsborough county sites. No paid APIs.

**Options Considered:**
| Option | Pros | Cons |
|--------|------|------|
| Web Scraping | Free, full data access | Fragile (site changes), maintenance burden |
| Paid APIs (Estated) | Reliable, structured data | $100-500/month, vendor dependency |
| Hybrid (free + paid fallback) | Best of both | Complexity, still costs money |

**Rationale:** For a single investor's use case, scraping is acceptable. Usage volume is low (10-50 lookups/day). When scrapers break, graceful degradation shows cached data or manual entry.

**Consequences:**
- Scrapers may break when county sites change
- Need monitoring/logging for scraper failures
- Cache property data locally to reduce repeat scraping

---

### ADR-005: Tailscale-Only Access (No Authentication)

**Status:** Accepted
**Date:** 2025-02-21

**Context:** The app is for Chad and Maranda only. Adding authentication adds complexity.

**Decision:** Bind Next.js to Tailscale IP only. No auth code. Tailscale ACLs control access.

**Options Considered:**
| Option | Pros | Cons |
|--------|------|------|
| Tailscale-only | Zero auth code, simple, secure enough | Requires Tailscale on all devices |
| PocketBase Auth | Built-in, user management | Extra code, Chad doesn't need it |
| Next-Auth | Full-featured, multiple providers | Overkill, complexity |

**Rationale:** Tailscale already runs on fred. Chad's phone and laptop have Tailscale. Network-level access is sufficient for a single-user app.

**Consequences:**
- App listens on `100.98.154.42:3007` (Tailscale IP)
- No login page, no sessions, no JWTs
- PocketBase uses service account (auto-login)

---

### ADR-006: Gemini 3.0 Flash for AI Features

**Status:** Accepted
**Date:** 2025-02-21

**Context:** AI features include comp analysis, ARV estimation, and skip trace enhancement.

**Decision:** Use Gemini 3.0 Flash API for all AI features. API key stored server-side only.

**Options Considered:**
| Option | Pros | Cons |
|--------|------|------|
| Gemini 3.0 Flash | Free tier, fast, multimodal | Google dependency |
| OpenAI GPT-4 | Best quality, reliable | Cost, no free tier |
| Local LLM (Ollama) | No API dependency | Hardware requirements, quality |

**Rationale:** Gemini Flash has a generous free tier. Response speed is critical for interactive features. Chad's usage volume fits within free tier limits.

**Consequences:**
- All Gemini calls in Next.js API routes (key never exposed)
- Graceful degradation when API fails
- Rate limiting may be needed if usage spikes

---

### ADR-007: PM2 for Process Management

**Status:** Accepted
**Date:** 2025-02-21

**Context:** The app needs to run continuously with auto-restart on crash.

**Decision:** Use PM2 (already on fred) to manage the Next.js process.

**Options Considered:**
| Option | Pros | Cons |
|--------|------|------|
| PM2 | Already installed, proven, simple | Manual log rotation |
| systemd | Native, integrated | More complex config |
| Docker | Isolation, reproducibility | Overkill for single app |

**Rationale:** PM2 is already managing other services on fred. Keeps infrastructure consistent.

**Consequences:**
- `pm2 start npm --name "cfr-command-center" -- start`
- Logs in `~/.pm2/logs/`
- Auto-restart on crash

---

### ADR-008: TypeScript Strict Mode

**Status:** Accepted
**Date:** 2025-02-21

**Context:** Code quality and maintainability for a long-lived project.

**Decision:** Enable TypeScript strict mode. All new code must be fully typed.

**Options Considered:**
| Option | Pros | Cons |
|--------|------|------|
| Strict Mode | Catch errors early, better DX | More verbose, learning curve |
| Loose Mode | Faster initial development | Runtime errors, poor maintainability |
| JavaScript | No compilation | No type safety |

**Rationale:** Single-developer project benefits from type safety. Reduces bugs and improves refactoring confidence.

**Consequences:**
- `strict: true` in tsconfig.json
- Zod for runtime validation
- Generate types from PocketBase schema

---

## 4. System Architecture

### 4.1 High-Level Diagram

```
┌──────────────────────────────────────────────────────────────────────┐
│                           FRED (Ubuntu Server)                        │
│                                                                       │
│  ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐ │
│  │   Tailscale     │     │   Next.js 14    │     │   PocketBase    │ │
│  │   Network       │────▶│   Port 3007     │────▶│   Port 8090     │ │
│  │   (Auth Layer)  │     │   (App Router)  │     │   (SQLite)      │ │
│  └─────────────────┘     └────────┬────────┘     └─────────────────┘ │
│                                   │                                   │
│                                   ▼                                   │
│                          ┌─────────────────┐                         │
│                          │   API Routes    │                         │
│                          │   /api/*        │                         │
│                          └────────┬────────┘                         │
│                                   │                                   │
│          ┌────────────────────────┼────────────────────────┐         │
│          │                        │                        │         │
│          ▼                        ▼                        ▼         │
│  ┌──────────────┐      ┌──────────────┐      ┌──────────────┐       │
│  │   Gemini     │      │   County     │      │   /mnt/      │       │
│  │   API        │      │   Scrapers   │      │   storage    │       │
│  │   (AI)       │      │   (Data)     │      │   (Files)    │       │
│  └──────────────┘      └──────────────┘      └──────────────┘       │
│                                                                       │
└──────────────────────────────────────────────────────────────────────┘
```

### 4.2 Components

#### 4.2.1 Frontend (Next.js App Router)

| Component | Description |
|-----------|-------------|
| `app/` | File-based routing with React Server Components |
| `app/(dashboard)/` | Dashboard pages (protected by Tailscale) |
| `app/api/` | Backend API routes |
| `components/` | Reusable UI components (ShadCN-based) |
| `lib/` | Utilities, PocketBase client, scrapers |
| `hooks/` | Custom React hooks |
| `types/` | TypeScript type definitions |

#### 4.2.2 Backend (API Routes)

| Route Group | Purpose |
|-------------|---------|
| `/api/properties/*` | Property CRUD, metrics |
| `/api/deals/*` | BRRRR analysis, deal pipeline |
| `/api/comps/*` | Comp search, AI analysis, PDF export |
| `/api/research/*` | County data scraping, title search |
| `/api/skip-trace/*` | Owner contact lookup |
| `/api/tenants/*` | Rent roll, payments, lease tracking |
| `/api/documents/*` | File upload, organization |
| `/api/lender-package/*` | PDF generation |
| `/api/dashboard/*` | Portfolio metrics |

#### 4.2.3 Data Layer (PocketBase)

| Collection | Purpose |
|------------|---------|
| `properties` | Property records (existing) |
| `tenants` | Tenant info (existing) |
| `rentHistory` | Payment history (existing) |
| `damages` | Damage records (existing) |
| `rentArrears` | Arrears tracking (existing) |
| `propertyLedger` | Financial ledger (existing) |
| `propertyValues` | Value history (existing) |
| `deals` | Deal pipeline (new) |
| `compReports` | Saved comp analysis (new) |
| `documents` | Document vault (new) |
| `skipTraces` | Skip trace results (new) |
| `maintenanceRequests` | Maintenance tickets (new) |

#### 4.2.4 External Services

| Service | Purpose | Integration |
|---------|---------|-------------|
| Gemini 3.0 Flash | AI analysis | REST API (server-side) |
| Polk County PA | Property data | Web scraping (Cheerio) |
| Hillsborough County PA | Property data | Web scraping (Cheerio) |
| Polk County Clerk | Title/lien search | Web scraping (Cheerio) |
| FEMA Flood API | Flood zone lookup | REST API |

---

## 5. Data Architecture

### 5.1 Database Schema

#### 5.1.1 Existing Collections (Extended)

**properties** (existing, extend)
```typescript
interface Property {
  id: string;
  address: string;
  city: string;
  state: string;
  zip: string;
  county: string;
  status: 'rented' | 'rehab' | 'vacant' | 'closing';
  purchasePrice: number;
  rehabCost: number;
  arv: number;
  monthlyRent: number;
  currentDebt: number;
  loanType: 'hardMoney' | 'DSCR' | 'HELOC' | 'conventional' | 'FF';
  interestRate: number;
  beds: number;
  baths: number;
  sqft: number;
  yearBuilt: number;
  lotSize: number;
  propertyType: 'siteBuilt' | 'manufactured' | 'mobileHome' | 'doubleWide';

  // New fields for CFR Command Center
  floodZone?: string;
  zoning?: string;
  taxAssessment?: number;
  taxAmount?: number;
  taxStatus?: 'paid' | 'delinquent';
  parcelId?: string;
  legalDescription?: string;
  ownerName?: string;
  lastCountySync?: Date;
}
```

#### 5.1.2 New Collections

**deals**
```typescript
interface Deal {
  id: string;
  propertyId?: string; // relation to properties
  address: string;
  purchasePrice: number;
  rehabCost: number;
  arv: number;
  monthlyRent: number;
  status: 'analyzing' | 'offerMade' | 'underContract' | 'closed' | 'passed';
  verdict?: 'go' | 'negotiate' | 'pass';
  notes?: string;
  compReportId?: string; // relation to compReports

  // Analysis outputs
  totalCashIn?: number;
  refiAmount?: number;
  cashLeftInDeal?: number;
  monthlyPI?: number;
  noi?: number;
  cashFlow?: number;
  cashOnCash?: number;
  dscr?: number;

  created: Date;
  updated: Date;
}
```

**compReports**
```typescript
interface CompReport {
  id: string;
  subjectAddress: string;
  subjectPropertyId?: string;

  // Search parameters
  searchRadius: number; // miles
  dateRangeStart: Date;
  dateRangeEnd: Date;

  // Comps data
  comps: Comp[];
  adjustments: Adjustment[];

  // Results
  estimatedARV: number;
  confidenceLevel: 'low' | 'medium' | 'high';
  geminiAnalysis?: string;

  created: Date;
}

interface Comp {
  address: string;
  salePrice: number;
  saleDate: Date;
  beds: number;
  baths: number;
  sqft: number;
  pricePerSqft: number;
  distance: number; // miles from subject
  source: string;
}

interface Adjustment {
  compAddress: string;
  sqftAdjustment: number;
  bedAdjustment: number;
  bathAdjustment: number;
  conditionAdjustment: number;
  lotAdjustment: number;
  poolAdjustment: number;
  garageAdjustment: number;
  totalAdjustment: number;
  adjustedValue: number;
}
```

**documents**
```typescript
interface Document {
  id: string;
  propertyId: string; // relation to properties
  category: 'deed' | 'insurance' | 'lease' | 'loan' | 'tax' | 'rehab' | 'photos' | 'other';
  name: string;
  description?: string;

  // Storage
  filePath?: string; // for linked existing files
  file?: File; // PocketBase file upload

  expirationDate?: Date;
  notes?: string;

  created: Date;
  updated: Date;
}
```

**skipTraces**
```typescript
interface SkipTrace {
  id: string;
  address: string;
  propertyId?: string;

  // Owner info
  ownerName?: string;
  mailingAddress?: string;

  // Contact info
  phones: string[];
  emails: string[];

  // Entity info
  isEntity: boolean;
  entityName?: string;
  registeredAgent?: string;

  // Tracking
  contactAttempts: ContactAttempt[];

  created: Date;
}

interface ContactAttempt {
  date: Date;
  method: 'phone' | 'email' | 'mail';
  result: string;
  notes?: string;
}
```

**maintenanceRequests**
```typescript
interface MaintenanceRequest {
  id: string;
  propertyId: string;
  tenantId?: string;

  description: string;
  priority: 'low' | 'medium' | 'high' | 'emergency';
  status: 'open' | 'inProgress' | 'completed' | 'cancelled';

  photos?: File[];
  estimatedCost?: number;
  actualCost?: number;

  completedDate?: Date;
  notes?: string;

  created: Date;
  updated: Date;
}
```

### 5.2 Entity Relationships

```
┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│  properties │◀──────│   tenants   │       │    deals    │
│             │       │             │       │             │
│ - id        │       │ - id        │       │ - id        │
│ - address   │       │ - propertyId│       │ - propertyId│
│ - ...       │       │ - ...       │       │ - ...       │
└──────┬──────┘       └─────────────┘       └──────┬──────┘
       │                                           │
       │                                           │
       │     ┌─────────────┐                       │
       │     │  documents  │                       │
       │     │             │                       │
       │     │ - id        │                       │
       └────▶│ - propertyId│                       │
             │ - ...       │                       │
             └─────────────┘                       │
                                                   │
       ┌─────────────┐                             │
       │ compReports │◀────────────────────────────┘
       │             │
       │ - id        │
       │ - dealId    │
       │ - ...       │
       └─────────────┘

┌─────────────┐       ┌─────────────┐
│  properties │       │maintenance  │
│             │       │  Requests   │
│             │       │             │
│             │◀──────│ - id        │
│             │       │ - propertyId│
│             │       │ - tenantId  │
└─────────────┘       └─────────────┘

┌─────────────┐       ┌─────────────┐
│  properties │       │skipTraces   │
│             │       │             │
│             │◀──────│ - id        │
│             │       │ - propertyId│
│             │       │ - ...       │
└─────────────┘       └─────────────┘
```

### 5.3 PocketBase Service Account

Since there's no user authentication, the app uses a service account for PocketBase access:

```typescript
// lib/pocketbase.ts
import PocketBase from 'pocketbase';

const pb = new PocketBase('http://100.98.154.42:8090');

// Auto-authenticate as service account
export async function getPB() {
  if (!pb.authStore.isValid) {
    await pb.admins.authWithPassword(
      process.env.POCKETBASE_ADMIN_EMAIL!,
      process.env.POCKETBASE_ADMIN_PASSWORD!
    );
  }
  return pb;
}
```

---

## 6. API Design

### 6.1 Style

RESTful API with JSON request/response bodies. All routes are server-side Next.js API routes (`app/api/`).

**Naming Convention:**
- Plural nouns for resources: `/api/properties`, `/api/deals`
- Nested resources for relationships: `/api/properties/[id]/documents`
- Actions as POST endpoints: `/api/comps/search`, `/api/deals/analyze`

### 6.2 Standard Response Format

**Success:**
```typescript
{
  data: T;
  meta?: {
    page?: number;
    perPage?: number;
    total?: number;
  };
}
```

**Error:**
```typescript
{
  error: {
    code: string;
    message: string;
    details?: unknown;
  };
}
```

### 6.3 API Routes

#### Properties

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/properties` | List all properties with metrics |
| GET | `/api/properties/[id]` | Get property detail |
| POST | `/api/properties` | Create property |
| PATCH | `/api/properties/[id]` | Update property |
| DELETE | `/api/properties/[id]` | Delete property |
| GET | `/api/properties/[id]/documents` | List property documents |

#### Deals

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/deals` | List deal pipeline |
| GET | `/api/deals/[id]` | Get deal detail |
| POST | `/api/deals` | Create deal |
| POST | `/api/deals/analyze` | Run BRRRR analysis |
| PATCH | `/api/deals/[id]` | Update deal |
| DELETE | `/api/deals/[id]` | Delete deal |

#### Comps

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/comps/search` | Search comparable sales |
| POST | `/api/comps/analyze` | Gemini AI analysis |
| GET | `/api/comps/[id]` | Get saved comp report |
| POST | `/api/comps/[id]/export` | Export to PDF |

#### Research

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/research/property` | Pull county property data |
| GET | `/api/research/title` | Search clerk records |
| GET | `/api/research/flood` | Flood zone lookup |

#### Skip Trace

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/skip-trace` | Single property skip trace |
| POST | `/api/skip-trace/batch` | Batch skip trace |

#### Tenants

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/tenants` | List all tenants |
| GET | `/api/tenants/[id]` | Get tenant detail |
| POST | `/api/tenants/[id]/payment` | Record rent payment |
| GET | `/api/tenants/expiring` | Leases expiring soon |

#### Documents

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/documents/[propertyId]` | List documents |
| POST | `/api/documents/upload` | Upload document |
| DELETE | `/api/documents/[id]` | Delete document |

#### Lender Package

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/lender-package/[propertyId]` | Generate PDF package |

#### Dashboard

| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/dashboard/metrics` | Portfolio summary |
| GET | `/api/dashboard/alerts` | Active alerts |

### 6.4 Error Handling

```typescript
// lib/errors.ts
export class AppError extends Error {
  constructor(
    public code: string,
    message: string,
    public statusCode: number = 500,
    public details?: unknown
  ) {
    super(message);
  }
}

export const Errors = {
  NOT_FOUND: (resource: string) => new AppError('NOT_FOUND', `${resource} not found`, 404),
  VALIDATION: (details: unknown) => new AppError('VALIDATION_ERROR', 'Invalid input', 400, details),
  EXTERNAL_SERVICE: (service: string) => new AppError('EXTERNAL_SERVICE_ERROR', `${service} unavailable`, 502),
  RATE_LIMIT: () => new AppError('RATE_LIMIT', 'Too many requests', 429),
};
```

---

## 7. Project Structure

```
cfr-command-center/
├── app/
│   ├── (dashboard)/           # Dashboard layout group
│   │   ├── layout.tsx         # Dashboard shell
│   │   ├── page.tsx           # Dashboard home
│   │   ├── properties/
│   │   │   ├── page.tsx       # Property list
│   │   │   └── [id]/
│   │   │       └── page.tsx   # Property detail
│   │   ├── deals/
│   │   │   ├── page.tsx       # Deal pipeline
│   │   │   ├── new/
│   │   │   │   └── page.tsx   # New deal form
│   │   │   └── [id]/
│   │   │       └── page.tsx   # Deal detail
│   │   ├── comps/
│   │   │   ├── page.tsx       # Comp search
│   │   │   └── [id]/
│   │   │       └── page.tsx   # Comp report
│   │   ├── research/
│   │   │   └── page.tsx       # Property research
│   │   ├── tenants/
│   │   │   ├── page.tsx       # Rent roll
│   │   │   └── [id]/
│   │   │       └── page.tsx   # Tenant detail
│   │   └── documents/
│   │       └── page.tsx       # Document vault
│   ├── api/
│   │   ├── properties/
│   │   │   ├── route.ts       # GET, POST
│   │   │   └── [id]/
│   │   │       └── route.ts   # GET, PATCH, DELETE
│   │   ├── deals/
│   │   │   ├── route.ts
│   │   │   ├── analyze/
│   │   │   │   └── route.ts   # POST analysis
│   │   │   └── [id]/
│   │   │       └── route.ts
│   │   ├── comps/
│   │   │   ├── search/
│   │   │   │   └── route.ts   # POST search
│   │   │   ├── analyze/
│   │   │   │   └── route.ts   # POST AI analysis
│   │   │   └── [id]/
│   │   │       ├── route.ts
│   │   │       └── export/
│   │   │           └── route.ts
│   │   ├── research/
│   │   │   ├── property/
│   │   │   │   └── route.ts
│   │   │   ├── title/
│   │   │   │   └── route.ts
│   │   │   └── flood/
│   │   │       └── route.ts
│   │   ├── skip-trace/
│   │   │   ├── route.ts
│   │   │   └── batch/
│   │   │       └── route.ts
│   │   ├── tenants/
│   │   │   ├── route.ts
│   │   │   └── [id]/
│   │   │       ├── route.ts
│   │   │       └── payment/
│   │   │           └── route.ts
│   │   ├── documents/
│   │   │   ├── [propertyId]/
│   │   │   │   └── route.ts
│   │   │   └── upload/
│   │   │       └── route.ts
│   │   ├── lender-package/
│   │   │   └── [propertyId]/
│   │   │       └── route.ts
│   │   └── dashboard/
│   │       ├── metrics/
│   │       │   └── route.ts
│   │       └── alerts/
│   │           └── route.ts
│   ├── layout.tsx             # Root layout
│   └── globals.css            # Global styles
├── components/
│   ├── ui/                    # ShadCN components
│   │   ├── button.tsx
│   │   ├── card.tsx
│   │   ├── input.tsx
│   │   ├── table.tsx
│   │   └── ...
│   ├── dashboard/
│   │   ├── property-card.tsx
│   │   ├── metrics-bar.tsx
│   │   └── alerts-panel.tsx
│   ├── deals/
│   │   ├── brrrr-calculator.tsx
│   │   ├── verdict-display.tsx
│   │   └── scenario-comparison.tsx
│   ├── comps/
│   │   ├── comp-grid.tsx
│   │   ├── adjustment-calculator.tsx
│   │   └── arv-display.tsx
│   ├── research/
│   │   ├── property-search.tsx
│   │   └── property-profile.tsx
│   ├── tenants/
│   │   ├── rent-roll.tsx
│   │   └── payment-form.tsx
│   └── documents/
│       ├── document-vault.tsx
│       └── checklist.tsx
├── lib/
│   ├── pocketbase.ts          # PB client
│   ├── gemini.ts              # Gemini client
│   ├── scrapers/
│   │   ├── polk-pa.ts         # Polk County Property Appraiser
│   │   ├── hillsborough-pa.ts # Hillsborough County PA
│   │   └── polk-clerk.ts      # Polk County Clerk
│   ├── calculations/
│   │   ├── brrrr.ts           # BRRRR calculator
│   │   └── dscr.ts            # DSCR calculation
│   ├── pdf/
│   │   ├── comp-report.tsx    # Comp report PDF
│   │   └── lender-package.tsx # Lender package PDF
│   └── utils.ts
├── hooks/
│   ├── use-properties.ts
│   ├── use-deal-analysis.ts
│   └── use-comp-search.ts
├── types/
│   ├── property.ts
│   ├── deal.ts
│   ├── comp.ts
│   ├── tenant.ts
│   └── api.ts
├── public/
│   └── ...
├── .env.local                 # Environment variables
├── next.config.js
├── tailwind.config.ts
├── tsconfig.json
├── package.json
└── ecosystem.config.js        # PM2 config
```

---

## 8. Coding Standards

### 8.1 Naming Conventions

| Type | Convention | Example |
|------|------------|---------|
| Components | PascalCase | `PropertyCard.tsx` |
| Hooks | camelCase with `use` prefix | `useProperties.ts` |
| API Routes | kebab-case | `/api/skip-trace/` |
| Database Collections | camelCase | `compReports` |
| TypeScript Types | PascalCase | `interface Property` |
| Constants | SCREAMING_SNAKE | `MAX_FILE_SIZE` |
| Functions | camelCase | `calculateDSCR()` |
| CSS Classes | Tailwind utilities | `className="bg-blue-500"` |

### 8.2 File Organization

- One component per file
- Colocate related files (component + types + tests)
- Index files for clean imports
- Keep components under 200 lines; extract sub-components

### 8.3 TypeScript Standards

```typescript
// Prefer interfaces for object shapes
interface Property {
  id: string;
  address: string;
}

// Use type for unions, utility types
type PropertyStatus = 'rented' | 'rehab' | 'vacant' | 'closing';

// Avoid `any`; use `unknown` when type is truly unknown
function parseJson(input: string): unknown {
  return JSON.parse(input);
}

// Use Zod for runtime validation
import { z } from 'zod';

const PropertySchema = z.object({
  address: z.string(),
  purchasePrice: z.number().positive(),
});
```

### 8.4 Error Handling Pattern

```typescript
// API route error handling
import { NextResponse } from 'next/server';
import { AppError, Errors } from '@/lib/errors';

export async function GET(request: Request) {
  try {
    const data = await fetchData();
    return NextResponse.json({ data });
  } catch (error) {
    if (error instanceof AppError) {
      return NextResponse.json(
        { error: { code: error.code, message: error.message } },
        { status: error.statusCode }
      );
    }
    console.error('Unexpected error:', error);
    return NextResponse.json(
      { error: { code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' } },
      { status: 500 }
    );
  }
}
```

### 8.5 Component Pattern

```typescript
// Server Component (default)
import { getProperties } from '@/lib/pocketbase';
import { PropertyCard } from '@/components/dashboard/property-card';

export default async function PropertiesPage() {
  const properties = await getProperties();

  return (
    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
      {properties.map((property) => (
        <PropertyCard key={property.id} property={property} />
      ))}
    </div>
  );
}

// Client Component (when interactivity needed)
'use client';

import { useState } from 'react';
import { Button } from '@/components/ui/button';

export function PropertyCard({ property }: { property: Property }) {
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <div className="p-4 border rounded-lg">
      <h3>{property.address}</h3>
      <Button onClick={() => setIsExpanded(!isExpanded)}>
        {isExpanded ? 'Collapse' : 'Expand'}
      </Button>
    </div>
  );
}
```

---

## 9. Testing Strategy

| Type | Tool | Coverage Target | Focus |
|------|------|-----------------|-------|
| Unit | Vitest | 80% | Calculations, utilities, scrapers |
| Integration | Vitest + MSW | 60% | API routes, PocketBase interactions |
| E2E | Playwright | Critical paths | Deal analysis, comp search, PDF export |
| Visual | Storybook | N/A | Component library |

### 9.1 Test Priorities

**High Priority (must test):**
- BRRRR calculator logic
- DSCR calculation
- Comp adjustment math
- PDF generation
- County scraper parsing

**Medium Priority:**
- API route error handling
- Form validation
- Cache invalidation

**Lower Priority:**
- UI component rendering (TypeScript catches most issues)
- ShadCN components (already tested by library)

### 9.2 Test Structure

```
__tests__/
├── unit/
│   ├── calculations/
│   │   ├── brrrr.test.ts
│   │   └── dscr.test.ts
│   └── scrapers/
│       ├── polk-pa.test.ts
│       └── hillsborough-pa.test.ts
├── integration/
│   ├── api/
│   │   ├── properties.test.ts
│   │   └── deals.test.ts
│   └── lib/
│       └── pocketbase.test.ts
└── e2e/
    ├── deal-analysis.spec.ts
    └── comp-search.spec.ts
```

---

## 10. Security Considerations

### 10.1 Network Security

| Consideration | Mitigation |
|---------------|------------|
| Unauthorized access | Tailscale-only binding (no public internet) |
| API key exposure | All external API calls server-side only |
| PocketBase access | Admin credentials in `.env.local`, not in code |

### 10.2 Data Security

| Consideration | Mitigation |
|---------------|------------|
| Sensitive data (SSN, financial) | Not stored in this system |
| File uploads | Size limit 50MB, type whitelist |
| SQL injection | PocketBase uses parameterized queries |

### 10.3 Input Validation

- All API inputs validated with Zod schemas
- File uploads checked for type and size
- Address inputs sanitized before scraping

### 10.4 Error Messages

- Generic error messages to client
- Detailed errors logged server-side only
- No stack traces in production responses

---

## 11. Performance Considerations

### 11.1 Caching Strategy

| Data Type | Cache Strategy | TTL |
|-----------|----------------|-----|
| County property data | Local cache in PocketBase | 24 hours |
| Comp search results | In-memory cache | 1 hour |
| Dashboard metrics | Server cache | 5 minutes |
| Gemini AI responses | No cache (dynamic) | N/A |

### 11.2 Optimization Targets

| Metric | Target | Strategy |
|--------|--------|----------|
| Dashboard load | < 2s | Server components, streaming |
| Property research | < 10s | Parallel county requests |
| Deal calculation | < 100ms | Client-side calculation |
| PDF export | < 30s | Streaming response |

### 11.3 Bundle Size

- Tree-shaking for all imports
- Dynamic imports for heavy components
- ShadCN components imported individually
- Target: < 200KB initial JS bundle

### 11.4 Database Performance

- Indexes on `properties.status`, `properties.county`
- Indexes on `deals.status`, `tenants.propertyId`
- Pagination for all list views (50 items default)

---

## 12. Deployment

### 12.1 Server Configuration

```bash
# Environment
NODE_ENV=production
PORT=3007
HOSTNAME=100.98.154.42  # Tailscale IP only

# PocketBase
POCKETBASE_URL=http://100.98.154.42:8090
POCKETBASE_ADMIN_EMAIL=admin@example.com
POCKETBASE_ADMIN_PASSWORD=***

# Gemini
GEMINI_API_KEY=***

# File Storage
DOCUMENT_STORAGE_PATH=/mnt/storage/CFR Investments/
```

### 12.2 PM2 Configuration

```javascript
// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'cfr-command-center',
    script: 'npm',
    args: 'start',
    cwd: '/home/crogers2287/cfr-command-center',
    instances: 1,
    autorestart: true,
    watch: false,
    max_memory_restart: '500M',
    env_production: {
      NODE_ENV: 'production',
      PORT: 3007,
    },
    error_file: '/home/crogers2287/.pm2/logs/cfr-command-center-error.log',
    out_file: '/home/crogers2287/.pm2/logs/cfr-command-center-out.log',
  }],
};
```

### 12.3 Deployment Steps

```bash
# Initial setup (one-time)
cd /home/crogers2287
git clone <repo> cfr-command-center
cd cfr-command-center
npm install
cp .env.example .env.local
# Edit .env.local with production values

# Build
npm run build

# Start with PM2
pm2 start ecosystem.config.js --env production
pm2 save

# Updates
git pull
npm install
npm run build
pm2 restart cfr-command-center
```

### 12.4 Health Check

```typescript
// app/api/health/route.ts
import { NextResponse } from 'next/server';
import { getPB } from '@/lib/pocketbase';

export async function GET() {
  try {
    const pb = await getPB();
    await pb.health.check();
    return NextResponse.json({ status: 'healthy' });
  } catch (error) {
    return NextResponse.json(
      { status: 'unhealthy', error: String(error) },
      { status: 503 }
    );
  }
}
```

### 12.5 Monitoring

- PM2 logs: `pm2 logs cfr-command-center`
- PocketBase logs: Check PocketBase admin
- Uptime: Simple ping check via cron
- Alerts: None for MVP (Chad monitors manually)

---

## 13. Appendix

### 13.1 Environment Variables

```bash
# Required
NODE_ENV=production
PORT=3007
HOSTNAME=100.98.154.42
POCKETBASE_URL=http://100.98.154.42:8090
POCKETBASE_ADMIN_EMAIL=***
POCKETBASE_ADMIN_PASSWORD=***
GEMINI_API_KEY=***

# Optional
DOCUMENT_STORAGE_PATH=/mnt/storage/CFR Investments/
LOG_LEVEL=info
```

### 13.2 PocketBase Collection Schemas

See separate migration files for exact schema definitions. All new collections follow PocketBase conventions:
- `id` (text, primary key)
- `created` (datetime, auto)
- `updated` (datetime, auto)

### 13.3 Third-Party Dependencies

| Package | Version | Purpose |
|---------|---------|---------|
| next | 14.x | Framework |
| react | 18.x | UI library |
| react-dom | 18.x | React DOM |
| pocketbase | 0.22.x | Database client |
| @google/generative-ai | Latest | Gemini API |
| @react-pdf/renderer | 3.x | PDF generation |
| tailwindcss | 3.x | Styling |
| zod | 3.x | Validation |
| react-hook-form | 7.x | Forms |
| date-fns | 3.x | Date handling |
| cheerio | 1.x | HTML parsing |
| ky | 1.x | HTTP client |

---

**Document Complete.** This architecture provides a comprehensive technical foundation for implementing CFR Command Center. Next step: Run ux-designer to create UX specification.


# Epics & Stories: CFR Command Center

**Version:** 1.0
**Date:** 2026-02-21
**Author:** Scrum Master Agent

## Overview

| Epic | Stories | Priority | Dependencies |
|------|---------|----------|--------------|
| Epic 1: Foundation & Portfolio Dashboard | 12 | P0 (Must-Have) | None |
| Epic 2: BRRRR Deal Analyzer | 11 | P0 (Must-Have) | Epic 1 |
| Epic 3: Comp Search & AI Analysis | 10 | P0 (Must-Have) | Epic 1 |
| Epic 4: Property Research | 9 | P0 (Must-Have) | Epic 1 |
| Epic 5: Document Vault | 8 | P0 (Must-Have) | Epic 1 |
| Epic 6: Tenant Management | 9 | P1 (Should-Have) | Epic 1, 4 |
| Epic 7: Skip Tracing | 7 | P1 (Should-Have) | Epic 1, 4 |
| Epic 8: Title Search | 6 | P1 (Should-Have) | Epic 1, 4 |
| Epic 9: Lender Package Generator | 8 | P1 (Should-Have) | Epic 1, 2, 3, 5 |

**Total Stories:** 80
**Estimated Timeline:** MVP (Epics 1-5) in 2-3 weeks, Should-Haves (Epics 6-9) within 30 days

---

## Epic 1: Foundation & Portfolio Dashboard

**Goal:** Establish project infrastructure and deliver immediate value with portfolio overview
**Value:** Single source of truth for existing properties, leveraging existing PocketBase data
**Dependencies:** None (build first)

### Stories

#### Story 1-1: Initialize Next.js Project

**As a** developer
**I want** a Next.js 14 project with TypeScript configured
**So that** development can begin on a solid foundation

**Acceptance Criteria:**

```gherkin
Given a fresh project directory
When I run the initialization
Then Next.js 14 is installed with App Router
And TypeScript is configured with strict mode
And ESLint and Prettier are configured
And the project builds successfully with `npm run build`
```

**Technical Notes:**
- Use `npx create-next-app@latest` with TypeScript, Tailwind, App Router
- Configure `tsconfig.json` with `strict: true`
- Add path aliases (`@/components`, `@/lib`, etc.)

**Dependencies:** None

**Estimated Effort:** S (1-2 hours)

---

#### Story 1-2: Configure Tailwind CSS & Dark Theme

**As a** developer
**I want** Tailwind CSS configured with custom design tokens
**So that** consistent styling is available across all components

**Acceptance Criteria:**

```gherkin
Given the Next.js project
When Tailwind CSS is configured
Then custom colors (bg-primary, money-green, danger-red, etc.) are defined
And dark mode is the default and only theme
And Inter font is loaded and applied
And tabular-nums is set for numeric content
```

**Technical Notes:**
- Define CSS custom properties in `globals.css`
- Extend Tailwind theme in `tailwind.config.ts`
- Add Inter font via `next/font`

**Dependencies:** 1-1

**Estimated Effort:** S (1-2 hours)

---

#### Story 1-3: Install and Configure ShadCN UI

**As a** developer
**I want** ShadCN UI components available
**So that** consistent, accessible UI components can be used

**Acceptance Criteria:**

```gherkin
Given the Next.js project with Tailwind
When ShadCN UI is initialized
Then Button, Card, Input, Table, Dialog, Badge components are installed
And components are styled with dark theme defaults
And components use Tailwind CSS
```

**Technical Notes:**
- Run `npx shadcn@latest init` with dark theme defaults
- Install core components: button, card, input, table, dialog, badge, dropdown-menu, toast

**Dependencies:** 1-2

**Estimated Effort:** S (1-2 hours)

---

#### Story 1-4: Create PocketBase Client Library

**As a** developer
**I want** a PocketBase client configured with service account auth
**So that** all API routes can access the database

**Acceptance Criteria:**

```gherkin
Given PocketBase running at the Tailscale address
When I import the client library
Then it auto-authenticates as admin on first use
And it maintains auth state across requests
And it throws clear errors on connection failure
```

**Technical Notes:**
- Create `lib/pocketbase.ts` with singleton pattern
- Store credentials in `.env.local`
- Implement re-auth on token expiry

**Dependencies:** 1-1

**Estimated Effort:** S (1-2 hours)

---

#### Story 1-5: Create TypeScript Types from PocketBase Schema

**As a** developer
**I want** TypeScript interfaces for all PocketBase collections
**So that** type safety is enforced throughout the codebase

**Acceptance Criteria:**

```gherkin
Given the existing PocketBase collections
When types are defined
Then Property, Tenant, RentHistory, RentArrears, PropertyLedger, PropertyValue interfaces exist
And all types match PocketBase schema exactly
And relation fields use proper types
```

**Technical Notes:**
- Create `types/pocketbase.ts`
- Include all existing collections: properties, tenants, rentHistory, damages, rentArrears, propertyLedger, propertyValues
- Add utility types for create/update operations

**Dependencies:** 1-4

**Estimated Effort:** S (1-2 hours)

---

#### Story 1-6: Build Dashboard Layout with Sidebar Navigation

**As a** user
**I want** a consistent layout with sidebar navigation
**So that** I can easily navigate between all sections of the app

**Acceptance Criteria:**

```gherkin
Given the app loads
When I view any dashboard page
Then a sidebar is visible on the left with navigation items
And the sidebar shows: Dashboard, Research, Deals, Properties, Tenants, Documents, Lender Pkg
And clicking a nav item navigates to that section
And the active item has green left border highlight
And on mobile, sidebar is hidden with hamburger toggle
```

**Technical Notes:**
- Create `app/(dashboard)/layout.tsx`
- Use responsive design: full sidebar on desktop, hamburger on mobile
- Navigation items with icons (Lucide or emoji)

**Dependencies:** 1-3

**Estimated Effort:** M (2-3 hours)

---

#### Story 1-7: Build Metric Card Component

**As a** developer
**I want** a reusable metric card component
**So that** dashboard metrics are consistently displayed

**Acceptance Criteria:**

```gherkin
Given the MetricCard component
When rendered with a label, value, and optional trend
Then it displays the label in caption/secondary style
And the value is large (display font) in money-green
And trend indicator shows arrow and percentage
And component accepts className for layout flexibility
```

**Technical Notes:**
- Create `components/dashboard/metric-card.tsx`
- Props: label, value, trend, icon
- Use mono font for numeric values

**Dependencies:** 1-3

**Estimated Effort:** S (1-2 hours)

---

#### Story 1-8: Build Property Card Component

**As a** user
**I want** to see property information in a card format
**So that** I can quickly scan portfolio status

**Acceptance Criteria:**

```gherkin
Given a property object
When the PropertyCard is rendered
Then it shows address, city, state
And status badge (Rented=green, Rehab=amber, Vacant=red, Closing=blue)
And three key metrics: Equity, Cash Flow, DSCR with color coding
And alert bar if issues exist (hard money loan, arrears)
And clicking the card navigates to property detail
```

**Technical Notes:**
- Create `components/dashboard/property-card.tsx`
- Calculate equity from current_value - current_debt
- Calculate cash flow from rent - PITI
- Show DSCR with green checkmark if >= 1.25

**Dependencies:** 1-3, 1-5

**Estimated Effort:** M (2-3 hours)

---

#### Story 1-9: Build Alerts Panel Component

**As a** user
**I want** to see important alerts on the dashboard
**So that** time-sensitive items are visible immediately

**Acceptance Criteria:**

```gherkin
Given properties with alert conditions
When the AlertsPanel is rendered
Then it shows insurance renewals due within 60 days
And property tax deadlines within 60 days
And lease expirations within 90 days
And hard money loans with high rates
And arrears over $1,000
And clicking an alert navigates to the property
```

**Technical Notes:**
- Create `components/dashboard/alerts-panel.tsx`
- Calculate alerts from property data
- Use semantic colors: red for critical, amber for warning

**Dependencies:** 1-5, 1-8

**Estimated Effort:** M (2-3 hours)

---

#### Story 1-10: Create Dashboard API Route

**As a** developer
**I want** an API endpoint that returns portfolio metrics
**So that** the dashboard can display aggregate data

**Acceptance Criteria:**

```gherkin
Given a GET request to /api/dashboard
When the endpoint is called
Then it returns total portfolio value
And total equity
And total monthly cash flow
And average DSCR
And list of active alerts
And response time is under 500ms
```

**Technical Notes:**
- Create `app/api/dashboard/route.ts`
- Query PocketBase for all properties
- Calculate aggregates server-side

**Dependencies:** 1-4, 1-5

**Estimated Effort:** M (2-3 hours)

---

#### Story 1-11: Build Dashboard Home Page

**As a** user
**I want** to see a portfolio overview when I open the app
**So that** I immediately understand my investment position

**Acceptance Criteria:**

```gherkin
Given the app loads
When I navigate to the dashboard home
Then I see four metric cards: Total Value, Total Equity, Monthly Cash Flow, Avg DSCR
And I see the alerts panel with any active alerts
And I see property cards in a grid layout
And I see upcoming events timeline (closings, lease renewals, tax deadlines)
And the page loads in under 2 seconds
```

**Technical Notes:**
- Create `app/(dashboard)/page.tsx`
- Fetch data from `/api/dashboard`
- Use MetricCard, AlertsPanel, PropertyCard components
- Server component for data fetching

**Dependencies:** 1-6, 1-7, 1-8, 1-9, 1-10

**Estimated Effort:** M (2-3 hours)

---

#### Story 1-12: Configure PM2 Deployment

**As a** developer
**I want** the app managed by PM2
**So that** it auto-restarts and logs are captured

**Acceptance Criteria:**

```gherkin
Given the app is built
When PM2 starts the app
Then it listens on Tailscale IP port 3007
And it auto-restarts on crash
And logs are written to PM2 log files
And ecosystem.config.js is committed to the repo
```

**Technical Notes:**
- Create `ecosystem.config.js`
- Configure for production: NODE_ENV, PORT, HOSTNAME
- Test with `pm2 start ecosystem.config.js`

**Dependencies:** 1-11

**Estimated Effort:** S (1-2 hours)

---

## Epic 2: BRRRR Deal Analyzer

**Goal:** Enable rapid deal analysis with BRRRR methodology calculations
**Value:** Core decision-making engine for investment decisions
**Dependencies:** Epic 1 (Foundation)

### Stories

#### Story 2-1: Create Deal Type Definitions

**As a** developer
**I want** TypeScript types for deal analysis
**So that** the analyzer has type safety

**Acceptance Criteria:**

```gherkin
Given the need for deal analysis types
When types are created
Then Deal, RefiScenario, DealAnalysis interfaces exist
And all calculation outputs are typed
And refi_scenarios is typed as array
```

**Technical Notes:**
- Create `types/deal.ts`
- Include all fields from architecture doc: purchasePrice, rehabCost, arv, monthlyRent, etc.
- Add calculation result types

**Dependencies:** 1-5

**Estimated Effort:** S (1-2 hours)

---

#### Story 2-2: Build BRRRR Calculator Library

**As a** developer
**I want** pure calculation functions for BRRRR metrics
**So that** deal analysis is testable and reusable

**Acceptance Criteria:**

```gherkin
Given deal inputs (purchase, rehab, ARV, rent, rate, LTV)
When I call calculateBRRRR()
Then it returns: allInCost, maxOffer (70% rule), refiAmount, cashBack, monthlyPI
And it calculates DSCR = rent / PITI
And it calculates cash-on-cash return
And all calculations handle edge cases (zero values, negative)
```

**Technical Notes:**
- Create `lib/calculations/brrrr.ts`
- Include PITI calculation (principal, interest, taxes, insurance)
- DSCR formula: rent / PITI
- 70% rule: ARV * 0.70 - rehab

**Dependencies:** 2-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 2-3: Build DSCR Calculation Library

**As a** developer
**I want** a dedicated DSCR calculation function
**So that** debt service coverage is accurately computed

**Acceptance Criteria:**

```gherkin
Given monthly rent and loan details (principal, rate, term, taxes, insurance)
When I call calculateDSCR()
Then it returns DSCR value to 2 decimal places
And it returns the PITI breakdown
And it flags if DSCR >= 1.25 (lender ready)
And it handles partial inputs gracefully
```

**Technical Notes:**
- Create `lib/calculations/dscr.ts`
- Standard formula: DSCR = Gross Rent / PITI
- Include helper for monthly mortgage payment (amortization formula)

**Dependencies:** 2-1

**Estimated Effort:** S (1-2 hours)

---

#### Story 2-4: Build Deal Input Form Component

**As a** user
**I want** to enter deal parameters in a form
**So that** I can analyze a potential investment

**Acceptance Criteria:**

```gherkin
Given I'm on the Deal Analyzer page
When I view the input form
Then I see fields for: address, purchase price, rehab budget, ARV, monthly rent
And I see fields for: interest rate, LTV, insurance, taxes, vacancy rate
And currency fields auto-format with $ prefix
And percentage fields show % suffix
And all inputs have clear labels
```

**Technical Notes:**
- Create `components/deals/deal-input-form.tsx`
- Use React Hook Form for form state
- Use Zod schema for validation
- Currency inputs auto-format on blur

**Dependencies:** 1-3, 2-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 2-5: Build Deal Results Panel Component

**As a** user
**I want** to see calculation results update in real-time
**So that** I can understand the deal metrics as I enter data

**Acceptance Criteria:**

```gherkin
Given I have entered deal inputs
When I view the results panel
Then I see: Total Cash In, Refi Amount, Cash Back, Monthly P&I
And I see: NOI, Cash Flow, DSCR, Cash-on-Cash
And results update within 100ms of input change
And DSCR shows green checkmark if >= 1.25
And negative cash flow shows in red
```

**Technical Notes:**
- Create `components/deals/deal-results-panel.tsx`
- Use calculation library from 2-2
- Implement debounced recalculation
- Color-code metrics based on values

**Dependencies:** 2-2, 2-3, 2-4

**Estimated Effort:** M (2-3 hours)

---

#### Story 2-6: Build Verdict Display Component

**As a** user
**I want** to see a clear GO/NEGOTIATE/PASS verdict
**So that** I can quickly understand if the deal is viable

**Acceptance Criteria:**

```gherkin
Given deal analysis results
When the verdict is calculated
Then GO shows green badge if: DSCR >= 1.25 AND cash flow positive AND ARV covers all-in
And NEGOTIATE shows amber badge if: DSCR 1.0-1.25 OR borderline metrics
And PASS shows red badge if: DSCR < 1.0 OR negative cash flow OR ARV too low
And verdict updates in real-time with input changes
```

**Technical Notes:**
- Create `components/deals/verdict-display.tsx`
- Implement verdict logic based on thresholds
- Large badge with clear visual hierarchy

**Dependencies:** 2-5

**Estimated Effort:** S (1-2 hours)

---

#### Story 2-7: Build Refi Scenario Comparison Component

**As a** user
**I want** to compare multiple refinance scenarios
**So that** I can choose the best financing approach

**Acceptance Criteria:**

```gherkin
Given deal inputs
When I view the refi scenarios
Then I see a table with 3 LTV options (65%, 70%, 75%)
And I see cash flow and DSCR for each scenario
And I can edit the LTV percentages
And I can add a custom scenario
And best scenario is highlighted
```

**Technical Notes:**
- Create `components/deals/refi-scenarios.tsx`
- Default 3 columns, allow editing
- Highlight column with highest cash-out + positive DSCR

**Dependencies:** 2-5

**Estimated Effort:** M (2-3 hours)

---

#### Story 2-8: Create Deal Analyzer Page

**As a** user
**I want** a dedicated page for deal analysis
**So that** I can analyze deals in a focused environment

**Acceptance Criteria:**

```gherkin
Given I navigate to /deals/new
When the page loads
Then I see the deal input form on the left
And I see the results panel on the right (desktop)
And I see the verdict display prominently
And I see the refi scenarios below results
And mobile shows inputs top, results below (stacked)
And there's a Save Deal button
And there's a Reset button to clear form
```

**Technical Notes:**
- Create `app/(dashboard)/deals/new/page.tsx`
- Two-column layout on desktop
- Use DealInputForm, DealResultsPanel, VerdictDisplay, RefiScenarios

**Dependencies:** 2-4, 2-5, 2-6, 2-7

**Estimated Effort:** M (2-3 hours)

---

#### Story 2-9: Create Deals Collection in PocketBase

**As a** developer
**I want** a deals collection in PocketBase
**So that** deal analyses can be saved and retrieved

**Acceptance Criteria:**

```gherkin
Given PocketBase admin access
When the deals collection is created
Then it has fields matching the Deal type
And it has relation to properties (optional)
And it has status field: analyzing, offerMade, underContract, closed, passed
And it has verdict field: go, negotiate, pass
```

**Technical Notes:**
- Create collection via PocketBase admin or migration
- Include all fields from types/deal.ts
- Set up relation to properties collection

**Dependencies:** 1-4

**Estimated Effort:** S (1-2 hours)

---

#### Story 2-10: Create Deal Save/Load API Routes

**As a** developer
**I want** API routes for saving and loading deals
**So that** deal analysis persistence works

**Acceptance Criteria:**

```gherkin
Given a deal analysis
When I POST to /api/deals
Then the deal is saved to PocketBase
And it returns the saved deal with ID

Given a deal ID
When I GET /api/deals/[id]
Then the deal is retrieved from PocketBase
And all fields are populated
```

**Technical Notes:**
- Create `app/api/deals/route.ts` (GET list, POST create)
- Create `app/api/deals/[id]/route.ts` (GET, PUT, DELETE)
- Validate input with Zod

**Dependencies:** 2-9

**Estimated Effort:** M (2-3 hours)

---

#### Story 2-11: Build Deal Pipeline Page

**As a** user
**I want** to see all saved deals in a pipeline view
**So that** I can track deal status

**Acceptance Criteria:**

```gherkin
Given I navigate to /deals
When the page loads
Then I see deals organized by status (Kanban or table)
And statuses are: Analyzing, Offer Made, Under Contract, Closed, Passed
And each deal card shows: address, price, ARV, verdict badge
And I can click a deal to open analyzer
And there's a "+ New Deal" button
```

**Technical Notes:**
- Create `app/(dashboard)/deals/page.tsx`
- Kanban view with drag-to-change-status (optional)
- Table view as alternative
- Fetch from /api/deals

**Dependencies:** 2-8, 2-10

**Estimated Effort:** M (2-3 hours)

---

## Epic 3: Comp Search & AI Analysis

**Goal:** Find comparable sales and estimate ARV with Gemini AI
**Value:** Replace manual comp research with AI-powered analysis
**Dependencies:** Epic 1 (Foundation)

### Stories

#### Story 3-1: Create Comp Type Definitions

**As a** developer
**I want** TypeScript types for comp data
**So that** comp search has type safety

**Acceptance Criteria:**

```gherkin
Given the need for comp types
When types are created
Then Comp, CompReport, Adjustment interfaces exist
And search parameters are typed
And Gemini response is typed
```

**Technical Notes:**
- Create `types/comp.ts`
- Include: address, beds, baths, sqft, salePrice, saleDate, pricePerSqft
- Add adjustment types for comp comparison

**Dependencies:** 1-5

**Estimated Effort:** S (1-2 hours)

---

#### Story 3-2: Build Gemini API Client Library

**As a** developer
**I want** a configured Gemini API client
**So that** AI features can be added

**Acceptance Criteria:**

```gherkin
Given Gemini API key in environment
When I import the Gemini client
Then it authenticates with the API key
And it provides a generateContent function
And it handles rate limiting gracefully
And it logs errors to console
```

**Technical Notes:**
- Create `lib/gemini.ts`
- Use @google/generative-ai package
- Store API key in .env.local
- Implement retry logic for rate limits

**Dependencies:** 1-1

**Estimated Effort:** S (1-2 hours)

---

#### Story 3-3: Build Comp Search Input Component

**As a** user
**I want** to search for comps by address
**So that** I can find comparable sales

**Acceptance Criteria:**

```gherkin
Given I'm on the Comp Search page
When I view the search form
Then I see an address search bar (prominent)
And I see filter dropdowns: radius (1-5 mi), date range (3-12 mo), property type
And I see bed/bath/sqft range filters
And I can click "Search Comps" to execute
```

**Technical Notes:**
- Create `components/comps/comp-search-input.tsx`
- Large search bar (44px height)
- Filters in a collapsible panel

**Dependencies:** 1-3, 3-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 3-4: Build Comp Results Grid Component

**As a** user
**I want** to see comp results in a table
**So that** I can review comparable sales

**Acceptance Criteria:**

```gherkin
Given comps are found
When the results grid renders
Then I see a table with: address, price, beds/baths, sqft, $/sqft, sale date
And I can sort by any column
And I can select comps with checkboxes
And I see the number of results found
And empty state shows if no comps found
```

**Technical Notes:**
- Create `components/comps/comp-results-grid.tsx`
- Use ShadCN Table component
- Sticky header, sortable columns
- Checkbox selection for comparison

**Dependencies:** 3-3

**Estimated Effort:** M (2-3 hours)

---

#### Story 3-5: Build Comp Comparison Panel Component

**As a** user
**I want** to compare selected comps side-by-side
**So that** I can make adjustments and estimate ARV

**Acceptance Criteria:**

```gherkin
Given I have selected 2-5 comps
When the comparison panel shows
Then I see subject property on left
And I see selected comps in columns
And I see adjustment rows for: sqft, beds, baths, condition, lot, pool, garage
And each comp shows adjusted value
And estimated ARV is calculated from adjusted values
```

**Technical Notes:**
- Create `components/comps/comp-comparison-panel.tsx`
- Allow manual adjustment entry
- Calculate weighted average ARV

**Dependencies:** 3-4

**Estimated Effort:** M (2-3 hours)

---

#### Story 3-6: Build AI ARV Analysis Component

**As a** user
**I want** Gemini AI to analyze comps and estimate ARV
**So that** I get an intelligent ARV recommendation

**Acceptance Criteria:**

```gherkin
Given selected comps
When I request AI analysis
Then Gemini receives the comp data
And it returns an estimated ARV with confidence level
And it returns a narrative explanation
And the response shows in a highlighted panel
And loading state shows during API call
And error shows if API fails
```

**Technical Notes:**
- Create `components/comps/ai-arv-analysis.tsx`
- Call Gemini API via server-side route
- Prompt engineering for real estate ARV estimation
- Show confidence: HIGH/MEDIUM/LOW

**Dependencies:** 3-2, 3-5

**Estimated Effort:** M (2-3 hours)

---

#### Story 3-7: Create Comp Search API Route

**As a** developer
**I want** an API endpoint for comp search
**So that** comp data can be fetched

**Acceptance Criteria:**

```gherkin
Given a POST request to /api/comps/search with address and filters
When the endpoint is called
Then it searches for comparable sales
And it returns comp data array
And it handles errors gracefully
```

**Technical Notes:**
- Create `app/api/comps/search/route.ts`
- For MVP, use Gemini to analyze available data or mock
- Future: integrate with actual MLS/comp APIs

**Dependencies:** 3-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 3-8: Create Gemini Analysis API Route

**As a** developer
**I want** an API endpoint for Gemini ARV analysis
**So that** the API key stays server-side

**Acceptance Criteria:**

```gherkin
Given a POST request to /api/comps/analyze with comp data
When the endpoint is called
Then it calls Gemini API with structured prompt
And it parses the response
And it returns ARV estimate and narrative
And API key is never exposed to client
```

**Technical Notes:**
- Create `app/api/comps/analyze/route.ts`
- Use Gemini client from 3-2
- Prompt template for ARV estimation

**Dependencies:** 3-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 3-9: Create Comp Reports Collection

**As a** developer
**I want** a compReports collection in PocketBase
**So that** comp analyses can be saved

**Acceptance Criteria:**

```gherkin
Given PocketBase admin access
When the compReports collection is created
Then it has subjectAddress, search params, comps array
And it has estimatedARV, confidenceLevel, geminiAnalysis
And it has created timestamp
```

**Technical Notes:**
- Create collection via PocketBase admin
- Include all fields from types/comp.ts
- Relation to deals and properties

**Dependencies:** 1-4

**Estimated Effort:** S (1-2 hours)

---

#### Story 3-10: Build Comp Search Page

**As a** user
**I want** a dedicated page for comp search
**So that** I can research property values

**Acceptance Criteria:**

```gherkin
Given I navigate to /comps
When the page loads
Then I see the comp search input at top
And I see results grid after search
And I see comparison panel when comps selected
And I see AI analysis panel after analysis
And there are Save Report and Export PDF buttons
And "Use in Deal Analyzer" button pre-fills ARV
```

**Technical Notes:**
- Create `app/(dashboard)/comps/page.tsx`
- Integrate all comp components
- Handle loading and error states

**Dependencies:** 3-3, 3-4, 3-5, 3-6, 3-7, 3-8

**Estimated Effort:** M (2-3 hours)

---

## Epic 4: Property Research

**Goal:** Pull property data from county records automatically
**Value:** Eliminate manual lookups on county appraiser sites
**Dependencies:** Epic 1 (Foundation)

### Stories

#### Story 4-1: Create Property Research Types

**As a** developer
**I want** TypeScript types for county property data
**So that** research features have type safety

**Acceptance Criteria:**

```gherkin
Given the need for property data types
When types are created
Then CountyProperty, PropertyProfile interfaces exist
And scraper response types are defined
And error types are included
```

**Technical Notes:**
- Create `types/research.ts`
- Include all county data fields: parcelId, beds, baths, sqft, yearBuilt, lotSize, etc.

**Dependencies:** 1-5

**Estimated Effort:** S (1-2 hours)

---

#### Story 4-2: Build Polk County Scraper

**As a** developer
**I want** a scraper for Polk County Property Appraiser
**So that** property data can be fetched

**Acceptance Criteria:**

```gherkin
Given a property address in Polk County
When I call scrapePolkCounty(address)
Then it returns: parcelId, owner, beds, baths, sqft, yearBuilt, lotSize
And it returns: taxAssessment, taxAmount, taxStatus
And it handles address not found
And it handles site unavailable
And response is under 10 seconds
```

**Technical Notes:**
- Create `lib/scrapers/polk-pa.ts`
- Use Cheerio for HTML parsing
- Handle rate limiting
- Cache results

**Dependencies:** 4-1

**Estimated Effort:** L (3-4 hours)

---

#### Story 4-3: Build Hillsborough County Scraper

**As a** developer
**I want** a scraper for Hillsborough County Property Appraiser
**So that** property data can be fetched

**Acceptance Criteria:**

```gherkin
Given a property address in Hillsborough County
When I call scrapeHillsboroughCounty(address)
Then it returns same fields as Polk scraper
And it handles address not found
And it handles site unavailable
And response is under 10 seconds
```

**Technical Notes:**
- Create `lib/scrapers/hillsborough-pa.ts`
- Similar approach to Polk scraper
- May have different HTML structure

**Dependencies:** 4-1

**Estimated Effort:** L (3-4 hours)

---

#### Story 4-4: Build County Detection Logic

**As a** developer
**I want** automatic county detection from address
**So that** the right scraper is called

**Acceptance Criteria:**

```gherkin
Given a property address
When I call detectCounty(address)
Then it returns "Polk" for Polk County addresses
And it returns "Hillsborough" for Hillsborough addresses
And it returns null for unsupported counties
And it handles edge cases (unincorporated areas)
```

**Technical Notes:**
- Create `lib/scrapers/detect-county.ts`
- Use city name or ZIP code mapping
- Map of ZIP codes to counties

**Dependencies:** 4-1

**Estimated Effort:** S (1-2 hours)

---

#### Story 4-5: Build Property Profile Card Component

**As a** user
**I want** to see property data in a formatted card
**So that** I can quickly review the property details

**Acceptance Criteria:**

```gherkin
Given county property data
When the PropertyProfileCard renders
Then it shows address, parcel ID, county
And it shows beds, baths, sqft, year built, lot size
And it shows property type with manufactured warning badge
And it shows owner name and mailing address
And it shows tax assessment, tax amount, tax status
And it shows flood zone if available
```

**Technical Notes:**
- Create `components/research/property-profile-card.tsx`
- Use Card component with sections
- Flag manufactured homes with amber warning

**Dependencies:** 1-3, 4-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 4-6: Create Property Research API Route

**As a** developer
**I want** an API endpoint for property research
**So that** county data can be fetched client-side

**Acceptance Criteria:**

```gherkin
Given a GET request to /api/research/property with address param
When the endpoint is called
Then it detects the county
And it calls the appropriate scraper
And it returns the property data
And it handles errors (not found, unavailable)
And it caches results for 24 hours
```

**Technical Notes:**
- Create `app/api/research/property/route.ts`
- Use county detection and scrapers
- Implement caching

**Dependencies:** 4-2, 4-3, 4-4

**Estimated Effort:** M (2-3 hours)

---

#### Story 4-7: Extend PocketBase Properties Collection

**As a** developer
**I want** extended property fields in PocketBase
**So that** county data can be saved

**Acceptance Criteria:**

```gherkin
Given the existing properties collection
When fields are added
Then new fields exist: county, parcelId, floodZone, zoning, taxAssessment, taxAmount, taxStatus
And new fields exist: legalDescription, ownerName, lastCountySync
And existing data is preserved
```

**Technical Notes:**
- Update PocketBase schema via admin UI
- Add new optional fields
- Run migration for existing records

**Dependencies:** 1-4

**Estimated Effort:** S (1-2 hours)

---

#### Story 4-8: Build Property Research Page

**As a** user
**I want** a dedicated page for property research
**So that** I can look up any property

**Acceptance Criteria:**

```gherkin
Given I navigate to /research
When the page loads
Then I see a prominent search bar
And after search, I see the property profile card
And I see quick action buttons: Skip Trace, Title Search, Analyze Deal, Save
And I can save property to my portfolio
And empty state shows if address not found
```

**Technical Notes:**
- Create `app/(dashboard)/research/page.tsx`
- Integrate PropertyProfileCard
- Action buttons link to other modules

**Dependencies:** 4-5, 4-6

**Estimated Effort:** M (2-3 hours)

---

#### Story 4-9: Add Property Type Detection

**As a** user
**I want** the system to detect if a property is manufactured
**So that** I understand financing implications

**Acceptance Criteria:**

```gherkin
Given property data from county
When property type is detected
Then "siteBuilt", "manufactured", "mobileHome", "doubleWide" labels are assigned
And manufactured/mobile shows amber warning badge
And warning explains financing limitations
```

**Technical Notes:**
- Add logic to scrapers to detect property type
- Check county data fields for indicators
- Show warning in PropertyProfileCard

**Dependencies:** 4-2, 4-3

**Estimated Effort:** S (1-2 hours)

---

## Epic 5: Document Vault

**Goal:** Organize property documents with checklist tracking
**Value:** Ensure all required documents are collected for lender packages
**Dependencies:** Epic 1 (Foundation)

### Stories

#### Story 5-1: Create Document Types

**As a** developer
**I want** TypeScript types for documents
**So that** document features have type safety

**Acceptance Criteria:**

```gherkin
Given the need for document types
When types are created
Then Document, DocumentCategory, ChecklistItem interfaces exist
And upload types are defined
```

**Technical Notes:**
- Create `types/document.ts`
- Categories: deed, title, insurance, lease, loan, tax, rehab, photos, other

**Dependencies:** 1-5

**Estimated Effort:** S (1-2 hours)

---

#### Story 5-2: Create Documents Collection in PocketBase

**As a** developer
**I want** a documents collection in PocketBase
**So that** files can be stored and retrieved

**Acceptance Criteria:**

```gherkin
Given PocketBase admin access
When the documents collection is created
Then it has: propertyId (relation), filename, filePath, fileType, fileSize
And it has: category, checklistItem, notes, uploadedAt
And it supports file uploads up to 25MB
And file field uses PocketBase storage
```

**Technical Notes:**
- Create collection with file field
- Configure storage location
- Set file size limits

**Dependencies:** 1-4

**Estimated Effort:** S (1-2 hours)

---

#### Story 5-3: Build Document Upload Component

**As a** user
**I want** to upload documents to a property
**So that** files are organized and accessible

**Acceptance Criteria:**

```gherkin
Given I'm on the Documents page
When I click Upload
Then I see a file picker accepting PDF, JPG, PNG, DOC, XLSX
And I can drag-and-drop files
And I select the property and category
And upload shows progress indicator
And success/error toast shows on completion
And duplicate detection warns before upload
```

**Technical Notes:**
- Create `components/documents/document-upload.tsx`
- Use drag-and-drop zone
- Validate file types and size
- Upload to PocketBase

**Dependencies:** 5-1, 5-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 5-4: Build Document Checklist Component

**As a** user
**I want** to see what documents are missing for each property
**So that** I can ensure completeness

**Acceptance Criteria:**

```gherkin
Given a property
When the checklist renders
Then I see required document types: deed, title, insurance, lease, loan, tax, rehab
And each type shows checkmark if uploaded or X if missing
And completion percentage shows
And clicking a missing item prompts upload
```

**Technical Notes:**
- Create `components/documents/document-checklist.tsx`
- Calculate completion from uploaded documents
- Define required checklist items

**Dependencies:** 5-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 5-5: Build Document List Component

**As a** user
**I want** to see all documents for a property
**So that** I can find and access files

**Acceptance Criteria:**

```gherkin
Given a property with documents
When the document list renders
Then I see a table with: filename, category, date, size
And I can filter by category
And I can search by filename
And I can click to download or preview
And I can delete with confirmation
```

**Technical Notes:**
- Create `components/documents/document-list.tsx`
- Use ShadCN Table
- Category filter dropdown
- Search functionality

**Dependencies:** 5-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 5-6: Create Document API Routes

**As a** developer
**I want** API routes for document operations
**So that** files can be uploaded and retrieved

**Acceptance Criteria:**

```gherkin
Given the document API routes
When I POST /api/documents/upload with file
Then file is uploaded to PocketBase
And document record is created

When I GET /api/documents/[propertyId]
Then all documents for property are returned

When I DELETE /api/documents/[id]
Then document is deleted with confirmation
```

**Technical Notes:**
- Create `app/api/documents/upload/route.ts`
- Create `app/api/documents/[propertyId]/route.ts`
- Handle multipart file uploads

**Dependencies:** 5-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 5-7: Build Document Vault Page

**As a** user
**I want** a dedicated page for document management
**So that** I can organize property files

**Acceptance Criteria:**

```gherkin
Given I navigate to /documents
When the page loads
Then I see a property selector dropdown
And I see the document checklist
And I see completion percentage progress bar
And I see the document list
And I see Upload button
And I see Download All button
```

**Technical Notes:**
- Create `app/(dashboard)/documents/page.tsx`
- Integrate DocumentChecklist, DocumentList, DocumentUpload
- Property selector for filtering

**Dependencies:** 5-3, 5-4, 5-5, 5-6

**Estimated Effort:** M (2-3 hours)

---

#### Story 5-8: Link to Existing /mnt/storage Files

**As a** user
**I want** to link existing files from /mnt/storage
**So that** I don't need to re-upload everything

**Acceptance Criteria:**

```gherkin
Given I'm adding a document
When I choose "Link Existing File"
Then I can browse /mnt/storage/CFR Investments/
And I can select a file to link
And the link is saved with the document record
And the file opens when clicked
```

**Technical Notes:**
- Add "link" option to upload component
- Server-side file browsing API
- Store file path in document record

**Dependencies:** 5-3, 5-6

**Estimated Effort:** M (2-3 hours)

---

## Epic 6: Tenant Management

**Goal:** Track tenants, payments, and lease information
**Value:** Replace scattered tenant data with organized system
**Dependencies:** Epic 1, Epic 4

### Stories

#### Story 6-1: Build Tenant List Component

**As a** user
**I want** to see all tenants in a list
**So that** I can manage my renters

**Acceptance Criteria:**

```gherkin
Given tenants exist in PocketBase
When the tenant list renders
Then I see a table with: property, tenant name, rent, status, arrears
And I can sort by any column
And status badges show: Current, Late, In Eviction
And arrears show in red if > $0
And clicking a tenant opens detail view
```

**Technical Notes:**
- Create `components/tenants/tenant-list.tsx`
- Use existing tenants collection
- Calculate arrears from rentArrears collection

**Dependencies:** 1-5

**Estimated Effort:** M (2-3 hours)

---

#### Story 6-2: Build Tenant Detail Card Component

**As a** user
**I want** to see complete tenant information
**So that** I can manage the tenant relationship

**Acceptance Criteria:**

```gherkin
Given a tenant
When the detail card renders
Then I see: name, phone, email, emergency contact
And I see: lease start, lease end, monthly rent, security deposit
And I see lease status indicator (active/expiring/expired)
And I see payment history summary
And I see current arrears amount
```

**Technical Notes:**
- Create `components/tenants/tenant-detail-card.tsx`
- Pull from tenants collection
- Calculate days until lease expiration

**Dependencies:** 6-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 6-3: Build Payment Recording Form

**As a** user
**I want** to record rent payments
**So that** payment history is tracked

**Acceptance Criteria:**

```gherkin
Given I'm viewing a tenant
When I click "Record Payment"
Then I see a form with: amount, date, type (rent/late fee/deposit)
And I can enter partial payments
And payment is saved to rentHistory
And arrears are recalculated
And success toast shows
```

**Technical Notes:**
- Create `components/tenants/payment-form.tsx`
- Save to rentHistory collection
- Trigger arrears recalculation

**Dependencies:** 6-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 6-4: Build Arrears Calculator

**As a** developer
**I want** a function to calculate rent arrears
**So that** outstanding balances are always current

**Acceptance Criteria:**

```gherkin
Given a tenant with rent and payment history
When I call calculateArrears(tenantId)
Then it returns: total owed, breakdown by month, days overdue
And it handles partial payments
And it updates rentArrears collection
```

**Technical Notes:**
- Create `lib/calculations/arrears.ts`
- Compare expected rent vs actual payments
- Store result in rentArrears collection

**Dependencies:** 1-5

**Estimated Effort:** M (2-3 hours)

---

#### Story 6-5: Build Florida Eviction Timeline Component

**As a** user
**I want** to see eviction progress for problem tenants
**So that** I can track the legal process

**Acceptance Criteria:**

```gherkin
Given a tenant in eviction
When the eviction timeline renders
Then I see Florida eviction stages: 3-Day Notice → Filed → Hearing → Writ → Lockout
And each stage shows completion date or pending status
And completed stages show green checkmark
And pending stages show amber indicator
And current stage is highlighted
```

**Technical Notes:**
- Create `components/tenants/eviction-timeline.tsx`
- Florida eviction stages from PRD
- Track in tenant's evictionStage field

**Dependencies:** 6-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 6-6: Build Maintenance Request Form

**As a** user
**I want** to log maintenance requests
**So that** issues are tracked

**Acceptance Criteria:**

```gherkin
Given I'm viewing a property or tenant
When I click "New Maintenance Request"
Then I see a form with: description, priority (low/medium/high/emergency), photos
And I can upload photos
And request is saved to maintenanceRequests collection
And it links to property and optionally tenant
```

**Technical Notes:**
- Create `components/tenants/maintenance-form.tsx`
- Create maintenanceRequests collection if needed
- Allow photo uploads

**Dependencies:** 5-3

**Estimated Effort:** M (2-3 hours)

---

#### Story 6-7: Build Rent Roll Report Component

**As a** user
**I want** to see a rent roll for all properties
**So that** I can share with lenders

**Acceptance Criteria:**

```gherkin
Given multiple properties with tenants
When I view the rent roll
Then I see: property address, tenant, lease dates, monthly rent, status
And I see total monthly rent at bottom
And I can export to PDF
And report is formatted professionally
```

**Technical Notes:**
- Create `components/tenants/rent-roll.tsx`
- Aggregate all tenant data
- PDF export integration

**Dependencies:** 6-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 6-8: Create Tenant API Routes

**As a** developer
**I want** API routes for tenant operations
**So that** tenant data can be managed

**Acceptance Criteria:**

```gherkin
Given the tenant API routes
When I GET /api/tenants
Then all tenants are returned with property info

When I POST /api/tenants/[id]/payment
Then payment is recorded and arrears updated

When I PUT /api/tenants/[id]
Then tenant info is updated
```

**Technical Notes:**
- Create `app/api/tenants/route.ts`
- Create `app/api/tenants/[id]/route.ts`
- Create `app/api/tenants/[id]/payment/route.ts`

**Dependencies:** 1-4

**Estimated Effort:** M (2-3 hours)

---

#### Story 6-9: Build Tenants Page

**As a** user
**I want** a dedicated page for tenant management
**So that** I can manage all renters

**Acceptance Criteria:**

```gherkin
Given I navigate to /tenants
When the page loads
Then I see the tenant list (rent roll)
And I see total monthly rent
And I see total arrears
And I can expand a tenant to see detail
And I see +Add Tenant button
And I can view maintenance requests
```

**Technical Notes:**
- Create `app/(dashboard)/tenants/page.tsx`
- Integrate TenantList, RentRoll components
- Expandable rows for detail view

**Dependencies:** 6-1, 6-2, 6-7, 6-8

**Estimated Effort:** M (2-3 hours)

---

## Epic 7: Skip Tracing

**Goal:** Find property owner contact information
**Value:** Enable outreach for off-market deals
**Dependencies:** Epic 1, Epic 4

### Stories

#### Story 7-1: Create Skip Trace Types

**As a** developer
**I want** TypeScript types for skip trace data
**So that** skip tracing has type safety

**Acceptance Criteria:**

```gherkin
Given the need for skip trace types
When types are created
Then SkipTrace, ContactAttempt, BatchJob interfaces exist
```

**Technical Notes:**
- Create `types/skiptrace.ts`
- Include: ownerName, mailingAddress, phones, emails, isEntity, contactAttempts

**Dependencies:** 1-5

**Estimated Effort:** S (1-2 hours)

---

#### Story 7-2: Create Skip Traces Collection

**As a** developer
**I want** a skipTraces collection in PocketBase
**So that** results can be saved

**Acceptance Criteria:**

```gherkin
Given PocketBase admin access
When the skipTraces collection is created
Then it has: propertyId, searchAddress, ownerName, mailingAddress
And it has: phones (array), emails (array)
And it has: isCorporate, phoneConfidence, emailConfidence
And it has: contactAttempts (JSON), searchedAt
```

**Technical Notes:**
- Create collection via PocketBase admin
- Arrays for phones/emails
- JSON field for contact log

**Dependencies:** 1-4

**Estimated Effort:** S (1-2 hours)

---

#### Story 7-3: Build Skip Trace Result Display

**As a** user
**I want** to see owner contact information clearly
**So that** I can reach out to owners

**Acceptance Criteria:**

```gherkin
Given skip trace results
When the result display renders
Then I see owner name and entity status
And I see phones with copy and call buttons
And I see emails with copy and email buttons
And I see mailing address with copy button
And confidence scores show for each data point
```

**Technical Notes:**
- Create `components/skiptrace/skip-trace-result.tsx`
- Action buttons for each contact method
- Copy to clipboard functionality

**Dependencies:** 7-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 7-4: Build Contact Log Component

**As a** user
**I want** to log contact attempts
**So that** I track outreach history

**Acceptance Criteria:**

```gherkin
Given skip trace results
When I click "Log Attempt"
Then I can enter: date, method (phone/email/mail), result, notes
And the log is saved to contactAttempts
And I see history of all attempts below
```

**Technical Notes:**
- Create `components/skiptrace/contact-log.tsx`
- Form for logging attempts
- Timeline display of history

**Dependencies:** 7-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 7-5: Build Batch Skip Trace Upload

**As a** user
**I want** to upload a CSV for batch skip tracing
**So that** I can process many addresses at once

**Acceptance Criteria:**

```gherkin
Given I'm on the Skip Trace page
When I click "Batch Upload"
Then I can upload a CSV with addresses
And the system processes each address
And I see progress indicator
And I can download results as CSV when complete
```

**Technical Notes:**
- Create `components/skiptrace/batch-upload.tsx`
- CSV parsing on server
- Async processing with status updates

**Dependencies:** 7-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 7-6: Create Skip Trace API Routes

**As a** developer
**I want** API routes for skip tracing
**So that** owner lookup works

**Acceptance Criteria:**

```gherkin
Given the skip trace API routes
When I POST /api/skiptrace with address
Then owner info is returned if found

When I POST /api/skiptrace/batch with CSV
Then job is started and jobId returned

When I GET /api/skiptrace/batch/[jobId]
Then job status and results are returned
```

**Technical Notes:**
- Create `app/api/skiptrace/route.ts`
- Create `app/api/skiptrace/batch/route.ts`
- For MVP, use county data as source (owner name from property records)

**Dependencies:** 4-2, 4-3, 7-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 7-7: Build Skip Trace Page

**As a** user
**I want** a dedicated page for skip tracing
**So that** I can find owner contacts

**Acceptance Criteria:**

```gherkin
Given I navigate to /skiptrace
When the page loads
Then I see a search bar for address entry
And after search, I see the skip trace result
And I see the contact log
And I see Batch Upload option
And I see quick links to Research Property and Analyze Deal
```

**Technical Notes:**
- Create `app/(dashboard)/skiptrace/page.tsx`
- Integrate all skip trace components
- Link to other modules

**Dependencies:** 7-3, 7-4, 7-5, 7-6

**Estimated Effort:** M (2-3 hours)

---

## Epic 8: Title Search

**Goal:** Research deed history, liens, and encumbrances
**Value:** Understand title issues before purchase
**Dependencies:** Epic 1, Epic 4

### Stories

#### Story 8-1: Create Title Search Types

**As a** developer
**I want** TypeScript types for title data
**So that** title search has type safety

**Acceptance Criteria:**

```gherkin
Given the need for title types
When types are created
Then TitleSearch, ChainOfTitle, Lien, Judgment interfaces exist
```

**Technical Notes:**
- Create `types/title.ts`
- Include: grantor, grantee, date, instrumentType for deeds
- Include: type, amount, holder, date for liens

**Dependencies:** 1-5

**Estimated Effort:** S (1-2 hours)

---

#### Story 8-2: Create Title Searches Collection

**As a** developer
**I want** a titleSearches collection in PocketBase
**So that** results can be saved

**Acceptance Criteria:**

```gherkin
Given PocketBase admin access
When the titleSearches collection is created
Then it has: propertyId, parcelId, chainOfTitle (JSON)
And it has: liens (JSON), judgments (JSON), mortgages (JSON)
And it has: lisPendens (boolean), searchedAt, dataSource
```

**Technical Notes:**
- Create collection via PocketBase admin
- JSON fields for arrays of records

**Dependencies:** 1-4

**Estimated Effort:** S (1-2 hours)

---

#### Story 8-3: Build Chain of Title Timeline Component

**As a** user
**I want** to see ownership history as a timeline
**So that** I understand the property's past

**Acceptance Criteria:**

```gherkin
Given title search results
When the timeline renders
Then I see a vertical timeline with deed records
And each record shows: date, grantor, grantee, instrument type
And liens/judgments show in red
And lis pendens shows prominently flagged
And I can expand for full detail
```

**Technical Notes:**
- Create `components/title/chain-of-title.tsx`
- Use timeline visualization
- Color-code by record type

**Dependencies:** 8-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 8-4: Build Liens & Judgments Table Component

**As a** user
**I want** to see all liens and judgments
**So that** I understand encumbrances

**Acceptance Criteria:**

```gherkin
Given title search results
When the liens table renders
Then I see a table with: type, holder, amount, date, status
And active liens show in red
And satisfied liens show with strikethrough
And I can sort by amount or date
And total encumbrance shows at bottom
```

**Technical Notes:**
- Create `components/title/liens-table.tsx`
- Aggregate amounts
- Show status clearly

**Dependencies:** 8-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 8-5: Create Title Search API Route

**As a** developer
**I want** an API route for title search
**So that** clerk data can be fetched

**Acceptance Criteria:**

```gherkin
Given a GET request to /api/title/search with address
When the endpoint is called
Then it searches county clerk records
And it returns chain of title, liens, judgments
And it flags lis pendens if present
And results are saved to titleSearches
```

**Technical Notes:**
- Create `app/api/title/search/route.ts`
- For MVP, may need to be manual entry or mock
- Future: build Polk/Hillsborough clerk scrapers

**Dependencies:** 8-2

**Estimated Effort:** M (2-3 hours)

---

#### Story 8-6: Build Title Search Page

**As a** user
**I want** a dedicated page for title search
**So that** I can research property history

**Acceptance Criteria:**

```gherkin
Given I navigate to /title
When the page loads
Then I see a search bar for address/owner
And after search, I see the chain of title timeline
And I see liens and judgments table
And I see summary of active encumbrances
And I can export results as PDF
And I can save to deal analysis
```

**Technical Notes:**
- Create `app/(dashboard)/title/page.tsx`
- Integrate ChainOfTitle, LiensTable components
- Export functionality

**Dependencies:** 8-3, 8-4, 8-5

**Estimated Effort:** M (2-3 hours)

---

## Epic 9: Lender Package Generator

**Goal:** Auto-compile lender packages with PDF export
**Value:** Reduce refinance package creation from hours to minutes
**Dependencies:** Epic 1, Epic 2, Epic 3, Epic 5

### Stories

#### Story 9-1: Install React-PDF and Create Base Template

**As a** developer
**I want** React-PDF configured with base templates
**So that** PDF generation works

**Acceptance Criteria:**

```gherkin
Given React-PDF is installed
When I create a PDF document
Then it renders with proper formatting
And it includes the CFR logo/branding
And it uses consistent typography
And it generates in under 30 seconds
```

**Technical Notes:**
- Install @react-pdf/renderer
- Create `lib/pdf/base-template.tsx`
- Define styles and layout

**Dependencies:** 1-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 9-2: Build Property Summary PDF Component

**As a** developer
**I want** a PDF component for property summary
**So that** lender packages include property overview

**Acceptance Criteria:**

```gherkin
Given a property
When the PropertySummary PDF renders
Then it shows: address, purchase price, rehab cost, total basis
And it shows: ARV, monthly rent, DSCR
And it shows: property photo (if available)
And layout is professional and lender-ready
```

**Technical Notes:**
- Create `lib/pdf/property-summary.tsx`
- Use React-PDF primitives
- Include photo if available

**Dependencies:** 9-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 9-3: Build Comp Report PDF Component

**As a** developer
**I want** a PDF component for comp report
**So that** lender packages include comparable analysis

**Acceptance Criteria:**

```gherkin
Given a comp report
When the CompReport PDF renders
Then it shows subject property
And it shows 3-5 comps with details
And it shows adjustments and ARV estimate
And it includes Gemini analysis summary
And layout is professional
```

**Technical Notes:**
- Create `lib/pdf/comp-report.tsx`
- Table layout for comps
- Include AI analysis text

**Dependencies:** 9-1, 3-10

**Estimated Effort:** M (2-3 hours)

---

#### Story 9-4: Build Rent Roll PDF Component

**As a** developer
**I want** a PDF component for rent roll
**So that** lender packages include income documentation

**Acceptance Criteria:**

```gherkin
Given tenants data
When the RentRoll PDF renders
Then it shows all properties with tenants
And it shows: tenant name, lease dates, monthly rent
And it shows total monthly and annual rent
And it matches standard rent roll format
```

**Technical Notes:**
- Create `lib/pdf/rent-roll.tsx`
- Standard rent roll layout
- Aggregate totals

**Dependencies:** 9-1, 6-7

**Estimated Effort:** M (2-3 hours)

---

#### Story 9-5: Build Operating Statement PDF Component

**As a** developer
**I want** a PDF component for P&L statement
**So that** lender packages include financials

**Acceptance Criteria:**

```erkin
Given property financial data
When the OperatingStatement PDF renders
Then it shows income: rent, other income
And it shows expenses: taxes, insurance, maintenance, management
And it shows NOI and cash flow
And it covers 12-month period
```

**Technical Notes:**
- Create `lib/pdf/operating-statement.tsx`
- Use propertyLedger data
- Standard P&L format

**Dependencies:** 9-1

**Estimated Effort:** M (2-3 hours)

---

#### Story 9-6: Build DSCR Calculation PDF Component

**As a** developer
**I want** a PDF component showing DSCR calculation
**So that** lenders see the qualification metric

**Acceptance Criteria:**

```gherkin
Given deal analysis data
When the DSCR PDF component renders
Then it shows the DSCR formula
And it shows rent and PITI breakdown
And it highlights DSCR >= 1.25 with green
And it includes rate and LTV assumptions
```

**Technical Notes:**
- Create `lib/pdf/dscr-calculation.tsx`
- Clear formula visualization
- Color coding for results

**Dependencies:** 9-1, 2-3

**Estimated Effort:** S (1-2 hours)

---

#### Story 9-7: Build Package Configuration Component

**As a** user
**I want** to select which documents to include
**So that** I can customize lender packages

**Acceptance Criteria:**

```gherkin
Given I'm generating a lender package
When the configuration modal shows
Then I see checkboxes for: property summary, comp report, rent roll, P&L, DSCR, photos
And I see property selector dropdown
And I see preview of package
And I can generate or cancel
```

**Technical Notes:**
- Create `components/lender-package/package-config.tsx`
- Checkbox selection
- Live preview

**Dependencies:** 5-5

**Estimated Effort:** M (2-3 hours)

---

#### Story 9-8: Build Lender Package Page

**As a** user
**I want** a page to generate lender packages
**So that** I can create refi documentation

**Acceptance Criteria:**

```gherkin
Given I navigate to /lender-package
When the page loads
Then I see property selector
And I see package configuration options
And I see preview panel
And I see Generate PDF button
And after generation, I can download or save to vault
And generation completes in under 30 seconds
```

**Technical Notes:**
- Create `app/(dashboard)/lender-package/page.tsx`
- Integrate all PDF components
- Stream PDF response

**Dependencies:** 9-2, 9-3, 9-4, 9-5, 9-6, 9-7

**Estimated Effort:** M (2-3 hours)

---

## Story Dependency Graph

```
Epic 1 (Foundation):
1-1 → 1-2 → 1-3 → 1-6 → 1-11 → 1-12
1-1 → 1-4 → 1-5 → 1-10 → 1-11
1-3 → 1-7 → 1-11
1-3 → 1-8 → 1-9 → 1-11

Epic 2 (Deal Analyzer):
1-5 → 2-1 → 2-2 → 2-5 → 2-6 → 2-8 → 2-11
1-4 → 2-9 → 2-10 → 2-11
1-3 → 2-4 → 2-8

Epic 3 (Comp Search):
1-5 → 3-1 → 3-3 → 3-4 → 3-5 → 3-6 → 3-10
1-1 → 3-2 → 3-6 → 3-8 → 3-10
3-1 → 3-7 → 3-10
1-4 → 3-9

Epic 4 (Property Research):
1-5 → 4-1 → 4-2 → 4-6 → 4-8
4-1 → 4-3 → 4-6
4-1 → 4-4 → 4-6
1-3 → 4-5 → 4-8
1-4 → 4-7
4-2 → 4-9

Epic 5 (Document Vault):
1-5 → 5-1 → 5-3 → 5-7
1-4 → 5-2 → 5-4 → 5-7
5-2 → 5-5 → 5-7
5-2 → 5-6 → 5-7
5-3 → 5-8

Epic 6 (Tenant Management):
1-5 → 6-1 → 6-2 → 6-3
1-5 → 6-4
6-2 → 6-5
5-3 → 6-6
6-1 → 6-7
1-4 → 6-8 → 6-9

Epic 7 (Skip Tracing):
1-5 → 7-1 → 7-3
1-4 → 7-2 → 7-4 → 7-7
7-2 → 7-5 → 7-7
4-2 → 7-6 → 7-7

Epic 8 (Title Search):
1-5 → 8-1 → 8-3 → 8-6
8-1 → 8-4 → 8-6
1-4 → 8-2 → 8-5 → 8-6

Epic 9 (Lender Package):
1-1 → 9-1 → 9-2 → 9-8
9-1 → 9-3 → 9-8
9-1 → 9-4 → 9-8
9-1 → 9-5 → 9-8
9-1 → 9-6 → 9-8
5-5 → 9-7 → 9-8
```

---

## Requirement Traceability

| Story | Requirements |
|-------|--------------|
| 1-11 | FR-DASH-001, FR-DASH-002, FR-DASH-003, FR-DASH-004, NFR-PERF-001 |
| 1-9 | FR-DASH-006, FR-DASH-007, FR-DASH-008, FR-DASH-010 |
| 2-2 | FR-DEAL-002, FR-DEAL-003, FR-DEAL-007, NFR-PERF-007 |
| 2-3 | FR-DEAL-004 |
| 2-5 | FR-DEAL-005, FR-DEAL-006 |
| 2-6 | FR-DEAL-012 |
| 2-8 | FR-DEAL-001 |
| 2-10 | FR-DEAL-008 |
| 3-6 | FR-COMP-002, FR-COMP-004 |
| 3-4 | FR-COMP-003, FR-COMP-006 |
| 3-5 | FR-COMP-005, FR-COMP-007 |
| 4-2 | FR-PROP-001, NFR-PERF-002 |
| 4-3 | FR-PROP-002 |
| 4-5 | FR-PROP-003, FR-PROP-004 |
| 5-3 | FR-VAULT-002, FR-VAULT-003 |
| 5-4 | FR-VAULT-004, FR-VAULT-005 |
| 6-3 | FR-TENANT-005 |
| 6-4 | FR-TENANT-006 |
| 6-5 | FR-TENANT-007 |
| 7-3 | FR-SKIP-002 |
| 8-3 | FR-TITLE-002 |
| 8-4 | FR-TITLE-003, FR-TITLE-004 |
| 9-8 | FR-PKG-001, FR-PKG-006 |

---

## Notes

1. **Story Sizing:** All stories estimated at S (1-2 hours), M (2-3 hours), or L (3-4 hours) to keep work granular and trackable.

2. **MVP Scope:** Epics 1-5 constitute the MVP (Must-Have). Epics 6-9 are Should-Have and can follow after core functionality is validated.

3. **Technical Debt:** Each epic includes foundational stories (types, collections, API routes) before UI stories to ensure solid architecture.

4. **Integration Points:** Many stories reference dependencies across epics. Plan sprint work to respect these dependencies.

5. **Testing:** Unit tests should be written alongside stories for calculation-heavy features (2-2, 2-3, 6-4).


Now I have all the context I need. I need to spawn a Claude Code agent to build all remaining epics (2-9). Let me create a comprehensive prompt that includes:

1. What's already done (Epic 1)
2. What needs to be built (Epics 2-9, 68 stories)
3. The technical stack and architecture
4. The project location

I'll use background mode with Claude Code to work through everything, and set up the auto-notify so I get pinged when it's done.

The project is at /home/crogers2287/cfr-command-center and it's already running on port 3007.

68 stories across 8 epics. MASSIVE ACTION time. Deploying Claude Code to BUILD IT ALL:

exec

claude --version 2>/dev/null || which claude


2.0.57 (Claude Code)