An autonomous commercial entity operating as a B2B discovery platform for UK broadcast, film, and TV production services.
CALLSHEET is not a product in the traditional sense. It is an autonomous commercial entity β a cognitive system instantiated within a legal shell, designed to perceive its environment, make decisions, and procure human or machine resources when it cannot act alone.
The platform (directory, search, matching, subscriptions) is what the entity does. The entity itself is the system that operates the platform.
Domain: UK broadcast/film/TV production services. 7 sectors, 64 service areas, 269 specialisations.
Model: Unified accounts (every user is both buyer and provider). Quality earned, visibility bought. Β£199/Β£399/Β£699 annual tiers.
flowchart LR
classDef person fill:#e8daef,stroke:#6c3483,color:#1a1a1a
classDef action fill:#d4efdf,stroke:#1e8449,color:#1a1a1a
classDef result fill:#d4e6f1,stroke:#2471a3,color:#1a1a1a
BUYER(["π¬ Production Company<br/><i>needs a camera crew</i>"]):::person
SEARCH["Search<br/><i>'camera operator London'</i>"]:::action
RESULTS["See ranked results<br/><i>quality + relevance</i>"]:::action
PROFILE["View provider profile<br/><i>credits, reviews, trust badge</i>"]:::action
ENQUIRY["Send enquiry"]:::action
RESPONSE(["Provider responds<br/><i>connection made β</i>"]):::result
BUYER --> SEARCH --> RESULTS --> PROFILE --> ENQUIRY --> RESPONSE
flowchart LR
classDef person fill:#fef9e7,stroke:#b7950b,color:#1a1a1a
classDef action fill:#d4efdf,stroke:#1e8449,color:#1a1a1a
classDef result fill:#d4e6f1,stroke:#2471a3,color:#1a1a1a
PROVIDER(["π₯ Camera Operator<br/><i>wants to be found</i>"]):::person
CLAIM["Claim or create<br/>their listing"]:::action
FILL["Add profile, credits,<br/>photos, services"]:::action
VERIFY["Get verified<br/><i>Companies House check</i>"]:::action
UPGRADE["Optionally upgrade<br/><i>more photos, priority, analytics</i>"]:::action
FOUND(["Found by buyers<br/><i>enquiries arrive β</i>"]):::result
PROVIDER --> CLAIM --> FILL --> VERIFY --> UPGRADE --> FOUND
flowchart TD
classDef money fill:#d5f5e3,stroke:#1e8449,color:#1a1a1a
classDef free fill:#d4e6f1,stroke:#2471a3,color:#1a1a1a
classDef value fill:#fef9e7,stroke:#b7950b,color:#1a1a1a
subgraph FREE ["Free Tier β Everyone gets this"]
F1["Listed in directory"]
F2["Appear in search results"]
F3["Receive enquiries"]
F4["Basic profile"]
end
class FREE free
subgraph PAID ["Paid Tiers β Β£199 / Β£399 / Β£699 per year"]
P1["More photos & media"]
P2["Visibility boost in search"]
P3["Analytics dashboard"]
P4["Priority placement"]
P5["Competitor benchmarking"]
end
class PAID money
subgraph ENGINE ["What the system does automatically"]
E1["Scores quality (0-100) based on<br/>completeness, freshness, accuracy"]:::value
E2["Detects stale data and<br/>nudges providers to update"]:::value
E3["Spots churn risk and<br/>intervenes before cancellation"]:::value
E4["Monitors its own health<br/>and generates reports"]:::value
end
FREE -- "conversion nudges<br/>(you got 12 views!)" --> PAID
PAID -- "revenue funds<br/>the platform" --> ENGINE
ENGINE -- "better data = better<br/>search = more buyers" --> FREE
Most directories are databases with a search box. CALLSHEET is designed to run itself.
flowchart LR
classDef normal fill:#fadbd8,stroke:#922b21,color:#1a1a1a
classDef cs fill:#d4efdf,stroke:#1e8449,color:#1a1a1a
subgraph NORMAL ["Typical Directory"]
N1["Human updates listings"]:::normal
N2["Human monitors quality"]:::normal
N3["Human chases renewals"]:::normal
N4["Human handles support"]:::normal
end
subgraph CALLSHEET ["CALLSHEET"]
C1["System detects stale data<br/>and triggers updates"]:::cs
C2["System scores quality<br/>across 5 dimensions"]:::cs
C3["System detects churn risk<br/>and sends win-back offers"]:::cs
C4["System triages support<br/>and escalates when stuck"]:::cs
end
NORMAL -- "replace manual<br/>work with" --> CALLSHEET
TL;DR: It's a production services directory that watches its own data quality, nudges providers to keep profiles fresh, automatically handles billing/compliance/churn, and only calls a human when it encounters something it can't resolve on its own.
The four departments, explained simply
Think of CALLSHEET as a company with four departments. Each department does its own job and talks to the others through a shared noticeboard. Nobody reaches into another department's filing cabinet.
| Department | Job | Example |
|---|---|---|
| Data & Listings | Owns all the directory records. Scores quality, checks freshness, verifies identities. | "This listing hasn't been updated in 6 months β flag it." |
| Operations | Processes payments, handles compliance, creates support tickets. | "Paddle says this card failed β start the grace period." |
| Platform & Product | The website. Search, profiles, dashboards, onboarding, emails. | "Someone searched 'camera operator London' β show the best matches." |
| Commercial & Revenue | Grows the business. Conversion nudges, churn prevention, pricing. | "This free user got 12 enquiries β suggest upgrading." |
Four autonomous sub-entities coordinate through typed events and query interfaces. No shared mutable state. Each sub-entity is a black box with defined contracts.
graph TB
classDef domain fill:#d4e6f1,stroke:#2471a3,color:#1a1a1a
classDef bus fill:#f9e79f,stroke:#b7950b,color:#333
classDef infra fill:#d5f5e3,stroke:#1e8449,color:#1a1a1a
classDef external fill:#e8d5b7,stroke:#8b6914,color:#333
PADDLE["Paddle<br/><i>Billing & Taxes</i>"]:::external
RESEND["Resend<br/><i>Transactional Email</i>"]:::external
R2["Cloudflare R2<br/><i>Media Storage</i>"]:::external
CH["Companies House<br/><i>Verification API</i>"]:::external
subgraph DL ["Data & Listings"]
direction TB
DL1["Listing CRUD & Integrity"]
DL2["Quality Scoring (5-dim, 0-100)"]
DL3["Decay Detection & Enrichment"]
DL4["Verification (4-tier)"]
end
class DL domain
subgraph OPS ["Operations"]
direction TB
OPS1["Paddle Webhook Processing"]
OPS2["Support Triage & Tickets"]
OPS3["Compliance & GDPR"]
OPS4["Billing Reconciliation"]
end
class OPS domain
subgraph PP ["Platform & Product"]
direction TB
PP1["Search (FTS + Trigram)"]
PP2["Onboarding & Claim Flows"]
PP3["Dashboard & Admin Panel"]
PP4["Email Pipeline"]
end
class PP domain
subgraph CR ["Commercial & Revenue"]
direction TB
CR1["Feature Gating & Pricing"]
CR2["Conversion Triggers (6 types)"]
CR3["Churn Intervention & Win-back"]
CR4["Revenue Perception"]
end
class CR domain
EB(["Event Bus β 25 Typed Events / ~50 Consumers"]):::bus
INFRA["Shared Infrastructure<br/><i>Event Bus Β· Scheduler (35 actions) Β· Flow Engine<br/>Decision Logger Β· Email Transport Β· Storage</i>"]:::infra
DL <-..-> EB
OPS <-..-> EB
PP <-..-> EB
CR <-..-> EB
PADDLE --> OPS
PP --> RESEND
PP --> R2
DL --> CH
DL & OPS & PP & CR --> INFRA
Sub-entity contract summary
| Sub-Entity | Events Emitted | Events Consumed | Queries Exposed | Autonomous Decisions |
|---|---|---|---|---|
| Data & Listings | 9 | 4 | 2 | Quality scoring, claim evaluation, decay response, enrichment cadence |
| Operations | 3 | 10 | 5 | Support triage, task routing, billing reconciliation, compliance scheduling |
| Platform & Product | 9 | 12 | 1 | Search ranking, onboarding flow, account closure orchestration |
| Commercial & Revenue | 4 | 8 | 0 | Conversion triggers, churn intervention, win-back eligibility |
erDiagram
ACCOUNT ||--o{ LISTING : "0..N manages"
ACCOUNT ||--o| BUYER_FACET : "always active"
ACCOUNT ||--o| SUPPRESSION : "comms control"
LISTING ||--o| VERIFICATION : "4-tier trust"
LISTING ||--o| QUALITY_SCORE : "5-dim 0-100"
LISTING ||--o| ENGAGEMENT : "views, searches, enquiries"
LISTING ||--o{ TAXONOMY_TAG : "sector > area > spec"
LISTING ||--o{ MEDIA_ITEM : "images, tier-gated"
LISTING ||--o{ CREDIT : "client endorsements"
BUYER_FACET ||--o{ SHORTLIST : "max 10"
BUYER_FACET ||--o{ ENQUIRY : "sent to listings"
BUYER_FACET ||--o{ SEARCH_HISTORY : "recent + saved"
ACCOUNT {
text id "Better Auth (text)"
string email "verified"
text_array departments
}
LISTING {
uuid id
enum entityType "freelancer | company | ..."
enum claimStatus "unclaimed | claimed | disputed"
enum subscriptionTier "free | standard | premium | partner"
enum lifecycleStatus "active | suspended | archived"
}
VERIFICATION {
enum tier "unclaimed > claimed > verified > premium_verified"
timestamp claimedAt
int verificationScore
}
QUALITY_SCORE {
int composite "0-100"
int completeness "0-25"
int freshness "0-25"
int accuracy "0-20"
int richness "0-15"
int verification "0-15"
}
Full schema: 8 schema files, 17 migrations, ~50 tables
src/db/schema/
βββ auth.ts # Better Auth (user, session, verification)
βββ accounts.ts # Account profiles, departments
βββ data-and-listings.ts # 25 tables β listings, taxonomy, engagement, credits
βββ commercial.ts # commercial_state, churn_analysis_log, sponsored_impressions
βββ operations.ts # support_tickets, task_specs, compliance, billing
βββ intelligence.ts # enrichment_schedules, decay_signals, perception_aggregates
βββ correspondence.ts # correspondence_log, suppressed_emails
βββ shared.ts # domain_events, deferred_actions, orchestrated_flows, decisions
PostgreSQL full-text search with synonym expansion, trigram fallback, and a multiplicative ranking formula.
flowchart LR
classDef input fill:#e8daef,stroke:#6c3483,color:#1a1a1a
classDef process fill:#d4efdf,stroke:#1e8449,color:#1a1a1a
classDef formula fill:#fef9e7,stroke:#b7950b,color:#1a1a1a
Q(["Query"]):::input --> SYN["Synonym<br/>Expansion"]:::process --> FTS["tsvector<br/>@@"]:::process --> RANK:::formula
FTS -- "0 results" --> TRI["Trigram<br/>Fallback<br/><i>similarity > 0.3</i>"]:::process --> OUT
RANK --> OUT(["Results"])
subgraph RANK ["Ranking Formula"]
F["ts_rank_cd Γ (1.0 + quality_boost + paid_boost)"]
QB["quality: composite/100 Γ 0.5"]
PB["paid: 0.00 | 0.15 | 0.25"]
end
A high-quality free listing beats a low-quality premium listing. Quality is earned, visibility is bought β but quality always wins the tiebreak.
stateDiagram-v2
[*] --> Unclaimed : seeded from import
Unclaimed --> Claimed : owner claims + email verified
Claimed --> Verified : Companies House match + domain match
Verified --> PremiumVerified : paid tier + enhanced evidence
Claimed --> Unclaimed : claim abandoned (90d)
state Claimed {
[*] --> AutoApprove : CH active + domain email match
[*] --> AutoReject : CH dissolved
[*] --> ManualReview : evidence insufficient
[*] --> DisputeResolution : competing claim
}
25 typed domain events connect the four sub-entities. ~50 async consumers react to state changes. No polling, no shared state.
flowchart LR
classDef dl fill:#d4efdf,stroke:#1e8449,color:#1a1a1a
classDef ops fill:#d6eaf8,stroke:#2e86c1,color:#1a1a1a
classDef pp fill:#e8daef,stroke:#6c3483,color:#1a1a1a
classDef cr fill:#fce4ec,stroke:#c0392b,color:#1a1a1a
classDef bus fill:#f9e79f,stroke:#b7950b,color:#333
DL["D&L<br/><i>9 events</i>"]:::dl
OPS["Ops<br/><i>3 events</i>"]:::ops
PP["PP<br/><i>9 events</i>"]:::pp
CR["CR<br/><i>4 events</i>"]:::cr
EB(["Event Bus"]):::bus
DL --> EB
OPS --> EB
PP --> EB
CR --> EB
EB --> DL
EB --> OPS
EB --> PP
EB --> CR
Key event flows
| Trigger | Event | Key Consumers |
|---|---|---|
| User claims listing | claim_approved |
D&L: quality recalc, enrichment upgrade Β· CR: trigger reset, win-back cancel Β· PP: search reindex |
| Paddle webhook | subscription_tier_changed |
D&L: enrichment cadence adjust Β· PP: feature gates refresh Β· CR: revenue metrics update |
| Subscription cancelled | subscription_ended |
CR: churn analysis + win-back scheduling Β· PP: downgrade feature access Β· Ops: update records |
| Quality drops below 40 | quality_score_changed |
CR: low-quality intervention (notification + 30d check) |
| GDPR erasure | erasure_completed |
PP: purge search indexes Β· CR: anonymise churn logs, cancel win-backs |
| Listing goes stale | decay_signal_detected |
Ops: create ticket if unreachable Β· Intel: annotate with ticket status |
Six shared modules that every domain builds on.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Shared Infrastructure β
ββββββββββββββββ¬βββββββββββββββ¬βββββββββββββββ¬ββββββββββββββββββ€
β Event Bus β Scheduler β Flow Engine β Decision Logger β
β 25 events β 35 actions β erasure + β typed decisions β
β ~50 async β self-perp. β closure β audit trail β
β consumers β retry logic β orchestrated β β
ββββββββββββββββΌβββββββββββββββΌβββββββββββββββΌββββββββββββββββββ€
β Email Transport (Resend) β Object Storage (R2) β
β logging Β· suppression Β· β upload Β· download Β· variants β
β bounce handling Β· DSAR β WebP processing via sharp β
ββββββββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββ
The entity's perception, learning, and autonomy systems.
flowchart TD
classDef perception fill:#d4efdf,stroke:#1e8449,color:#1a1a1a
classDef decision fill:#fef9e7,stroke:#b7950b,color:#1a1a1a
classDef learning fill:#e8daef,stroke:#6c3483,color:#1a1a1a
classDef action fill:#d6eaf8,stroke:#2e86c1,color:#1a1a1a
subgraph PERCEIVE ["Perception"]
QS["Quality Scoring<br/><i>5 dimensions, 0-100</i>"]:::perception
DECAY["Decay Detection<br/><i>website, email, CH, social, postcode</i>"]:::perception
ANALYTICS["Analytics Pipeline<br/><i>search terms, demographics,<br/>competitor bench, enquiry response</i>"]:::perception
end
subgraph DECIDE ["Intelligence"]
L["Learning Hypotheses<br/><i>L1-L7 measurement</i>"]:::decision
CHURN["Proactive Churn<br/><i>engagement decline + billing signals</i>"]:::decision
REVENUE["Revenue Health<br/><i>8-metric extended perception</i>"]:::decision
end
subgraph ACT ["Action"]
CEREMONY["12 Ceremony Handlers<br/><i>taxonomy review, data health,<br/>principal briefing, etc.</i>"]:::action
ENRICH["Enrichment Scheduling<br/><i>tiered cadence by verification</i>"]:::action
INTERVENE["Interventions<br/><i>churn, quality, conversion</i>"]:::action
end
PERCEIVE --> DECIDE --> ACT
ACT -.-> |"feedback"| PERCEIVE
callsheet/
β
βββ 0-strategic-frame/ # Entity architecture frame, output style guides
βββ 1-investigation/ # 14 research deliverables (LOCKED)
βββ 2-concept-design/ # 5 domain specs, 341 stress-test scenarios
β βββ diagrams/ # 38 Mermaid diagrams (technical + plain English)
βββ 3-requirements/ # 11 slices (v2), 5 interface specs, 693 AC
βββ 4-work-management/ # 82 work items, 93 retros, epochs/arcs/chapters
βββ 5-launch-readiness/ # Deployment readiness tracker
β
βββ src/
β βββ app/ # Next.js 16 pages (24 routes)
β β βββ admin/ # 8-page admin panel
β β βββ dashboard/ # Provider + buyer dashboard
β β βββ search/ # Full-text search with facets
β β βββ providers/[slug]/ # SSG + ISR listing profiles
β β βββ api/ # tRPC, auth, webhooks
β β
β βββ server/
β β βββ routers/ # 31 tRPC routers (20 domain + 10 admin + root)
β β βββ trpc.ts # Context, middleware, procedures
β β
β βββ domains/
β β βββ data-and-listings/ # Quality, decay, analytics, consumers
β β βββ commercial/ # Pricing, conversion, churn, win-back
β β βββ operations/ # Paddle, billing, compliance, support
β β βββ intelligence/ # Ceremony handlers, perception consumers
β β βββ platform/ # Buyer UX, dashboard, enquiry, reminders
β β
β βββ lib/
β β βββ events/ # Event bus + singleton + types
β β βββ scheduler/ # 35 deferred action handlers
β β βββ flows/ # Orchestrated flow engine
β β βββ email/ # Resend transport + logging
β β βββ services/ # DI container (AppServices)
β β
β βββ db/
β βββ schema/ # 8 schema files (~50 tables, 17 migrations)
β βββ seed/ # Taxonomy (7/64/269) + demo data
β
βββ drizzle/ # Migration SQL files
βββ e2e/ # Playwright API-level tests
βββ .claude/skills/ # 12 AI agent pipeline skills
703 unit tests ββββββββββββββββββββββββββββββ 40%
1,013 integration ββββββββββββββββββββββββββββββββββββββββββββββ 59%
7 E2E (API-level) ββββββββββββββββββββββββββββββ 1%
βββββββββββββββββββββββββββββββββββββββββββββββββββββ
1,723 total All passing. 0 type errors.
Integration tests run against a real Postgres instance (Supabase local). No mocked databases.
The platform was built in 11 vertical slices, each stress-tested with 20+ boundary scenarios before implementation.
S0 Infrastructure ββββββββββββββββββββ 52 AC Event bus, scheduler, flow engine, email, storage
S1 Data Model ββββββββββββββββββββ 42 AC Schema, search, CRUD, integrity, consumers
S2 Onboarding ββββββββββββββββββββ 41 AC Account creation, listing creation, CH lookup
S3 Claim & Verify ββββββββββββββββββββ 48 AC Claim evaluation, approval/rejection, disputes
S4 Subscriptions ββββββββββββββββββββ 50 AC Paddle webhooks, feature gating, downgrade
S5 Provider Exp ββββββββββββββββββββ 46 AC Dashboard, enquiry inbox, profile strength
S6 Buyer Exp ββββββββββββββββββββ 52 AC Search page, shortlists, buyer dashboard
S7 Operations ββββββββββββββββββββ 101 AC Admin panel, compliance, billing, health
S8 Commercial ββββββββββββββββββββ 93 AC Conversion, churn, win-back, revenue
S9 Entity Intel ββββββββββββββββββββ 101 AC Quality scoring, decay, analytics, learning
S10 Hardening ββββββββββββββββββββ 72 AC Not yet decomposed
Presentation ββββββββββββββββββββ 35 AC tRPC wiring, UI components, search page
| Layer | Technology | Why |
|---|---|---|
| Framework | Next.js 16 | App Router, RSC, ISR, API routes |
| Language | TypeScript 5.9 (strict) | Zero any, zero type errors |
| API | tRPC 11 | End-to-end type safety, batch requests |
| Database | PostgreSQL via Supabase | Full-text search, pg_trgm, JSONB |
| ORM | Drizzle | Type-safe schema, migrations, $type<T>() for JSONB |
| Auth | Better Auth | Email/password, session management |
| Payments | Paddle | Merchant of record, webhook-driven |
| Resend | Transactional email with bounce handling | |
| Storage | Cloudflare R2 | S3-compatible, WebP variant generation |
| Styling | Tailwind v4 + shadcn/ui | CSS-first config, Radix primitives |
| Testing | Vitest + Playwright | Unit + real-DB integration + API-level E2E |
| Hosting | Vercel | Edge-ready, ISR support |
| Target cost | ~Β£36/month | Supabase free + Vercel free + R2 free tier + Resend free |
# Prerequisites: Node.js 20+, Docker (for Supabase local)
# 1. Clone and install
git clone https://github.com/Rwb3n/callsheet.git
cd callsheet
npm install
# 2. Start local Supabase
npx supabase start
# 3. Set up environment
cp .env.example .env.local
# DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
# 4. Push schema + seed
drizzle-kit push
npm run db:custom-sql
npm run db:seed
npm run db:seed-demo
# 5. Run
npm run dev # http://localhost:3000
# 6. Test
npm run test # 703 unit tests
npm run test:integration # 1,013 integration tests (needs Supabase running)
npm run typecheck # 0 errorsNuclear reset
npm run db:reset # supabase reset β drizzle push β custom-sql β seed β seed-demo
npm run dev:demo # db:reset + dev server38 Mermaid diagrams in two versions β technical and plain English. All render natively on GitHub.
Phase 0 β Strategic Frame ββββββββββββββββββββββββββββββ ACTIVE
Phase 1 β Investigation ββββββββββββββββββββββββββββββ COMPLETE (LOCKED)
Phase 2 β Concept Design ββββββββββββββββββββββββββββββ COMPLETE (341 scenarios)
Phase 3 β Requirements ββββββββββββββββββββββββββββββ COMPLETE (11 slices, 693 AC)
Phase 4 β Work Management ββββββββββββββββββββββββββββββ ACTIVE (82 work items)
Phase 5 β Launch Readiness ββββββββββββββββββββββββββββββ PENDING
| Decision | Choice | Rationale |
|---|---|---|
| Account model | Unified (buyer + provider) | Every user is both. Provider is opt-in facet. |
| Sub-entity boundaries | 4 autonomous domains | Black-box composability. Typed contracts only. |
| Search | PostgreSQL FTS + pg_trgm | Good enough until 10-20K listings. Service layer abstracts. |
| Ranking | Quality Γ relevance + paid boost | Quality earned (0-100 composite), visibility bought (tier multiplier). |
| Pricing | Β£199 / Β£399 / Β£699 annual | Standard / Premium / Partner. Freelancer discount considered. |
| Verification | 4-tier (Unclaimed β Premium Verified) | Companies House API automated. Progressive trust. |
| Events | In-process TypeScript bus | ~50 async consumers via waitUntil(). Migrate at >30% request duration. |
| Transactions | Orchestrated flows + event reactions | No distributed transactions. Erasure/closure are stepped flows. |
These decisions are final. The rationale is documented in entity-architecture-frame.md and concept design specs.
- Modular monolith (extract when bottlenecks demand)
- Domain events as primary coordination mechanism
- Application-level in-process event bus
- TypeScript const exports for schema versioning
- Sub-entity composability as a hard constraint
- No shared mutable state across domain boundaries
Proprietary. All rights reserved.
Built by an autonomous entity, for autonomous entities.