diff --git a/package.json b/package.json index 1aec44c..5a29998 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "pig", "version": "0.1.0", "private": true, - "description": "PIG — Prime Intellect Growth. An open-source, agent-native CRM for two-sided AI-compute companies.", + "description": "PIG \u2014 Prime Intellect Growth. An open-source, agent-native CRM for two-sided AI-compute companies.", "license": "Apache-2.0", "type": "module", "engines": { @@ -22,7 +22,8 @@ "dev:mcp": "npm run dev -w @pig/mcp", "db:generate": "npm run generate -w @pig/db", "db:migrate": "npm run migrate -w @pig/db", - "db:seed": "npm run seed -w @pig/db" + "db:seed": "npm run seed -w @pig/db", + "db:demo": "npm run demo -w @pig/db" }, "devDependencies": { "@types/node": "^22.10.2", diff --git a/packages/db/package.json b/packages/db/package.json index 28d51c9..7324290 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -15,7 +15,8 @@ "migrate": "tsx src/migrate.ts", "seed": "tsx src/seed/index.ts", "studio": "drizzle-kit studio", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "demo": "tsx src/seed/demo.ts" }, "dependencies": { "@pig/core": "*", diff --git a/packages/db/src/seed/demo.ts b/packages/db/src/seed/demo.ts new file mode 100644 index 0000000..fb0744d --- /dev/null +++ b/packages/db/src/seed/demo.ts @@ -0,0 +1,645 @@ +/** + * Demo dataset — a plausible book of business, for development and demos. + * + * Separate from `index.ts` (which seeds publicly-sourced, cited people) because + * this is **invented**. It exists so the product is legible before anyone has + * entered real data: margin that moves, blocks at different utilisation, deals + * spread across both pipelines, contracts with real structure. + * + * Two integrity rules, and they are not fussiness: + * + * **Every record is prefixed `DEMO —`.** A screenshot of this must never be + * mistakeable for real business. + * + * **Demand-side customers are fictional.** Suppliers are real companies — + * they are public, and naming the actual market is the point — but their + * commitments are labelled and the prices are illustrative. Inventing + * *customers* with invented contract values against real named companies + * would be fabricating commercial records about real businesses, which is a + * different thing entirely and not worth the realism. + * + * Remove it all with `npm run db:demo -- --clear`. + * + * The numbers are chosen to teach. The book as a whole clears a modest margin — + * roughly what this industry actually earns once capacity cost is charged + * honestly — while individual blocks tell different stories: + * + * the large H200 block carries the book; + * the EU H100 block is UNDERWATER at 55% sold, because a 46% markup needs + * ~69% sold to break even at all; + * the community A100 pool has a large hold that has not converted, so it + * shows as reserved-but-unsold — the distinction between "sold" and "held" + * made visible rather than theoretical. + * + * A demo that opens on a healthy total and reveals the problems on drill-down + * is more useful than one that opens on a loss, which reads as a broken + * product rather than an under-utilised book. + */ +import { and, eq, like, or } from 'drizzle-orm'; +import { createDatabase } from '../client'; +import { + accounts, + activities, + allocations, + capacityCommitments, + capacityRequests, + contacts, + contracts, + contractObligations, + demandDeals, + sites, + slaTerms, + supplyDeals, +} from '../schema/index'; + +const db = createDatabase(); +const PREFIX = 'DEMO — '; + +const day = 86_400_000; +const now = Date.now(); +const at = (days: number) => new Date(now + days * day); + +/** GPU-hours for a block, allowing for a maintenance/ramp haircut. */ +const hours = (gpus: number, days: number, efficiency = 0.94) => + String(Math.round(gpus * 24 * days * efficiency)); + +// ---------------------------------------------------------------- suppliers + +const SUPPLY = [ + { + domain: 'coreweave.com', + site: { name: 'US-East (NJ)', country: 'United States', countryCode: 'US', region: 'us-east' }, + commitment: { + name: `${PREFIX}512× H200 reserved, 12 months`, + gpuType: 'H200', + socket: 'SXM5' as const, + gpuCount: 512, + interconnectType: 'Infiniband' as const, + days: 365, + costPerGpuHourCents: 189, + takeOrPayFloorPct: '100', + prepaidPct: '25', + }, + }, + { + domain: 'nebius.com', + site: { name: 'EU-North (FI)', country: 'Finland', countryCode: 'FI', region: 'eu-north' }, + commitment: { + name: `${PREFIX}128× H100 reserved, 6 months`, + gpuType: 'H100_80GB', + socket: 'SXM5' as const, + gpuCount: 128, + interconnectType: 'Infiniband' as const, + days: 180, + costPerGpuHourCents: 171, + takeOrPayFloorPct: '80', + prepaidPct: '0', + }, + }, + { + domain: 'crusoe.ai', + site: { name: 'US-Central (TX)', country: 'United States', countryCode: 'US', region: 'us-central' }, + commitment: { + name: `${PREFIX}64× B200 reserved, 9 months`, + gpuType: 'B200', + socket: 'SXM6' as const, + gpuCount: 64, + interconnectType: 'Infiniband' as const, + days: 270, + costPerGpuHourCents: 305, + takeOrPayFloorPct: '100', + prepaidPct: '15', + }, + }, + { + domain: 'runpod.io', + site: { name: 'Community pool', country: 'United States', countryCode: 'US', region: 'us-west' }, + commitment: { + name: `${PREFIX}32× A100 burst pool, 3 months`, + gpuType: 'A100_80GB', + socket: 'SXM4' as const, + gpuCount: 32, + interconnectType: 'Ethernet' as const, + securityTier: 'community_cloud' as const, + days: 90, + costPerGpuHourCents: 96, + takeOrPayFloorPct: '0', + prepaidPct: '0', + }, + }, +]; + +/** + * Fictional customers. + * + * Invented deliberately — see the note at the top of this file. Any resemblance + * to a real company is unintended, and none of these figures describes anyone's + * actual contract. + */ +const DEMAND = [ + { + account: `${PREFIX}Halcyon Research`, + segment: 'frontier_lab' as const, + country: 'United States', + contact: { name: 'Dana Whitfield', title: 'Head of Infrastructure', decisionMaker: true }, + deal: { + name: `${PREFIX}Pre-training cluster, 12 months`, + productLine: 'compute_reserved' as const, + stage: 'deployment' as const, + acvCents: 2_400_000_00, + termMonths: 12, + msaExecuted: true, + dpaExecuted: true, + }, + request: { gpuType: 'H200', gpuCount: 256, fastFabric: true, maxPriceCents: 285 }, + // Draws from the CoreWeave block. + allocation: { supplier: 'coreweave.com', share: 0.76, priceCents: 271, status: 'active' }, + }, + { + account: `${PREFIX}Verity Health AI`, + segment: 'enterprise' as const, + country: 'Germany', + contact: { name: 'Lukas Brenner', title: 'VP Engineering', decisionMaker: true }, + deal: { + name: `${PREFIX}EU-resident fine-tuning`, + productLine: 'post_training' as const, + stage: 'procurement' as const, + acvCents: 640_000_00, + termMonths: 6, + msaExecuted: true, + dpaExecuted: false, + }, + // Data residency: must land in the EU. Drives the Nebius block. + request: { + gpuType: 'H100_80GB', + gpuCount: 64, + fastFabric: true, + maxPriceCents: 260, + allowedRegions: ['eu-north', 'eu-west'], + certifications: ['ISO 27001', 'SOC 2 Type II'], + }, + allocation: { supplier: 'nebius.com', share: 0.55, priceCents: 249, status: 'committed' }, + }, + { + account: `${PREFIX}Northwind Robotics`, + segment: 'applied_ai_startup' as const, + country: 'United States', + contact: { name: 'Priya Raghavan', title: 'CTO', decisionMaker: true }, + deal: { + name: `${PREFIX}Blackwell evaluation → production`, + productLine: 'compute_reserved' as const, + stage: 'poc' as const, + acvCents: 890_000_00, + termMonths: 9, + msaExecuted: true, + dpaExecuted: true, + }, + request: { gpuType: 'B200', gpuCount: 32, fastFabric: true, maxPriceCents: 460 }, + allocation: { supplier: 'crusoe.ai', share: 0.74, priceCents: 441, status: 'active' }, + }, + { + account: `${PREFIX}Tessellate Labs`, + segment: 'applied_ai_startup' as const, + country: 'United Kingdom', + contact: { name: 'Owen Marsh', title: 'Founding Engineer', decisionMaker: false }, + deal: { + name: `${PREFIX}Inference burst capacity`, + productLine: 'inference' as const, + stage: 'proposal' as const, + acvCents: 145_000_00, + termMonths: 3, + msaExecuted: false, + dpaExecuted: false, + }, + request: { gpuType: 'A100_80GB', gpuCount: 16, fastFabric: false, maxPriceCents: 175 }, + // A HOLD, not a sale. The deal has not closed, so this reserves capacity + // without counting as revenue — the distinction the capacity view exists + // to make visible. + allocation: { supplier: 'runpod.io', share: 0.55, priceCents: 168, status: 'planned', holdDays: 12 }, + }, + { + account: `${PREFIX}Aurelian Systems`, + segment: 'enterprise' as const, + country: 'United States', + contact: { name: 'Meredith Cole', title: 'Director, ML Platform', decisionMaker: true }, + deal: { + name: `${PREFIX}Multi-year committed capacity`, + productLine: 'compute_reserved' as const, + stage: 'legal' as const, + acvCents: 3_100_000_00, + termMonths: 24, + msaExecuted: false, + dpaExecuted: false, + }, + request: { gpuType: 'H200', gpuCount: 128, fastFabric: true, maxPriceCents: 265 }, + allocation: null, // Still in legal. Nothing reserved yet — correctly. + }, + { + account: `${PREFIX}Quillon AI`, + segment: 'applied_ai_startup' as const, + country: 'Canada', + contact: { name: 'Sofia Trentini', title: 'Head of Research', decisionMaker: true }, + deal: { + name: `${PREFIX}Evaluation harness pilot`, + productLine: 'evaluations' as const, + stage: 'qualification' as const, + acvCents: 48_000_00, + termMonths: 3, + msaExecuted: false, + dpaExecuted: false, + }, + request: null, + allocation: null, + }, +]; + +async function clear() { + console.log('Removing demo data…'); + // Ordered so foreign keys never block a delete. + const demoAccounts = await db + .select({ id: accounts.id }) + .from(accounts) + .where(like(accounts.name, `${PREFIX}%`)); + const ids = demoAccounts.map((a) => a.id); + + await db.delete(allocations).where(like(allocations.notes, `${PREFIX}%`)); + await db.delete(contractObligations); + await db.delete(slaTerms); + await db.delete(contracts).where(like(contracts.title, `${PREFIX}%`)); + await db.delete(capacityRequests); + await db.delete(demandDeals).where(like(demandDeals.name, `${PREFIX}%`)); + await db.delete(supplyDeals).where(like(supplyDeals.name, `${PREFIX}%`)); + await db.delete(capacityCommitments).where(like(capacityCommitments.name, `${PREFIX}%`)); + await db.delete(activities).where(like(activities.subject, `${PREFIX}%`)); + for (const id of ids) { + await db.delete(contacts).where(eq(contacts.accountId, id)); + } + await db.delete(accounts).where(like(accounts.name, `${PREFIX}%`)); + console.log(`Removed ${ids.length} demo account(s) and everything hanging from them.`); +} + +async function seedDemo() { + console.log('Seeding the demo book…\n'); + + // ------------------------------------------------------------ supply side + const commitmentIds = new Map(); + + for (const supplier of SUPPLY) { + const [account] = await db + .select({ id: accounts.id }) + .from(accounts) + .where(eq(accounts.domain, supplier.domain)) + .limit(1); + if (!account) { + console.log(` skipped ${supplier.domain} — run the base seed first`); + continue; + } + + const [existing] = await db + .select({ id: capacityCommitments.id }) + .from(capacityCommitments) + .where(eq(capacityCommitments.name, supplier.commitment.name)) + .limit(1); + if (existing) { + commitmentIds.set(supplier.domain, existing.id); + continue; + } + + const [site] = await db + .insert(sites) + .values({ + accountId: account.id, + name: `${PREFIX}${supplier.site.name}`, + country: supplier.site.country, + countryCode: supplier.site.countryCode, + region: supplier.site.region, + certifications: + supplier.commitment.securityTier === 'community_cloud' + ? [] + : ['SOC 2 Type II', 'ISO 27001'], + }) + .returning(); + + const c = supplier.commitment; + const [commitment] = await db + .insert(capacityCommitments) + .values({ + accountId: account.id, + siteId: site?.id, + name: c.name, + gpuType: c.gpuType, + socket: c.socket, + gpuCount: c.gpuCount, + interconnectType: c.interconnectType, + securityTier: c.securityTier ?? 'secure_cloud', + startsAt: at(-30), + endsAt: at(c.days - 30), + totalGpuHours: hours(c.gpuCount, c.days), + costPerGpuHourCents: c.costPerGpuHourCents, + takeOrPayFloorPct: c.takeOrPayFloorPct, + prepaidPct: c.prepaidPct, + usefulLifeYears: '5', + // A ramp: a quarter of the fleet for the first fortnight, then full. + // Present so the shape column is exercised by something other than a + // rectangle, which is the case it exists for. + shape: { + intervals: [at(-30).toISOString(), at(-16).toISOString(), at(c.days - 30).toISOString()], + quantities: [Math.round(c.gpuCount * 0.25), c.gpuCount], + }, + notes: 'Illustrative demo data. Not a real contract.', + }) + .returning(); + + if (commitment) commitmentIds.set(supplier.domain, commitment.id); + + // The paper behind it. + const [msa] = await db + .insert(contracts) + .values({ + accountId: account.id, + type: 'msa', + status: 'executed', + side: 'supply', + title: `${PREFIX}MSA — ${supplier.domain}`, + capacityCommitmentId: commitment?.id, + effectiveAt: at(-60), + expiresAt: at(c.days + 60), + isAutoRenew: true, + noticeDays: 90, + takeOrPayFloorPct: c.takeOrPayFloorPct, + prepaidPct: c.prepaidPct, + terminationTier: c.prepaidPct !== '0' ? '1_prepaid' : '2_take_or_pay', + governingLaw: 'New York', + }) + .returning(); + + if (msa) { + // A negotiated SLA with fee abatement — the remedy that actually matters + // on the supply side, and the one a credits-only model cannot express. + const [sla] = await db + .insert(contracts) + .values({ + accountId: account.id, + type: 'sla', + status: 'executed', + side: 'supply', + title: `${PREFIX}SLA — ${supplier.domain}`, + parentContractId: msa.id, + effectiveAt: at(-60), + }) + .returning(); + + if (sla) { + await db.insert(slaTerms).values({ + contractId: sla.id, + kind: 'negotiated', + uptimeTargetPct: '99.500', + measurementUnit: 'node', + measurementWindow: 'monthly', + remedyType: 'fee_abatement', + abatementTriggerValue: 2, + abatementTriggerUnit: 'business_days', + nodeReplacementHours: 24, + claimDeadlineValue: 30, + claimDeadlineUnit: 'days', + creditCapPct: '50.000', + sparePoolObligation: 'Spares held on site sufficient to replace failed nodes and switches.', + sparePoolScope: ['compute_nodes', 'network_switches'], + rcaDeliveryHours: 72, + maintenanceClasses: [ + { class: 'planned', noticeValue: 5, noticeUnit: 'business_days', excludedFromUptime: true }, + { class: 'emergency', noticeValue: 24, noticeUnit: 'hours', excludedFromUptime: false }, + ], + }); + } + + await db.insert(contractObligations).values({ + contractId: msa.id, + title: `${PREFIX}Renewal notice — ${supplier.domain}`, + kind: 'renewal_notice', + // Deliberately near-term on one supplier so the renewal alarm has + // something real to fire on. + dueAt: at(supplier.domain === 'nebius.com' ? 21 : 200), + description: '90 days notice required to prevent auto-renewal.', + }); + } + + await db.insert(supplyDeals).values({ + accountId: account.id, + siteId: site?.id, + name: `${PREFIX}${c.gpuCount}× ${c.gpuType} — ${supplier.domain}`, + stage: 'live', + gpuType: c.gpuType, + gpuCount: c.gpuCount, + interconnectType: c.interconnectType, + targetCostPerGpuHourCents: c.costPerGpuHourCents, + termMonths: Math.round(c.days / 30), + technicalVerdict: 'pass', + technicalNotes: 'NCCL all-reduce within 8% of theoretical across the full fabric.', + financialVerdict: 'pass', + financialNotes: `Break-even at ~${Math.round((c.costPerGpuHourCents / 0.75 / 100) * 100) / 100}/GPU-hr at 75% utilisation.`, + }); + } + + // A supply deal still in diligence, so that pipeline is not all `live`. + const [lambda] = await db + .select({ id: accounts.id }) + .from(accounts) + .where(eq(accounts.domain, 'lambda.ai')) + .limit(1); + if (lambda) { + await db + .insert(supplyDeals) + .values({ + accountId: lambda.id, + name: `${PREFIX}256× H200 — pricing`, + stage: 'financial_diligence', + gpuType: 'H200', + gpuCount: 256, + interconnectType: 'Infiniband', + targetCostPerGpuHourCents: 176, + termMonths: 12, + technicalVerdict: 'pass', + technicalNotes: 'Fabric verified. Storage throughput per GPU is below spec — flagged.', + financialNotes: 'Awaiting a firm quote on the committed tranche.', + }) + .onConflictDoNothing(); + } + + // ------------------------------------------------------------ demand side + for (const d of DEMAND) { + const [existingAccount] = await db + .select({ id: accounts.id }) + .from(accounts) + .where(eq(accounts.name, d.account)) + .limit(1); + if (existingAccount) continue; + + const [account] = await db + .insert(accounts) + .values({ + name: d.account, + side: 'demand', + customerSegment: d.segment, + country: d.country, + description: 'Fictional company, for demonstration only.', + source: 'seed', + confidence: 'confirmed', + lastActivityAt: at(-Math.random() * 10), + }) + .returning(); + if (!account) continue; + + const [contact] = await db + .insert(contacts) + .values({ + accountId: account.id, + fullName: d.contact.name, + title: d.contact.title, + affiliation: 'staff', + isDecisionMaker: d.contact.decisionMaker, + confidence: 'confirmed', + source: 'seed', + email: null, + }) + .returning(); + + const [deal] = await db + .insert(demandDeals) + .values({ + accountId: account.id, + name: d.deal.name, + productLine: d.deal.productLine, + stage: d.deal.stage, + acvCents: d.deal.acvCents, + tcvCents: Math.round((d.deal.acvCents * d.deal.termMonths) / 12), + termMonths: d.deal.termMonths, + msaExecuted: d.deal.msaExecuted, + dpaExecuted: d.deal.dpaExecuted, + primaryContactId: contact?.id, + expectedCloseDate: at(20 + Math.round(Math.random() * 60)), + probability: String( + { qualification: 0.1, legal: 0.35, proposal: 0.45, procurement: 0.6, poc: 0.7, deployment: 0.9 }[ + d.deal.stage + ] ?? 0.5, + ), + lastActivityAt: at(-Math.random() * 8), + }) + .returning(); + if (!deal) continue; + + if (d.request) { + await db.insert(capacityRequests).values({ + demandDealId: deal.id, + gpuType: d.request.gpuType, + gpuCount: d.request.gpuCount, + requiresHighSpeedInterconnect: d.request.fastFabric, + minInterconnectType: d.request.fastFabric ? 'Infiniband' : undefined, + maxPricePerGpuHourCents: d.request.maxPriceCents, + allowedRegions: d.request.allowedRegions ?? [], + requiredCertifications: d.request.certifications ?? [], + startsAt: at(15), + endsAt: at(15 + d.deal.termMonths * 30), + totalGpuHours: hours(d.request.gpuCount, d.deal.termMonths * 30, 1), + }); + } + + if (d.allocation) { + const commitmentId = commitmentIds.get(d.allocation.supplier); + if (commitmentId) { + const [commitment] = await db + .select() + .from(capacityCommitments) + .where(eq(capacityCommitments.id, commitmentId)) + .limit(1); + + if (commitment) { + await db.insert(allocations).values({ + capacityCommitmentId: commitmentId, + demandDealId: deal.id, + gpuHours: String( + Math.round(Number(commitment.totalGpuHours) * d.allocation.share), + ), + pricePerGpuHourCents: d.allocation.priceCents, + startsAt: commitment.startsAt, + endsAt: commitment.endsAt, + status: d.allocation.status, + guaranteeType: d.allocation.status === 'planned' ? 'committed' : 'guaranteed', + priority: d.allocation.status === 'planned' ? 100 : 10, + holdExpiresAt: d.allocation.holdDays ? at(d.allocation.holdDays) : null, + notes: d.account, + }); + } + } + } + + // A little history, so the activity feed is not empty. + await db.insert(activities).values([ + { + type: 'meeting', + subject: `${PREFIX}Technical scoping with ${d.contact.name}`, + accountId: account.id, + demandDealId: deal.id, + occurredAt: at(-12), + }, + { + type: 'note', + subject: `${PREFIX}${d.deal.stage} — next step agreed`, + accountId: account.id, + demandDealId: deal.id, + occurredAt: at(-3), + }, + ]); + } + + // Internal research burn against the largest block — real cost, no revenue. + const coreweave = commitmentIds.get('coreweave.com'); + if (coreweave) { + const [commitment] = await db + .select() + .from(capacityCommitments) + .where(eq(capacityCommitments.id, coreweave)) + .limit(1); + const RESEARCH_NOTE = `${PREFIX}Internal research consumption`; + const [existingResearch] = await db + .select({ id: allocations.id }) + .from(allocations) + .where( + and( + eq(allocations.capacityCommitmentId, coreweave), + eq(allocations.notes, RESEARCH_NOTE), + ), + ) + .limit(1); + + if (commitment && !existingResearch) { + await db + .insert(allocations) + .values({ + capacityCommitmentId: coreweave, + internalTeam: 'research', + gpuHours: String(Math.round(Number(commitment.totalGpuHours) * 0.08)), + pricePerGpuHourCents: 0, + startsAt: commitment.startsAt, + endsAt: commitment.endsAt, + status: 'active', + guaranteeType: 'internal', + priority: 200, + notes: RESEARCH_NOTE, + }); + } + } + + console.log(' 4 capacity commitments, with sites, MSAs and negotiated SLAs'); + console.log(' 6 demand deals across the pipeline, 5 supply deals'); + console.log(' Allocations including one unconverted hold and internal research burn'); + console.log('\nEverything is prefixed "DEMO — ". Remove it with: npm run db:demo -- --clear'); +} + +const shouldClear = process.argv.includes('--clear'); +(shouldClear ? clear() : seedDemo()) + .then(() => process.exit(0)) + .catch((error) => { + console.error('Demo seed failed:', error); + process.exit(1); + });