Files
ten31-database/venture-crm-prompt.md
T
2026-02-27 12:44:50 -06:00

13 KiB

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 <jwt_token> 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

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 <style> tag (search for hex colors like #0f172a, #1e293b, #6366f1)

Adding a new API endpoint:

  1. In server.py: add route matching in do_GET/do_POST/etc., then add handler method

Adding a new page:

  1. In index.html: create a new component, add it to the navigation sidebar and the page router