259 lines
13 KiB
Markdown
259 lines
13 KiB
Markdown
# 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
|
|
|
|
```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 `<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
|