# Venture Fund CRM — Project Context for Claude You are continuing development on a self-hosted CRM system for a venture fund. Below is everything you need to know about what has been built, how it works, and what comes next. --- ## Business Context - **Fund:** ~$200M AUM, currently fundraising for Fund II - **Users:** Team of 5 people, accessing via browser on local network or remotely via Tailscale VPN - **Current LPs:** 150 investors - **Prospects:** 250+ being tracked - **Migrating from:** Airtable (CSV exports available) - **Core goals:** 1. Eliminate sensitive LP/prospect data from third-party servers (Airtable, CRMs) 2. Stop paying monthly subscription costs 3. Purpose-built tool for fundraising workflow: managing existing investors, tracking new prospects, raising capital - **User:** Grant (grant@ten31.xyz) --- ## What Has Been Built (Sprint 1 — Complete) A fully functional prototype with backend API, frontend UI, demo data, and utility scripts. Everything runs locally with zero external dependencies beyond two Python packages. ### Tech Stack (Actual — differs from original plan) The original plan called for FastAPI + SQLAlchemy + separate React build, but the build environment lacked pip/npm access. The stack was adapted to: - **Backend:** Python 3 stdlib HTTP server + `sqlite3` + `bcrypt` + `PyJWT` — single file, no framework - **Database:** SQLite with WAL mode (concurrent reads, serialized writes — fine for 5 users) - **Frontend:** Single self-contained HTML file loading React 18 + Babel from CDN (unpkg) - **Deployment:** Run `python3 backend/server.py` — serves both API and frontend on port 8080 - **Remote access:** Tailscale mesh VPN (each device gets a private IP, peer-to-peer encrypted) ### Project Structure ``` venture-crm/ ├── backend/ │ ├── server.py # Complete API server (1,873 lines) │ └── requirements.txt # bcrypt, PyJWT (for reference) ├── frontend/ │ └── index.html # Complete React SPA (2,982 lines) ├── data/ │ └── crm.db # SQLite database (created on first run) ├── scripts/ │ ├── create_user.py # CLI tool to add users │ ├── reset_password.py # CLI tool to reset passwords │ └── backup.sh # Database backup with 30-day retention └── start.sh # Launch script ``` ### Database Schema All tables use TEXT primary keys (8-char UUIDs). The database is at `data/crm.db`. **Tables:** - `users` — id, username, email, password_hash, full_name, role (admin/manager/member), is_active - `contacts` — id, first_name, last_name, email, phone, mobile, title, organization_id (FK), contact_type (investor/prospect/advisor/other), status, source, tags (JSON), notes, linkedin_url, preferred_contact, created_by (FK) - `organizations` — id, name, type, industry, website, phone, email, address, city, state, country, description, tags (JSON), created_by (FK) - `opportunities` — id, name, contact_id (FK), organization_id (FK), stage (lead/outreach/meeting/due_diligence/committed/funded), commitment_amount, expected_amount, probability, expected_close_date, fund_name, description, next_step, owner_id (FK), priority (low/medium/high), lost_reason - `communications` — id, contact_id (FK), opportunity_id (FK), type (email/call/meeting/note/text), subject, body, communication_date, duration_minutes, outcome, next_action, next_action_date, attendees (JSON), created_by (FK) - `lp_profiles` — id, contact_id (FK, unique), commitment_amount, funded_amount, commitment_date, fund_name, investor_type, accredited, legal_docs_signed, signed_date, wire_received, wire_date, k1_sent, preferred_communication, notes - `custom_fields` — id, name, entity_type, field_type, options (JSON), required, display_order - `custom_field_values` — id, custom_field_id (FK), entity_id, entity_type, value - `audit_log` — id, user_id (FK), entity_type, entity_id, action, changes (JSON), created_at - `tags` — id, name (unique), color **Key indexes:** contacts(contact_type, status, organization_id), opportunities(stage, owner_id, contact_id), communications(contact_id, communication_date), audit_log(entity_type, entity_id), lp_profiles(contact_id) ### API Endpoints All endpoints except auth require `Authorization: Bearer ` header. Server runs at `http://0.0.0.0:8080`. **Auth:** - `POST /api/auth/login` — body: {username, password} → {token, user} - `POST /api/auth/register` — body: {username, password, email, full_name} → {token, user} **Contacts:** - `GET /api/contacts?type=&status=&search=&sort=&order=&limit=&offset=&organization_id=&tag=` → {data[], total, limit, offset} - `GET /api/contacts/:id` → {data: {contact + communications[], opportunities[], lp_profile}} - `POST /api/contacts` — full CRUD - `PUT /api/contacts/:id` - `DELETE /api/contacts/:id` **Organizations:** - `GET /api/organizations?search=&type=&limit=&offset=` → {data[], total} - `GET /api/organizations/:id` → {data: {org + contacts[], opportunities[]}} - `POST /api/organizations` — full CRUD - `PUT /api/organizations/:id` - `DELETE /api/organizations/:id` **Opportunities (Pipeline):** - `GET /api/opportunities?stage=&owner_id=&search=&priority=&fund_name=&limit=&offset=` → {data[], total} - `GET /api/opportunities/:id` → {data: {opp + communications[], stage_history[]}} - `POST /api/opportunities` - `PUT /api/opportunities/:id` - `PATCH /api/opportunities/:id/stage` — body: {stage} (logs stage change in audit) - `DELETE /api/opportunities/:id` **Communications:** - `GET /api/communications?contact_id=&type=&search=&limit=&offset=` → {data[], total} - `GET /api/contacts/:id/communications` → same as above, scoped to contact - `POST /api/communications` - `PUT /api/communications/:id` - `DELETE /api/communications/:id` **LP Profiles:** - `GET /api/lp-profiles?fund_name=&search=` → {data[], total} - `GET /api/lp-profiles/:id` → {data} - `POST /api/lp-profiles` — also sets contact type to 'investor' - `PUT /api/lp-profiles/:id` **Reports:** - `GET /api/reports/dashboard` → {metrics, pipeline_stages[], recent_communications[], upcoming_actions[], recent_stage_changes[]} - `GET /api/reports/pipeline` → {by_stage[], by_owner[], by_priority[]} - `GET /api/reports/lp-breakdown` → {lps[], summary, by_type[]} - `GET /api/reports/activity?days=30` → {by_user[], by_day[]} **Import/Export:** - `POST /api/import/csv` — body: {data: [...objects], entity_type, mapping: {csv_col: crm_field}, dry_run: bool} - `GET /api/export/contacts` → {data[]} **Other:** - `GET /api/tags` / `POST /api/tags` - `GET /api/users` - `GET /api/audit-log?entity_type=&entity_id=` - `GET /api/health` ### Frontend Pages The frontend is a single HTML file with inline CSS (dark theme) and React via CDN. Pages: 1. **Login** — username/password form, registration option 2. **Dashboard** — KPI cards (Total LPs, Committed $, Pipeline Value, Active Opportunities, Prospects, Monthly Comms), pipeline stage visualization, recent communications, upcoming actions, recent stage changes 3. **Contacts** — tabbed (All/Investors/Prospects), searchable sortable table, slide-over detail panel with communications timeline and opportunities, add/edit modal 4. **Pipeline** — Kanban-style board (Lead → Outreach → Meeting → DD → Committed → Funded), stage summary bar with $ per stage, opportunity cards with stage selector, add/edit modal 5. **Communications** — chronological list, filter by type/contact, log new communication form 6. **LP Tracker** — summary cards (Total Committed, Funded, Avg Check, LP Count), table with status indicators (checkmarks) for docs/wire/K1 7. **Import** — CSV paste/upload, preview table, field mapping interface, dry-run validation, execute import 8. **Settings** — user profile, tag management ### Demo Data (Seeded Automatically) On first run, the server seeds: - 2 users: `admin`/`admin123` (admin role), `grant`/`password` (admin role) - 8 organizations (Sovereign Wealth Holdings, Pacific Capital Partners, Northeast Pension Fund, Redwood Endowment, Atlas Family Office, Summit Insurance Group, Cascade Wealth Management, Blue Harbor Foundation) - 12 contacts (6 investors, 6 prospects) - 6 LP profiles totaling $83M committed (all Fund I, all fully funded) - 6 pipeline opportunities totaling $40M expected (Fund II prospects at various stages) - 8 communication records (emails, calls, meetings) - 6 tags (High Priority, Fund I LP, Fund II Prospect, Family Office, Institutional, Re-up Target) ### How to Run ```bash pip3 install bcrypt PyJWT cd venture-crm python3 backend/server.py # Open http://localhost:8080 # Login: grant / password ``` ### What Has Been Tested All API endpoints have been verified via curl: - Auth (login, register) - Contact CRUD + search - Organization CRUD - Opportunity CRUD + stage changes - Communication CRUD - LP profile CRUD - Dashboard, pipeline, LP breakdown reports - CSV import with dry-run and field mapping - Frontend serves correctly from the backend --- ## What Has NOT Been Built Yet (Remaining Sprints) ### Sprint 2 items still needed: - Custom fields UI (backend schema exists but not wired to frontend forms) - Drag-and-drop on pipeline board (currently uses dropdown stage selector) ### Sprint 3: Airtable Migration + Custom Fields - Custom field definition admin UI - Display custom fields on contact/opportunity forms - Actual Airtable data migration (import wizard exists but hasn't been used with real data) ### Sprint 4: Reporting + Polish - Pipeline analytics (deal velocity, conversion rates between stages) - User activity report page - CSV export buttons on all reports - Bulk actions on contact list (tag multiple, assign, bulk export) - Automated daily backup via cron - Team setup documentation ### Future Enhancements (discussed but not planned): - Email integration (auto-log emails via IMAP) - Calendar sync - Task assignments linked to opportunities - Bulk email with templates - Two-factor authentication - Advanced saved search filters - Audit trail UI page --- ## Architecture Decisions & Constraints 1. **Single-file backend:** The Python server is one file (`server.py`) using stdlib `http.server`. No framework. This keeps deployment dead simple but means no middleware pattern, no auto-docs, no async. If the codebase grows significantly, consider migrating to FastAPI. 2. **Single-file frontend:** The React app is one HTML file loading from CDN. No build step. This means no TypeScript, no tree-shaking, no code splitting. Babel compiles JSX in the browser. If the UI grows significantly, consider splitting into a proper Vite/React project. 3. **SQLite WAL mode:** Handles 5 concurrent readers + 1 writer. Fine for this team size. If the team grows past 10-15, migrate to PostgreSQL. 4. **No localStorage:** JWT token stored in React state only (memory). Page refresh = re-login. This is intentional for security. 5. **8-char UUIDs:** Generated via `uuid.uuid4()[:8]`. Collision probability is negligible at this data scale. 6. **Tailscale for remote access:** Server binds to 0.0.0.0. Tailscale gives each device a 100.x.x.x IP. No port forwarding, no public exposure. --- ## Key Files to Read When making changes, these are the files: - **`backend/server.py`** (1,873 lines) — ALL backend logic: database schema, auth, every API endpoint, seed data, server startup. Search for handler method names like `handle_list_contacts`, `handle_create_opportunity`, etc. - **`frontend/index.html`** (2,982 lines) — ALL frontend logic: CSS styles, React components, API client, every page. Search for component names like `Dashboard`, `ContactsPage`, `PipelinePage`, etc. - **`scripts/create_user.py`** — CLI to add team members - **`scripts/backup.sh`** — Database backup with rotation - **`start.sh`** — Launch script --- ## Common Modification Patterns **Adding a new field to contacts:** 1. In `server.py`: add column to CREATE TABLE, add to INSERT/UPDATE in handler methods 2. In `index.html`: add field to the contact form component and detail view 3. Delete `data/crm.db` to recreate schema (or use sqlite3 ALTER TABLE) **Adding a new pipeline stage:** 1. In `server.py`: add to `PIPELINE_STAGES` list 2. In `index.html`: add to the stages array in the Pipeline component **Changing the color scheme:** 1. In `index.html`: modify the CSS variables in the `