DAO -- Deal and Access Orchestrator

2026-04-16

DAO -- Deal and Access Orchestrator

Agent: api-architect

Domain: API Design, Data Modeling, Integration Architecture

Date: 2026-04-16


Memory Search Results


1. Overview

The DAO (Deal and Access Orchestrator) is a standalone web application that maps Rimah Harb's network across Pure Technology (PT) and MAKR Venture Fund, surfaces synergies between contacts, and tracks value creation.

Core problem: Rimah sits at the intersection of two entities with overlapping but distinct networks. Opportunities fall through cracks because no system connects "MAKR pipeline company needs X" with "PT contact has X." The DAO closes that gap.

Architecture:


2. D1 Database Schema

2.1 Core Tables

-- ============================================================
-- CONTACTS
-- Primary table: 16K+ records imported from LinkedIn CSV
-- ============================================================
CREATE TABLE contacts (
  id TEXT PRIMARY KEY,                    -- nanoid
  first_name TEXT NOT NULL,
  last_name TEXT NOT NULL,
  full_name TEXT GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED,
  email TEXT,
  phone TEXT,
  linkedin_url TEXT UNIQUE,
  headline TEXT,                          -- LinkedIn headline
  company TEXT,                           -- Current company name
  position TEXT,                          -- Current title
  location TEXT,
  sector_tag TEXT,                        -- From LinkedIn CSV (e.g., "fintech", "healthtech")
  seniority_tag TEXT,                     -- From LinkedIn CSV (e.g., "C-suite", "VP", "Director")
  source TEXT NOT NULL DEFAULT 'linkedin', -- 'linkedin', 'makr_crm', 'manual'
  entity_affinity TEXT NOT NULL DEFAULT 'both', -- 'pt', 'makr', 'both', 'external'
  connection_strength INTEGER DEFAULT 3,  -- 1 (weak) to 5 (strong)
  last_interaction_at TEXT,               -- ISO 8601
  notes TEXT,
  is_archived INTEGER NOT NULL DEFAULT 0,
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_contacts_sector ON contacts(sector_tag);
CREATE INDEX idx_contacts_seniority ON contacts(seniority_tag);
CREATE INDEX idx_contacts_entity ON contacts(entity_affinity);
CREATE INDEX idx_contacts_company ON contacts(company);
CREATE INDEX idx_contacts_strength ON contacts(connection_strength DESC);
CREATE INDEX idx_contacts_fullname ON contacts(full_name);

-- ============================================================
-- ENTITIES
-- Organizations: PT, MAKR, and external companies
-- ============================================================
CREATE TABLE entities (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  type TEXT NOT NULL,                     -- 'internal' (PT/MAKR), 'portfolio', 'prospect', 'partner', 'other'
  category TEXT,                          -- 'venture_fund', 'tech_company', 'corporate', 'startup', 'advisory'
  website TEXT,
  sector TEXT,
  stage TEXT,                             -- For startups: 'pre-seed', 'seed', 'series_a', etc.
  description TEXT,
  is_makr_pipeline INTEGER NOT NULL DEFAULT 0,
  is_pt_partner INTEGER NOT NULL DEFAULT 0,
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_entities_type ON entities(type);
CREATE INDEX idx_entities_sector ON entities(sector);
CREATE INDEX idx_entities_pipeline ON entities(is_makr_pipeline) WHERE is_makr_pipeline = 1;

-- ============================================================
-- RELATIONSHIPS
-- Contact-to-contact and contact-to-entity edges
-- ============================================================
CREATE TABLE relationships (
  id TEXT PRIMARY KEY,
  source_type TEXT NOT NULL,              -- 'contact' or 'entity'
  source_id TEXT NOT NULL,
  target_type TEXT NOT NULL,              -- 'contact' or 'entity'
  target_id TEXT NOT NULL,
  relation TEXT NOT NULL,                 -- 'knows', 'works_at', 'advises', 'invested_in', 'introduced_by', 'co_founder'
  strength INTEGER DEFAULT 3,             -- 1-5
  context TEXT,                           -- How they know each other
  via_entity TEXT,                        -- 'pt', 'makr', 'both', 'external'
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  UNIQUE(source_type, source_id, target_type, target_id, relation)
);

CREATE INDEX idx_rel_source ON relationships(source_type, source_id);
CREATE INDEX idx_rel_target ON relationships(target_type, target_id);
CREATE INDEX idx_rel_via ON relationships(via_entity);

-- ============================================================
-- TAGS
-- Flexible tagging: capabilities, needs, sectors, skills
-- ============================================================
CREATE TABLE tags (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL UNIQUE,
  category TEXT NOT NULL,                 -- 'capability', 'need', 'sector', 'skill', 'deal_type'
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_tags_category ON tags(category);

-- Junction table for polymorphic tagging
CREATE TABLE taggables (
  tag_id TEXT NOT NULL REFERENCES tags(id),
  taggable_type TEXT NOT NULL,            -- 'contact', 'entity', 'opportunity'
  taggable_id TEXT NOT NULL,
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  PRIMARY KEY (tag_id, taggable_type, taggable_id)
);

CREATE INDEX idx_taggables_target ON taggables(taggable_type, taggable_id);

-- ============================================================
-- OPPORTUNITIES
-- Deals, partnerships, intros, collaborations
-- ============================================================
CREATE TABLE opportunities (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL,
  type TEXT NOT NULL,                     -- 'deal', 'partnership', 'intro', 'advisory', 'investment', 'tech_integration'
  status TEXT NOT NULL DEFAULT 'identified', -- 'identified', 'qualifying', 'active', 'won', 'lost', 'on_hold'
  routing TEXT NOT NULL,                  -- 'pt', 'makr', 'both'
  priority TEXT NOT NULL DEFAULT 'medium',-- 'critical', 'high', 'medium', 'low'
  description TEXT,
  estimated_value_usd INTEGER,            -- Null if not quantifiable
  source_contact_id TEXT REFERENCES contacts(id),
  target_entity_id TEXT REFERENCES entities(id),
  synergy_id TEXT REFERENCES synergies(id), -- If born from synergy engine
  closed_at TEXT,
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_opp_status ON opportunities(status);
CREATE INDEX idx_opp_routing ON opportunities(routing);
CREATE INDEX idx_opp_type ON opportunities(type);
CREATE INDEX idx_opp_priority ON opportunities(priority);

-- Participants in an opportunity
CREATE TABLE opportunity_participants (
  opportunity_id TEXT NOT NULL REFERENCES opportunities(id) ON DELETE CASCADE,
  participant_type TEXT NOT NULL,          -- 'contact' or 'entity'
  participant_id TEXT NOT NULL,
  role TEXT NOT NULL,                      -- 'lead', 'counterparty', 'introducer', 'advisor', 'beneficiary'
  PRIMARY KEY (opportunity_id, participant_type, participant_id)
);

-- ============================================================
-- SYNERGIES
-- Engine output: detected matches between needs and capabilities
-- ============================================================
CREATE TABLE synergies (
  id TEXT PRIMARY KEY,
  type TEXT NOT NULL,                     -- 'capability_match', 'sector_overlap', 'warm_intro_path', 'portfolio_fit'
  score REAL NOT NULL,                    -- 0.0 to 1.0 confidence
  status TEXT NOT NULL DEFAULT 'surfaced', -- 'surfaced', 'reviewed', 'actioned', 'dismissed'
  title TEXT NOT NULL,                    -- Human-readable summary
  description TEXT,                       -- Detailed reasoning
  need_side_type TEXT NOT NULL,           -- 'contact' or 'entity'
  need_side_id TEXT NOT NULL,
  supply_side_type TEXT NOT NULL,         -- 'contact' or 'entity'
  supply_side_id TEXT NOT NULL,
  matching_tags TEXT,                     -- JSON array of tag IDs that matched
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
  reviewed_at TEXT
);

CREATE INDEX idx_synergy_score ON synergies(score DESC);
CREATE INDEX idx_synergy_status ON synergies(status);
CREATE INDEX idx_synergy_type ON synergies(type);

-- ============================================================
-- ACTIVITY LOG
-- Value creation events: intros made, deals closed, revenue
-- ============================================================
CREATE TABLE activities (
  id TEXT PRIMARY KEY,
  type TEXT NOT NULL,                     -- 'intro_made', 'meeting_held', 'deal_closed', 'partnership_formed',
                                          -- 'revenue_generated', 'tech_integrated', 'investment_made', 'note'
  title TEXT NOT NULL,
  description TEXT,
  value_usd INTEGER,                      -- Quantifiable value if applicable
  opportunity_id TEXT REFERENCES opportunities(id),
  actor_contact_id TEXT REFERENCES contacts(id),
  entity_context TEXT,                    -- 'pt', 'makr', 'both'
  metadata TEXT,                          -- JSON blob for type-specific data
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_activities_type ON activities(type);
CREATE INDEX idx_activities_opp ON activities(opportunity_id);
CREATE INDEX idx_activities_created ON activities(created_at DESC);

-- ============================================================
-- IMPORT TRACKING
-- Track CSV imports for deduplication and audit
-- ============================================================
CREATE TABLE imports (
  id TEXT PRIMARY KEY,
  source TEXT NOT NULL,                   -- 'linkedin_csv', 'makr_crm', 'manual_bulk'
  filename TEXT,
  total_rows INTEGER,
  imported INTEGER,
  skipped INTEGER,
  errors INTEGER,
  error_details TEXT,                     -- JSON array of row-level errors
  started_at TEXT NOT NULL DEFAULT (datetime('now')),
  completed_at TEXT
);

2.2 Seed Data

-- The two core entities
INSERT INTO entities (id, name, type, category, website)
VALUES
  ('ent_pt', 'Pure Technology', 'internal', 'tech_company', 'https://puretechnology.ai'),
  ('ent_makr', 'MAKR Venture Fund', 'internal', 'venture_fund', 'https://makrvf.com');

-- Core capability tags
INSERT INTO tags (id, name, category) VALUES
  ('tag_cap_capital', 'Capital', 'capability'),
  ('tag_cap_tech', 'Technology', 'capability'),
  ('tag_cap_distribution', 'Distribution', 'capability'),
  ('tag_cap_domain', 'Domain Expertise', 'capability'),
  ('tag_cap_talent', 'Talent Network', 'capability'),
  ('tag_cap_regulatory', 'Regulatory Access', 'capability'),
  ('tag_cap_market', 'Market Access', 'capability'),
  ('tag_cap_ip', 'IP/Patents', 'capability'),
  ('tag_need_capital', 'Needs Capital', 'need'),
  ('tag_need_tech', 'Needs Technology', 'need'),
  ('tag_need_distribution', 'Needs Distribution', 'need'),
  ('tag_need_domain', 'Needs Domain Expertise', 'need'),
  ('tag_need_talent', 'Needs Talent', 'need');

3. API Specification

Base URL: https://dao-api.<domain>.workers.dev (or custom domain)

Auth: X-API-Key header on all requests

Content-Type: application/json

CORS Origins: https://dao.pages.dev, http://localhost:3000

3.1 Common Response Envelope

All responses use a consistent envelope:

// Success
{
  "ok": true,
  "data": { ... },
  "meta": {
    "total": 16423,
    "page": 1,
    "per_page": 50
  }
}

// Error
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "sector_tag is required",
    "details": [...]
  }
}

3.2 Pagination

All list endpoints accept:

3.3 Endpoints


3.3.1 Contacts

GET /api/contacts -- List contacts with filtering

Query parameters:

ParamTypeDescription
qstringFull-text search on name, company, headline
sectorstringFilter by sector_tag
senioritystringFilter by seniority_tag
entitystringFilter by entity_affinity: pt, makr, both
companystringFilter by company name (partial match)
min_strengthintegerMinimum connection_strength (1-5)
tagstringFilter by tag name (repeatable for AND logic)
sourcestringlinkedin, makr_crm, manual
sortstringname, company, strength, last_interaction, created
orderstringasc or desc (default desc)
pageintegerPage number
per_pageintegerResults per page

Response 200:

{
  "ok": true,
  "data": [
    {
      "id": "ct_abc123",
      "first_name": "Jane",
      "last_name": "Smith",
      "full_name": "Jane Smith",
      "email": "jane@example.com",
      "linkedin_url": "https://linkedin.com/in/janesmith",
      "headline": "VP Engineering at Acme Corp",
      "company": "Acme Corp",
      "position": "VP Engineering",
      "location": "Dubai, UAE",
      "sector_tag": "fintech",
      "seniority_tag": "VP",
      "entity_affinity": "pt",
      "connection_strength": 4,
      "last_interaction_at": "2026-03-15T10:00:00Z",
      "tags": ["Technology", "Domain Expertise"],
      "created_at": "2026-04-16T00:00:00Z"
    }
  ],
  "meta": { "total": 16423, "page": 1, "per_page": 50 }
}

GET /api/contacts/:id -- Single contact with full detail

Response 200:

{
  "ok": true,
  "data": {
    "id": "ct_abc123",
    "first_name": "Jane",
    "last_name": "Smith",
    "full_name": "Jane Smith",
    "email": "jane@example.com",
    "phone": "+971-555-1234",
    "linkedin_url": "https://linkedin.com/in/janesmith",
    "headline": "VP Engineering at Acme Corp",
    "company": "Acme Corp",
    "position": "VP Engineering",
    "location": "Dubai, UAE",
    "sector_tag": "fintech",
    "seniority_tag": "VP",
    "source": "linkedin",
    "entity_affinity": "pt",
    "connection_strength": 4,
    "last_interaction_at": "2026-03-15T10:00:00Z",
    "notes": "Met at GITEX 2025. Strong AI/ML background.",
    "tags": [
      { "id": "tag_cap_tech", "name": "Technology", "category": "capability" }
    ],
    "relationships": [
      {
        "id": "rel_001",
        "type": "contact",
        "target_id": "ct_def456",
        "target_name": "Ahmed Khalil",
        "relation": "knows",
        "strength": 3,
        "via_entity": "pt"
      }
    ],
    "opportunities": [
      {
        "id": "opp_xyz",
        "title": "Acme x PT Data Integration",
        "status": "active",
        "role": "lead"
      }
    ],
    "recent_activities": [
      {
        "id": "act_001",
        "type": "meeting_held",
        "title": "Intro call with Acme team",
        "created_at": "2026-03-15T10:00:00Z"
      }
    ],
    "created_at": "2026-04-16T00:00:00Z",
    "updated_at": "2026-04-16T00:00:00Z"
  }
}

POST /api/contacts -- Create contact

Request body:

{
  "first_name": "Jane",
  "last_name": "Smith",
  "email": "jane@example.com",
  "linkedin_url": "https://linkedin.com/in/janesmith",
  "company": "Acme Corp",
  "position": "VP Engineering",
  "sector_tag": "fintech",
  "seniority_tag": "VP",
  "entity_affinity": "pt",
  "connection_strength": 4,
  "tags": ["tag_cap_tech"]
}

Response 201: Full contact object.


PUT /api/contacts/:id -- Update contact

Request body: Partial update (only fields provided are changed).

Response 200: Updated contact object.


DELETE /api/contacts/:id -- Soft-delete (sets is_archived = 1)

Response 200:

{ "ok": true, "data": { "id": "ct_abc123", "archived": true } }

POST /api/contacts/import -- Bulk import from LinkedIn CSV

Request body: multipart/form-data

Response 200:

{
  "ok": true,
  "data": {
    "import_id": "imp_001",
    "total_rows": 16423,
    "imported": 16201,
    "skipped": 187,
    "errors": 35,
    "dry_run": false,
    "error_sample": [
      { "row": 44, "reason": "Missing first_name" }
    ]
  }
}

GET /api/contacts/:id/network -- Get contact's network graph (2 degrees)

Query parameters:

ParamTypeDescription
depthinteger1 or 2 (default 1)
min_strengthintegerMinimum relationship strength

Response 200:

{
  "ok": true,
  "data": {
    "center": { "id": "ct_abc123", "name": "Jane Smith" },
    "nodes": [
      { "id": "ct_def456", "name": "Ahmed Khalil", "type": "contact", "depth": 1 },
      { "id": "ent_pt", "name": "Pure Technology", "type": "entity", "depth": 1 }
    ],
    "edges": [
      { "source": "ct_abc123", "target": "ct_def456", "relation": "knows", "strength": 4 },
      { "source": "ct_abc123", "target": "ent_pt", "relation": "works_at", "strength": 5 }
    ]
  }
}

3.3.2 Entities

GET /api/entities -- List entities

Query parameters: type, sector, is_makr_pipeline, is_pt_partner, q, page, per_page

Response 200: Paginated entity list.


GET /api/entities/:id -- Entity detail with contacts and opportunities

Response 200:

{
  "ok": true,
  "data": {
    "id": "ent_ext_001",
    "name": "TechCo",
    "type": "prospect",
    "category": "startup",
    "sector": "fintech",
    "stage": "series_a",
    "contacts": [
      { "id": "ct_abc123", "name": "Jane Smith", "position": "VP Engineering", "relation": "works_at" }
    ],
    "opportunities": [
      { "id": "opp_xyz", "title": "TechCo investment", "status": "qualifying", "routing": "makr" }
    ],
    "tags": [
      { "id": "tag_need_capital", "name": "Needs Capital", "category": "need" }
    ]
  }
}

POST /api/entities -- Create entity

PUT /api/entities/:id -- Update entity

DELETE /api/entities/:id -- Delete entity

Standard CRUD following the same patterns as contacts.


3.3.3 Relationships

POST /api/relationships -- Create relationship

Request body:

{
  "source_type": "contact",
  "source_id": "ct_abc123",
  "target_type": "contact",
  "target_id": "ct_def456",
  "relation": "knows",
  "strength": 4,
  "context": "Met at GITEX 2025",
  "via_entity": "pt"
}

Response 201: Relationship object.


PUT /api/relationships/:id -- Update relationship

DELETE /api/relationships/:id -- Remove relationship


GET /api/relationships -- List relationships with filters

Query parameters: source_id, target_id, relation, via_entity, min_strength


3.3.4 Opportunities

GET /api/opportunities -- List opportunities

Query parameters:

ParamTypeDescription
statusstringidentified, qualifying, active, won, lost, on_hold
typestringdeal, partnership, intro, advisory, investment, tech_integration
routingstringpt, makr, both
prioritystringcritical, high, medium, low
qstringSearch title/description
sortstringcreated, priority, value, status

Response 200: Paginated list with participant summary.


GET /api/opportunities/:id -- Opportunity detail

Response 200:

{
  "ok": true,
  "data": {
    "id": "opp_xyz",
    "title": "Acme x PT Data Integration",
    "type": "partnership",
    "status": "active",
    "routing": "pt",
    "priority": "high",
    "description": "Integrate Acme's fintech data pipeline with PT's AI platform.",
    "estimated_value_usd": 250000,
    "synergy": {
      "id": "syn_001",
      "type": "capability_match",
      "score": 0.87
    },
    "participants": [
      { "type": "contact", "id": "ct_abc123", "name": "Jane Smith", "role": "lead" },
      { "type": "entity", "id": "ent_ext_001", "name": "Acme Corp", "role": "counterparty" }
    ],
    "activities": [
      { "id": "act_001", "type": "meeting_held", "title": "Intro call", "created_at": "2026-03-15T10:00:00Z" }
    ],
    "created_at": "2026-04-01T00:00:00Z",
    "updated_at": "2026-04-10T00:00:00Z"
  }
}

POST /api/opportunities -- Create opportunity

Request body:

{
  "title": "Acme x PT Data Integration",
  "type": "partnership",
  "routing": "pt",
  "priority": "high",
  "description": "...",
  "estimated_value_usd": 250000,
  "participants": [
    { "type": "contact", "id": "ct_abc123", "role": "lead" },
    { "type": "entity", "id": "ent_ext_001", "role": "counterparty" }
  ]
}

Response 201: Full opportunity object.


PUT /api/opportunities/:id -- Update opportunity

PUT /api/opportunities/:id/status -- Transition status

Request body:

{
  "status": "won",
  "note": "Contract signed 2026-04-15"
}

Automatically creates an activity log entry on status transitions.


3.3.5 Synergy Engine

POST /api/synergies/scan -- Run synergy detection

Request body:

{
  "scope": "all",              // 'all', 'makr_pipeline', 'pt_partners', 'entity:<id>'
  "min_score": 0.5,            // Minimum confidence threshold
  "types": ["capability_match", "sector_overlap", "warm_intro_path"],
  "limit": 50
}

Response 200:

{
  "ok": true,
  "data": {
    "scan_id": "scan_001",
    "synergies_found": 23,
    "synergies": [
      {
        "id": "syn_001",
        "type": "capability_match",
        "score": 0.87,
        "title": "TechCo needs Capital -- Ahmed Khalil has Capital access",
        "description": "TechCo (MAKR pipeline, Series A, fintech) is tagged 'Needs Capital'. Ahmed Khalil (PT network, connection strength 5) is tagged 'Capital' and has direct relationships with 3 VCs in the fintech sector.",
        "need_side": { "type": "entity", "id": "ent_ext_001", "name": "TechCo" },
        "supply_side": { "type": "contact", "id": "ct_def456", "name": "Ahmed Khalil" },
        "matching_tags": ["Capital"],
        "status": "surfaced"
      }
    ]
  }
}

GET /api/synergies -- List synergies

Query parameters: status (surfaced, reviewed, actioned, dismissed), type, min_score, sort, page, per_page


PUT /api/synergies/:id -- Update synergy status

Request body:

{
  "status": "actioned",
  "opportunity_id": "opp_xyz"    // Link to created opportunity
}

3.3.6 Activities

GET /api/activities -- Activity feed

Query parameters: type, entity_context (pt, makr, both), opportunity_id, contact_id, since (ISO date), page, per_page

Response 200: Paginated activity list, newest first.


POST /api/activities -- Log activity

Request body:

{
  "type": "intro_made",
  "title": "Introduced Jane Smith to Ahmed Khalil",
  "description": "Warm intro for fintech data partnership discussion.",
  "opportunity_id": "opp_xyz",
  "actor_contact_id": "ct_abc123",
  "entity_context": "both",
  "value_usd": null
}

3.3.7 Tags

GET /api/tags -- List all tags, optionally filtered by category

POST /api/tags -- Create tag

POST /api/contacts/:id/tags -- Attach tags to contact

DELETE /api/contacts/:id/tags/:tag_id -- Remove tag from contact

POST /api/entities/:id/tags -- Attach tags to entity

DELETE /api/entities/:id/tags/:tag_id -- Remove tag from entity


3.3.8 Dashboard / Widget

GET /api/dashboard/summary -- Command Center widget data

Response 200:

{
  "ok": true,
  "data": {
    "contacts_total": 16423,
    "contacts_by_entity": { "pt": 9200, "makr": 3100, "both": 4123 },
    "active_opportunities": 14,
    "opportunities_by_routing": { "pt": 6, "makr": 5, "both": 3 },
    "pipeline_value_usd": 2350000,
    "synergies_pending": 8,
    "recent_activities": [
      { "id": "act_005", "type": "deal_closed", "title": "TechCo investment closed", "created_at": "2026-04-15T14:00:00Z" }
    ],
    "top_synergies": [
      { "id": "syn_003", "title": "HealthAI needs Distribution -- PT has Distribution", "score": 0.92 }
    ],
    "value_created_30d_usd": 500000,
    "intros_made_30d": 12
  }
}

This endpoint is cached in KV for 5 minutes to keep Command Center widget loads fast.


GET /api/search -- Global search across contacts, entities, opportunities

Query parameters:

ParamTypeDescription
qstringSearch term (required, min 2 chars)
typesstringComma-separated: contacts,entities,opportunities (default: all)
limitintegerMax results per type (default 10)

Response 200:

{
  "ok": true,
  "data": {
    "contacts": [
      { "id": "ct_abc123", "name": "Jane Smith", "company": "Acme Corp", "match": "name" }
    ],
    "entities": [
      { "id": "ent_ext_001", "name": "Acme Corp", "type": "prospect", "match": "name" }
    ],
    "opportunities": []
  }
}

3.4 Error Codes

HTTP StatusCodeDescription
400VALIDATION_ERRORInvalid request body or params
401UNAUTHORIZEDMissing or invalid API key
404NOT_FOUNDResource does not exist
409CONFLICTDuplicate (e.g., LinkedIn URL already exists)
413PAYLOAD_TOO_LARGECSV file exceeds 10MB limit
422IMPORT_ERRORCSV parsing failed
429RATE_LIMITEDToo many requests (100/min)
500INTERNAL_ERRORServer error

4. Synergy Matching Algorithm

4.1 Concept

The synergy engine runs four matching strategies, scores each match, and returns a ranked list. It operates entirely in D1 SQL with post-processing in the Worker.

4.2 Matching Strategies

Strategy 1: Capability Match (weight: 0.40)

Match entities/contacts tagged with "needs X" against those tagged with "has X capability."

For each entity E with tag category='need':
  Find all contacts/entities with matching tag in category='capability'
  Where the capability name corresponds to the need name
  Score = base_weight * relationship_strength_factor * entity_affinity_bonus

Scoring factors:

Strategy 2: Sector Overlap (weight: 0.20)

Identify MAKR pipeline companies and PT contacts operating in the same sector who are not yet connected.

For each MAKR pipeline entity E in sector S:
  Find PT contacts where sector_tag = S AND connection_strength >= 3
  Exclude contacts already related to E
  Score = 0.20 * seniority_weight * strength_factor

Seniority weights: C-suite=1.0, VP=0.8, Director=0.6, Manager=0.4, Other=0.2

Strategy 3: Warm Intro Path (weight: 0.25)

Find two-hop paths connecting a target entity/contact to a desired contact through mutual connections.

For each opportunity O in status 'qualifying' or 'active':
  Get target contacts/entities
  For each target T:
    Find contacts C1 where C1 knows T (from relationships)
    Find contacts C2 where Rimah knows C1 with strength >= 3
    Path: Rimah -> C1 -> T
    Score = 0.25 * min(strength(Rimah,C1), strength(C1,T)) / 5

Strategy 4: Portfolio Fit (weight: 0.15)

Match external companies in the PT network against MAKR investment criteria (sector, stage, capabilities).

For each external entity E where is_pt_partner = 1:
  Check if E.sector matches MAKR target sectors
  Check if E.stage matches MAKR target stages
  Check if E is NOT already in MAKR pipeline
  Score = 0.15 * sector_match * stage_match * tag_overlap_count / max_tags

4.3 Composite Score

final_score = sum(strategy_scores) / count(matching_strategies)

Capped at 1.0. Synergies below min_score threshold are discarded.

4.4 Deduplication

Before returning results, deduplicate by (need_side_id, supply_side_id) pair, keeping the highest-scoring match. Also suppress synergies where an active opportunity already exists between the two parties.


5. Data Import Plan

5.1 LinkedIn CSV Format

LinkedIn exports contain these columns (mapped to contacts table):

CSV ColumnMaps ToNotes
First Namefirst_nameRequired
Last Namelast_nameRequired
Email AddressemailOften empty
Companycompany
Positionposition
Connected Oncreated_atParse date
URLlinkedin_urlUnique key for dedup

Custom columns from Rimah's tagged data:

CSV ColumnMaps ToNotes
sector_tagsector_tagAlready tagged by Rimah
seniority_tagseniority_tagAlready tagged by Rimah

5.2 Import Process

  1. Upload: User uploads CSV via the import endpoint or UI.
  2. Parse: Worker streams CSV rows using a lightweight parser (no external deps -- Workers-compatible).
  3. Validate: Each row checked for required fields (first_name, last_name). Invalid rows logged to error array.
  4. Deduplicate: Match on linkedin_url. If URL exists, update existing record (merge, do not overwrite non-null fields). If no URL, match on first_name + last_name + company.
  5. Generate IDs: Each new contact gets a ct_ prefixed nanoid.
  6. Batch Insert: Use D1 batch API (max 100 rows per batch for Worker CPU limits).
  7. Auto-Tag: If sector_tag or seniority_tag present, create corresponding tag relationships.
  8. Import Record: Write to imports table with counts.
  9. Invalidate Cache: Clear KV dashboard cache after import.

5.3 MAKR Contact Import

Same process but with source = 'makr_crm' and default_entity_affinity = 'makr'. If a contact exists from LinkedIn import, update entity_affinity to 'both'.

5.4 Incremental Updates

Re-importing the same CSV updates existing records rather than creating duplicates. The linkedin_url field serves as the stable identifier. The updated_at timestamp tracks when a record was last refreshed.


6. UI Wireframe Descriptions

6.1 Global Layout

6.2 Pages


Page 1: Dashboard (Home)

The landing page. Summary metrics and actionable items.

Layout (top to bottom):


Page 2: Contacts

Filterable, searchable list of all 16K+ contacts.

Layout:


Page 3: Contact Detail

Full profile for a single contact.

Layout:


Page 4: Entities

Company directory.

Layout:


Page 5: Entity Detail

Layout:


Page 6: Opportunities

Pipeline management.

Layout:


Page 7: Opportunity Detail

Layout:


Page 8: Synergies

Synergy engine results.

Layout:


Page 9: Settings / Import

Layout:


6.3 Navigation Sidebar

[DAO Logo]

Dashboard
Contacts (16,423)
Entities (342)
Opportunities (14)
Synergies (8 new)
Activity Log

---
Settings
Import Data

Entity filter toggle at the top of sidebar: [All] [PT] [MAKR] -- this globally filters all views.


7. Command Center Widget Integration

The Command Center fetches GET /api/dashboard/summary and renders a compact widget:

+------------------------------------------+
|  DEAL & ACCESS ORCHESTRATOR              |
|  ----------------------------------------|
|  16,423 contacts | 14 active opps        |
|  $2.35M pipeline | 8 synergies pending   |
|  ----------------------------------------|
|  Latest: TechCo investment closed  2h ago|
|  Top synergy: HealthAI x PT (0.92)       |
+------------------------------------------+

Widget refreshes every 5 minutes. Click-through opens DAO in a new tab.


8. Authentication and Security


9. Versioning Strategy


10. Implementation Phases

Phase 1 -- Foundation (MVP)

Phase 2 -- Relationships and Opportunities

Phase 3 -- Synergy Engine

Phase 4 -- Intelligence


11. File Structure

projects/dao/
  DAO-SPEC.md                  -- This document
  wrangler.toml                -- Cloudflare Worker config
  package.json
  src/
    index.ts                   -- Worker entry (Hono router)
    routes/
      contacts.ts
      entities.ts
      relationships.ts
      opportunities.ts
      synergies.ts
      activities.ts
      tags.ts
      dashboard.ts
      search.ts
      import.ts
    models/
      contact.ts
      entity.ts
      relationship.ts
      opportunity.ts
      synergy.ts
      activity.ts
      tag.ts
    services/
      synergy-engine.ts        -- Matching algorithm
      csv-parser.ts            -- LinkedIn CSV importer
      cache.ts                 -- KV cache helpers
    middleware/
      auth.ts                  -- API key validation
      cors.ts                  -- CORS headers
      validation.ts            -- Input validation
    db/
      schema.sql               -- D1 schema (from section 2)
      seed.sql                 -- Seed data
      migrations/              -- Incremental migrations
  frontend/
    index.html
    src/
      app.ts
      pages/
        dashboard.ts
        contacts.ts
        contact-detail.ts
        entities.ts
        entity-detail.ts
        opportunities.ts
        opportunity-detail.ts
        synergies.ts
        settings.ts
      components/
        sidebar.ts
        metric-card.ts
        activity-feed.ts
        synergy-card.ts
        opportunity-card.ts
        tag-chip.ts
        network-graph.ts
        import-form.ts
        search-bar.ts
      styles/
        tokens.css             -- Design tokens (#000, #111, #D84315, etc.)
        global.css
      lib/
        api-client.ts          -- Typed API wrapper
        types.ts               -- Shared TypeScript types

End of specification.