Files
helix-engage/scripts/seed-data.ts
saridsa2 cb4894ddc3 feat: Global E2E tests, multi-agent fixes, SIP agent tracing
- 13 Global Hospital smoke tests (CC Agent + Supervisor)
- Auto-unlock agent session in test setup via maint API
- agent-status-toggle sends agentId from localStorage (was missing)
- maint-otp-modal injects agentId from localStorage into all calls
- SIP manager logs agent identity on connect/disconnect/state changes
- seed-data.ts: added CC agent + marketing users, idempotent member
  creation, cleanup phase before seeding
- .gitignore: exclude test-results/ and playwright-report/

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 12:12:22 +05:30

595 lines
40 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Helix Engage — Platform Data Seeder
* Creates 2 clinics, 5 doctors with multi-clinic visit slots,
* 3 patient stories with fully linked records (campaigns, leads,
* calls, appointments, follow-ups, lead activities).
*
* Run: cd helix-engage && npx tsx scripts/seed-data.ts
* Env: SEED_GQL (graphql url), SEED_ORIGIN (workspace origin), SEED_SUB (workspace subdomain)
*
* Schema alignment (2026-04-10):
* - Doctor.visitingHours removed → replaced by DoctorVisitSlot entity
* - Doctor.portalUserId omitted (workspace member IDs are per-deployment)
* - Clinic entity added (needed for visit slot FK)
* NOTE: callNotes/visitNotes/clinicalNotes are RICH_TEXT — read-only, cannot seed
*/
const GQL = process.env.SEED_GQL ?? 'http://localhost:4000/graphql';
const SUB = process.env.SEED_SUB ?? 'fortytwo-dev';
const ORIGIN = process.env.SEED_ORIGIN ?? 'http://fortytwo-dev.localhost:4010';
let token = '';
function ago(days: number, hours = 0): string {
const d = new Date(); d.setDate(d.getDate() - days); d.setHours(d.getHours() - hours); return d.toISOString();
}
function future(days: number, hour = 10): string {
const d = new Date(); d.setDate(d.getDate() + days); d.setHours(hour, 0, 0, 0); return d.toISOString();
}
async function gql(query: string, variables?: any) {
const h: Record<string, string> = { 'Content-Type': 'application/json', 'X-Workspace-Subdomain': SUB };
if (token) h['Authorization'] = `Bearer ${token}`;
const r = await fetch(GQL, { method: 'POST', headers: h, body: JSON.stringify({ query, variables }) });
const d: any = await r.json();
if (d.errors) { console.error('❌', d.errors[0].message); throw new Error(d.errors[0].message); }
return d.data;
}
async function auth() {
const d1 = await gql(`mutation { getLoginTokenFromCredentials(email: "dev@fortytwo.dev", password: "tim@apple.dev", origin: "${ORIGIN}") { loginToken { token } } }`);
const lt = d1.getLoginTokenFromCredentials.loginToken.token;
const d2 = await gql(`mutation { getAuthTokensFromLoginToken(loginToken: "${lt}", origin: "${ORIGIN}") { tokens { accessOrWorkspaceAgnosticToken { token } } } }`);
token = d2.getAuthTokensFromLoginToken.tokens.accessOrWorkspaceAgnosticToken.token;
}
async function mk(entity: string, data: any): Promise<string> {
const cap = entity[0].toUpperCase() + entity.slice(1);
const d = await gql(`mutation($data: ${cap}CreateInput!) { create${cap}(data: $data) { id } }`, { data });
return d[`create${cap}`].id;
}
// Create a workspace member (user account) and return its workspace member id.
// Uses signUpInWorkspace + updateWorkspaceMember for name + updateWorkspaceMemberRole.
// The invite hash and role IDs are fetched once and cached.
let _inviteHash = '';
let _wsId = '';
const _roleIds: Record<string, string> = {};
async function ensureWorkspaceContext() {
if (_wsId) return;
const ws = await gql('{ currentWorkspace { id inviteHash } }');
_wsId = ws.currentWorkspace.id;
_inviteHash = ws.currentWorkspace.inviteHash;
const roles = await gql('{ getRoles { id label } }');
for (const r of roles.getRoles) _roleIds[r.label] = r.id;
}
async function mkMember(email: string, password: string, firstName: string, lastName: string, roleName?: string): Promise<string> {
await ensureWorkspaceContext();
// Check if already exists
const existing = await gql('{ workspaceMembers { edges { node { id userEmail } } } }');
const found = existing.workspaceMembers.edges.find((e: any) => e.node.userEmail.toLowerCase() === email.toLowerCase());
if (found) {
console.log(` (exists) ${email}${found.node.id}`);
return found.node.id;
}
// Create the user + link to workspace
await gql(
`mutation($email: String!, $password: String!, $workspaceId: UUID!, $workspaceInviteHash: String!) {
signUpInWorkspace(email: $email, password: $password, workspaceId: $workspaceId, workspaceInviteHash: $workspaceInviteHash) { workspace { id } }
}`,
{ email, password, workspaceId: _wsId, workspaceInviteHash: _inviteHash },
);
// Find the new member id
const members = await gql('{ workspaceMembers { edges { node { id userEmail } } } }');
const member = members.workspaceMembers.edges.find((e: any) => e.node.userEmail.toLowerCase() === email.toLowerCase());
if (!member) throw new Error(`Could not find workspace member for ${email}`);
const memberId = member.node.id;
// Set their display name
await gql(
`mutation($id: UUID!, $data: WorkspaceMemberUpdateInput!) { updateWorkspaceMember(id: $id, data: $data) { id } }`,
{ id: memberId, data: { name: { firstName, lastName } } },
);
// Assign role if specified
if (roleName && _roleIds[roleName]) {
await gql(
`mutation($wm: UUID!, $role: UUID!) { updateWorkspaceMemberRole(workspaceMemberId: $wm, roleId: $role) { id } }`,
{ wm: memberId, role: _roleIds[roleName] },
);
}
return memberId;
}
async function clearAll() {
// Delete in reverse dependency order
const entities = ['followUp', 'leadActivity', 'call', 'appointment', 'lead', 'patient', 'doctorVisitSlot', 'doctor', 'campaign', 'clinic'];
for (const entity of entities) {
const cap = entity[0].toUpperCase() + entity.slice(1);
try {
const data = await gql(`{ ${entity}s(first: 100) { edges { node { id } } } }`);
const ids: string[] = data[`${entity}s`].edges.map((e: any) => e.node.id);
if (ids.length === 0) { console.log(` ${cap}: 0 records`); continue; }
for (const id of ids) {
await gql(`mutation { delete${cap}(id: "${id}") { id } }`);
}
console.log(` ${cap}: deleted ${ids.length}`);
} catch (err: any) {
console.log(` ${cap}: skip (${err.message?.slice(0, 60)})`);
}
}
}
async function main() {
console.log('🌱 Seeding Helix Engage demo data...\n');
await auth();
console.log('✅ Auth OK\n');
// Clean slate — remove all existing entity data (not users)
console.log('🧹 Clearing existing data...');
await clearAll();
console.log('');
await auth();
// ═══════════════════════════════════════════
// CLINICS (needed for doctor visit slots)
// ═══════════════════════════════════════════
console.log('🏥 Clinics');
const clinicKor = await mk('clinic', {
name: 'Global Hospital — Koramangala',
clinicName: 'Global Hospital — Koramangala',
status: 'ACTIVE',
opensAt: '08:00', closesAt: '20:00',
openMonday: true, openTuesday: true, openWednesday: true,
openThursday: true, openFriday: true, openSaturday: true, openSunday: false,
phone: { primaryPhoneNumber: '8041763265', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
addressCustom: { addressCity: 'Bangalore', addressState: 'Karnataka', addressCountry: 'India', addressStreet1: 'Koramangala 4th Block' },
onlineBooking: true, walkInAllowed: true, acceptsCash: 'YES', acceptsCard: 'YES', acceptsUpi: 'YES',
});
console.log(` Koramangala: ${clinicKor}`);
const clinicWf = await mk('clinic', {
name: 'Global Hospital — Whitefield',
clinicName: 'Global Hospital — Whitefield',
status: 'ACTIVE',
opensAt: '09:00', closesAt: '18:00',
openMonday: true, openTuesday: true, openWednesday: true,
openThursday: true, openFriday: true, openSaturday: true, openSunday: false,
phone: { primaryPhoneNumber: '8041763400', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
addressCustom: { addressCity: 'Bangalore', addressState: 'Karnataka', addressCountry: 'India', addressStreet1: 'ITPL Main Road, Whitefield' },
onlineBooking: true, walkInAllowed: true, acceptsCash: 'YES', acceptsCard: 'YES', acceptsUpi: 'YES',
});
console.log(` Whitefield: ${clinicWf}\n`);
await auth();
// ═══════════════════════════════════════════
// CALL CENTER & MARKETING STAFF
//
// CC agents (HelixEngage User role) handle inbound/outbound calls.
// Marketing executives and supervisors use HelixEngage Supervisor role.
// Email domain uses globalcare.com to match the deployment.
// ═══════════════════════════════════════════
console.log('📞 Call center & marketing staff');
const wmRekha = await mkMember('rekha.cc@globalcare.com', 'Global@123', 'Rekha', 'Nair', 'HelixEngage User');
console.log(` Rekha (CC Agent): ${wmRekha}`);
const wmGanesh = await mkMember('ganesh.cc@globalcare.com', 'Global@123', 'Ganesh', 'Iyer', 'HelixEngage User');
console.log(` Ganesh (CC Agent): ${wmGanesh}`);
const wmSanjay = await mkMember('sanjay.marketing@globalcare.com', 'Global@123', 'Sanjay', 'Verma', 'HelixEngage Supervisor');
console.log(` Sanjay (Marketing): ${wmSanjay}`);
const wmRamesh = await mkMember('dr.ramesh@globalcare.com', 'Global@123', 'Ramesh', 'Gupta', 'HelixEngage Supervisor');
console.log(` Dr. Ramesh (Supervisor): ${wmRamesh}\n`);
await auth();
// ═══════════════════════════════════════════
// DOCTOR WORKSPACE MEMBERS
//
// Each doctor gets a real platform login so they can access the
// portal. Created via signUpInWorkspace, then linked to the Doctor
// entity via portalUserId. Email domain matches the deployment.
// ═══════════════════════════════════════════
console.log('👤 Doctor workspace members (role: HelixEngage Manager)');
const wmSharma = await mkMember('dr.sharma@globalcare.com', 'DrSharma@2026', 'Arun', 'Sharma', 'HelixEngage Manager');
console.log(` Dr. Sharma member: ${wmSharma}`);
const wmPatel = await mkMember('dr.patel@globalcare.com', 'DrPatel@2026', 'Meena', 'Patel', 'HelixEngage Manager');
console.log(` Dr. Patel member: ${wmPatel}`);
const wmKumar = await mkMember('dr.kumar@globalcare.com', 'DrKumar@2026', 'Rajesh', 'Kumar', 'HelixEngage Manager');
console.log(` Dr. Kumar member: ${wmKumar}`);
const wmReddy = await mkMember('dr.reddy@globalcare.com', 'DrReddy@2026', 'Lakshmi', 'Reddy', 'HelixEngage Manager');
console.log(` Dr. Reddy member: ${wmReddy}`);
const wmSingh = await mkMember('dr.singh@globalcare.com', 'DrSingh@2026', 'Harpreet', 'Singh', 'HelixEngage Manager');
console.log(` Dr. Singh member: ${wmSingh}\n`);
await auth();
// ═══════════════════════════════════════════
// DOCTORS (linked to workspace members via portalUserId)
//
// visitingHours was removed — multi-clinic schedules now live
// on DoctorVisitSlot (seeded below).
// ═══════════════════════════════════════════
console.log('👨‍⚕️ Doctors');
const drSharma = await mk('doctor', {
name: 'Dr. Arun Sharma',
fullName: { firstName: 'Arun', lastName: 'Sharma' },
department: 'CARDIOLOGY',
specialty: 'Interventional Cardiology',
qualifications: 'MBBS, MD (Medicine), DM (Cardiology), FACC',
yearsOfExperience: 18,
consultationFeeNew: { amountMicros: 800_000_000, currencyCode: 'INR' },
consultationFeeFollowUp: { amountMicros: 500_000_000, currencyCode: 'INR' },
phone: { primaryPhoneNumber: '9900100001', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
email: { primaryEmail: 'dr.sharma@globalcare.com' },
registrationNumber: 'KMC-45672',
active: true,
portalUserId: wmSharma,
});
console.log(` Dr. Sharma (Cardiology → ${wmSharma}): ${drSharma}`);
const drPatel = await mk('doctor', {
name: 'Dr. Meena Patel',
fullName: { firstName: 'Meena', lastName: 'Patel' },
department: 'GYNECOLOGY',
specialty: 'Reproductive Medicine & IVF',
qualifications: 'MBBS, MS (OBG), Fellowship in Reproductive Medicine',
yearsOfExperience: 15,
consultationFeeNew: { amountMicros: 700_000_000, currencyCode: 'INR' },
consultationFeeFollowUp: { amountMicros: 400_000_000, currencyCode: 'INR' },
phone: { primaryPhoneNumber: '9900100002', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
email: { primaryEmail: 'dr.patel@globalcare.com' },
registrationNumber: 'KMC-38291',
active: true,
portalUserId: wmPatel,
});
console.log(` Dr. Patel (Gynecology/IVF → ${wmPatel}): ${drPatel}`);
const drKumar = await mk('doctor', {
name: 'Dr. Rajesh Kumar',
fullName: { firstName: 'Rajesh', lastName: 'Kumar' },
department: 'ORTHOPEDICS',
specialty: 'Joint Replacement & Sports Medicine',
qualifications: 'MBBS, MS (Ortho), Fellowship in Arthroplasty',
yearsOfExperience: 12,
consultationFeeNew: { amountMicros: 600_000_000, currencyCode: 'INR' },
consultationFeeFollowUp: { amountMicros: 400_000_000, currencyCode: 'INR' },
phone: { primaryPhoneNumber: '9900100003', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
email: { primaryEmail: 'dr.kumar@globalcare.com' },
registrationNumber: 'KMC-51003',
active: true,
portalUserId: wmKumar,
});
console.log(` Dr. Kumar (Orthopedics → ${wmKumar}): ${drKumar}`);
const drReddy = await mk('doctor', {
name: 'Dr. Lakshmi Reddy',
fullName: { firstName: 'Lakshmi', lastName: 'Reddy' },
department: 'GENERAL_MEDICINE',
specialty: 'Internal Medicine & Preventive Health',
qualifications: 'MBBS, MD (General Medicine)',
yearsOfExperience: 20,
consultationFeeNew: { amountMicros: 500_000_000, currencyCode: 'INR' },
consultationFeeFollowUp: { amountMicros: 300_000_000, currencyCode: 'INR' },
phone: { primaryPhoneNumber: '9900100004', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
email: { primaryEmail: 'dr.reddy@globalcare.com' },
registrationNumber: 'KMC-22145',
active: true,
portalUserId: wmReddy,
});
console.log(` Dr. Reddy (General Medicine → ${wmReddy}): ${drReddy}`);
const drSingh = await mk('doctor', {
name: 'Dr. Harpreet Singh',
fullName: { firstName: 'Harpreet', lastName: 'Singh' },
department: 'ENT',
specialty: 'Otorhinolaryngology & Head/Neck Surgery',
qualifications: 'MBBS, MS (ENT), DNB',
yearsOfExperience: 10,
consultationFeeNew: { amountMicros: 600_000_000, currencyCode: 'INR' },
consultationFeeFollowUp: { amountMicros: 400_000_000, currencyCode: 'INR' },
phone: { primaryPhoneNumber: '9900100005', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
email: { primaryEmail: 'dr.singh@globalcare.com' },
registrationNumber: 'KMC-60782',
active: true,
portalUserId: wmSingh,
});
console.log(` Dr. Singh (ENT → ${wmSingh}): ${drSingh}\n`);
await auth();
// ═══════════════════════════════════════════
// DOCTOR VISIT SLOTS (weekly schedule per doctor × clinic)
// ═══════════════════════════════════════════
console.log('📅 Visit Slots');
const slots: Array<{ doc: string; docName: string; clinic: string; clinicName: string; day: string; start: string; end: string }> = [
// Dr. Sharma — Koramangala Mon/Wed/Fri 10:0013:00
{ doc: drSharma, docName: 'Sharma', clinic: clinicKor, clinicName: 'Kor', day: 'MONDAY', start: '10:00', end: '13:00' },
{ doc: drSharma, docName: 'Sharma', clinic: clinicKor, clinicName: 'Kor', day: 'WEDNESDAY', start: '10:00', end: '13:00' },
{ doc: drSharma, docName: 'Sharma', clinic: clinicKor, clinicName: 'Kor', day: 'FRIDAY', start: '10:00', end: '13:00' },
// Dr. Patel — Whitefield Tue/Thu/Sat 9:0012:00
{ doc: drPatel, docName: 'Patel', clinic: clinicWf, clinicName: 'WF', day: 'TUESDAY', start: '09:00', end: '12:00' },
{ doc: drPatel, docName: 'Patel', clinic: clinicWf, clinicName: 'WF', day: 'THURSDAY', start: '09:00', end: '12:00' },
{ doc: drPatel, docName: 'Patel', clinic: clinicWf, clinicName: 'WF', day: 'SATURDAY', start: '09:00', end: '12:00' },
// Dr. Kumar — Koramangala MonFri 14:0017:00
{ doc: drKumar, docName: 'Kumar', clinic: clinicKor, clinicName: 'Kor', day: 'MONDAY', start: '14:00', end: '17:00' },
{ doc: drKumar, docName: 'Kumar', clinic: clinicKor, clinicName: 'Kor', day: 'TUESDAY', start: '14:00', end: '17:00' },
{ doc: drKumar, docName: 'Kumar', clinic: clinicKor, clinicName: 'Kor', day: 'WEDNESDAY', start: '14:00', end: '17:00' },
{ doc: drKumar, docName: 'Kumar', clinic: clinicKor, clinicName: 'Kor', day: 'THURSDAY', start: '14:00', end: '17:00' },
{ doc: drKumar, docName: 'Kumar', clinic: clinicKor, clinicName: 'Kor', day: 'FRIDAY', start: '14:00', end: '17:00' },
// Dr. Reddy — both clinics MonSat
{ doc: drReddy, docName: 'Reddy', clinic: clinicKor, clinicName: 'Kor', day: 'MONDAY', start: '09:00', end: '13:00' },
{ doc: drReddy, docName: 'Reddy', clinic: clinicKor, clinicName: 'Kor', day: 'WEDNESDAY', start: '09:00', end: '13:00' },
{ doc: drReddy, docName: 'Reddy', clinic: clinicKor, clinicName: 'Kor', day: 'FRIDAY', start: '09:00', end: '13:00' },
{ doc: drReddy, docName: 'Reddy', clinic: clinicWf, clinicName: 'WF', day: 'TUESDAY', start: '14:00', end: '18:00' },
{ doc: drReddy, docName: 'Reddy', clinic: clinicWf, clinicName: 'WF', day: 'THURSDAY', start: '14:00', end: '18:00' },
{ doc: drReddy, docName: 'Reddy', clinic: clinicWf, clinicName: 'WF', day: 'SATURDAY', start: '14:00', end: '18:00' },
// Dr. Singh — Whitefield Mon/Wed/Fri 11:0015:00
{ doc: drSingh, docName: 'Singh', clinic: clinicWf, clinicName: 'WF', day: 'MONDAY', start: '11:00', end: '15:00' },
{ doc: drSingh, docName: 'Singh', clinic: clinicWf, clinicName: 'WF', day: 'WEDNESDAY', start: '11:00', end: '15:00' },
{ doc: drSingh, docName: 'Singh', clinic: clinicWf, clinicName: 'WF', day: 'FRIDAY', start: '11:00', end: '15:00' },
];
for (const s of slots) {
await mk('doctorVisitSlot', {
name: `Dr. ${s.docName}${s.day} ${s.start}${s.end} (${s.clinicName})`,
doctorId: s.doc, clinicId: s.clinic,
dayOfWeek: s.day, startTime: s.start, endTime: s.end,
});
}
console.log(` ${slots.length} visit slots created\n`);
await auth();
// ═══════════════════════════════════════════
// CAMPAIGNS
// ═══════════════════════════════════════════
console.log('📢 Campaigns');
const wdCamp = await mk('campaign', {
name: "Women's Day Health Checkup", campaignName: "Women's Day Health Checkup",
status: 'ACTIVE', platform: 'FACEBOOK', startDate: ago(14), endDate: future(16),
budget: { amountMicros: 200_000_000_000, currencyCode: 'INR' },
amountSpent: { amountMicros: 130_000_000_000, currencyCode: 'INR' },
impressions: 82000, clicks: 2100, targetCount: 500, contacted: 89, converted: 24, leadsGenerated: 148,
externalCampaignId: 'fb_camp_28491',
});
console.log(` Women's Day: ${wdCamp}`);
const csCamp = await mk('campaign', {
name: 'Cervical Cancer Screening Drive', campaignName: 'Cervical Cancer Screening Drive',
status: 'ACTIVE', platform: 'GOOGLE', startDate: ago(10), endDate: future(28),
budget: { amountMicros: 150_000_000_000, currencyCode: 'INR' },
amountSpent: { amountMicros: 57_500_000_000, currencyCode: 'INR' },
impressions: 45000, clicks: 1300, targetCount: 300, contacted: 31, converted: 8, leadsGenerated: 56,
externalCampaignId: 'ggl_camp_44102',
});
console.log(` Cervical Screening: ${csCamp}`);
const ivfCamp = await mk('campaign', {
name: 'IVF Consultation — Free First Visit', campaignName: 'IVF Consultation — Free First Visit',
status: 'ACTIVE', platform: 'FACEBOOK', startDate: ago(23), endDate: future(13),
budget: { amountMicros: 300_000_000_000, currencyCode: 'INR' },
amountSpent: { amountMicros: 246_000_000_000, currencyCode: 'INR' },
impressions: 156000, clicks: 4200, targetCount: 500, contacted: 198, converted: 52, leadsGenerated: 312,
externalCampaignId: 'fb_camp_28555',
});
console.log(` IVF: ${ivfCamp}\n`);
await auth();
// ═══════════════════════════════════════════
// PATIENTS
// ═══════════════════════════════════════════
console.log('🏥 Patients');
const priyaPat = await mk('patient', {
name: 'Priya Sharma',
fullName: { firstName: 'Priya', lastName: 'Sharma' },
phones: { primaryPhoneNumber: '9949879837', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
emails: { primaryEmail: 'priya.sharma@gmail.com' },
dateOfBirth: '1994-05-15', gender: 'FEMALE', patientType: 'RETURNING',
});
console.log(` Priya Sharma: ${priyaPat}`);
const deepaPat = await mk('patient', {
name: 'Deepa Rao',
fullName: { firstName: 'Deepa', lastName: 'Rao' },
phones: { primaryPhoneNumber: '7654321098', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
dateOfBirth: '1997-08-22', gender: 'FEMALE', patientType: 'NEW',
});
console.log(` Deepa Rao: ${deepaPat}`);
const vijayPat = await mk('patient', {
name: 'Vijay Nair',
fullName: { firstName: 'Vijay', lastName: 'Nair' },
phones: { primaryPhoneNumber: '6543210987', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
emails: { primaryEmail: 'vijay.n@gmail.com' },
dateOfBirth: '1971-02-10', gender: 'MALE', patientType: 'RETURNING',
});
console.log(` Vijay Nair: ${vijayPat}\n`);
await auth();
// ═══════════════════════════════════════════
// LEADS (linked to campaigns + patients)
// ═══════════════════════════════════════════
console.log('👥 Leads');
const priyaLead = await mk('lead', {
name: 'Priya Sharma',
contactName: { firstName: 'Priya', lastName: 'Sharma' },
contactPhone: { primaryPhoneNumber: '9949879837', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
contactEmail: { primaryEmail: 'priya.sharma@gmail.com' },
source: 'FACEBOOK_AD', status: 'APPOINTMENT_SET', interestedService: 'IVF Consultation',
assignedAgent: 'Rekha S', spamScore: 5, isSpam: false, isDuplicate: false,
firstContacted: ago(12), lastContacted: ago(2), contactAttempts: 3, leadScore: 92,
aiSummary: 'Returning IVF patient. Had first consultation with Dr. Patel last week. Next appointment in 3 days. Very responsive — all 3 calls answered. Interested in premium IVF package.',
aiSuggestedAction: 'Confirm upcoming appointment and discuss treatment timeline',
campaignId: ivfCamp, patientId: priyaPat,
utmSource: 'facebook', utmMedium: 'paid', utmCampaign: 'ivf-free-consult',
});
console.log(` Priya Sharma (phone: 9949879837): ${priyaLead}`);
const raviLead = await mk('lead', {
name: 'Ravi Kumar',
contactName: { firstName: 'Ravi', lastName: 'Kumar' },
contactPhone: { primaryPhoneNumber: '6309248884', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
contactEmail: { primaryEmail: 'ravi.k@yahoo.com' },
source: 'GOOGLE_AD', status: 'NEW', interestedService: 'Cervical Screening',
assignedAgent: 'Rekha S', spamScore: 8, isSpam: false, contactAttempts: 0, leadScore: 75,
aiSummary: 'New lead from Google Ads cervical screening campaign. Missed call 18 hours ago — no callback. SLA breached. Urgent follow-up required.',
aiSuggestedAction: 'Urgent callback — missed call SLA breached',
campaignId: csCamp, utmSource: 'google', utmMedium: 'paid',
});
console.log(` Ravi Kumar (phone: 6309248884): ${raviLead}`);
const deepaLead = await mk('lead', {
name: 'Deepa Rao',
contactName: { firstName: 'Deepa', lastName: 'Rao' },
contactPhone: { primaryPhoneNumber: '7654321098', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
source: 'WALK_IN', status: 'CONVERTED', interestedService: 'IVF Package',
assignedAgent: 'Rekha S', convertedAt: ago(2), firstContacted: ago(7),
lastContacted: ago(2), contactAttempts: 2, leadScore: 88,
aiSummary: 'Walk-in converted to IVF patient. Completed first consultation with Dr. Patel 2 days ago. Interested in premium IVF package.',
aiSuggestedAction: 'Follow up on treatment plan discussion',
campaignId: ivfCamp, patientId: deepaPat,
});
console.log(` Deepa Rao: ${deepaLead}`);
const vijayLead = await mk('lead', {
name: 'Vijay Nair',
contactName: { firstName: 'Vijay', lastName: 'Nair' },
contactPhone: { primaryPhoneNumber: '6543210987', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
contactEmail: { primaryEmail: 'vijay.n@gmail.com' },
source: 'REFERRAL', status: 'APPOINTMENT_SET', interestedService: 'Cardiology Consultation',
assignedAgent: 'Rekha S', firstContacted: ago(20), lastContacted: ago(3),
contactAttempts: 4, leadScore: 85,
aiSummary: 'Regular cardiology patient under Dr. Sharma. 2 completed visits, good outcomes. Upcoming quarterly check-up in 5 days. Cancelled once before — confirm proactively.',
aiSuggestedAction: 'Confirm upcoming appointment — previous cancellation history',
patientId: vijayPat,
});
console.log(` Vijay Nair: ${vijayLead}`);
const kavithaLead = await mk('lead', {
name: 'Kavitha S',
contactName: { firstName: 'Kavitha', lastName: 'S' },
contactPhone: { primaryPhoneNumber: '9845123456', primaryPhoneCallingCode: '+91', primaryPhoneCountryCode: 'IN' },
source: 'FACEBOOK_AD', status: 'NEW', interestedService: "Women's Health Checkup",
spamScore: 3, isSpam: false, contactAttempts: 0, leadScore: 80,
aiSummary: "Brand new lead from today's Women's Day Facebook campaign. No contact yet.",
aiSuggestedAction: 'Send WhatsApp template and assign to call center',
campaignId: wdCamp, utmSource: 'facebook', utmMedium: 'paid',
});
console.log(` Kavitha S: ${kavithaLead}\n`);
await auth();
// ═══════════════════════════════════════════
// APPOINTMENTS (linked to patients AND doctors)
// ═══════════════════════════════════════════
console.log('📅 Appointments');
await mk('appointment', { name: 'Priya — IVF Consultation', scheduledAt: ago(5), durationMin: 30, appointmentType: 'CONSULTATION', status: 'COMPLETED', doctorName: 'Dr. Patel', department: 'Gynecology', reasonForVisit: 'IVF initial consultation — fertility assessment', patientId: priyaPat, doctorId: drPatel });
console.log(' Priya × Dr. Patel — completed IVF consultation (5d ago)');
await mk('appointment', { name: 'Priya — IVF Follow-up', scheduledAt: future(3, 10), durationMin: 45, appointmentType: 'FOLLOW_UP', status: 'SCHEDULED', doctorName: 'Dr. Patel', department: 'Gynecology', reasonForVisit: 'IVF follow-up — treatment plan discussion', patientId: priyaPat, doctorId: drPatel });
console.log(' Priya × Dr. Patel — upcoming follow-up (in 3d)');
await mk('appointment', { name: 'Deepa — IVF Consultation', scheduledAt: ago(2), durationMin: 30, appointmentType: 'CONSULTATION', status: 'COMPLETED', doctorName: 'Dr. Patel', department: 'Gynecology', reasonForVisit: 'IVF package consultation — walk-in referral', patientId: deepaPat, doctorId: drPatel });
console.log(' Deepa × Dr. Patel — completed consultation (2d ago)');
await mk('appointment', { name: 'Vijay — Cardiology Initial', scheduledAt: ago(14), durationMin: 30, appointmentType: 'CONSULTATION', status: 'COMPLETED', doctorName: 'Dr. Sharma', department: 'Cardiology', reasonForVisit: 'Initial assessment — chest discomfort, family history', patientId: vijayPat, doctorId: drSharma });
console.log(' Vijay × Dr. Sharma — completed initial (14d ago)');
await mk('appointment', { name: 'Vijay — Echo Review', scheduledAt: ago(7), durationMin: 30, appointmentType: 'FOLLOW_UP', status: 'COMPLETED', doctorName: 'Dr. Sharma', department: 'Cardiology', reasonForVisit: 'Echocardiogram review — all clear', patientId: vijayPat, doctorId: drSharma });
console.log(' Vijay × Dr. Sharma — completed echo (7d ago)');
await mk('appointment', { name: 'Vijay — Quarterly Check-up', scheduledAt: future(5, 11), durationMin: 30, appointmentType: 'FOLLOW_UP', status: 'SCHEDULED', doctorName: 'Dr. Sharma', department: 'Cardiology', reasonForVisit: 'Quarterly cardiology check-up', patientId: vijayPat, doctorId: drSharma });
console.log(' Vijay × Dr. Sharma — upcoming quarterly (in 5d)\n');
await auth();
// ═══════════════════════════════════════════
// CALLS (linked to leads)
// ═══════════════════════════════════════════
console.log('📞 Calls');
await mk('call', { name: 'Priya — IVF Enquiry', direction: 'INBOUND', callStatus: 'COMPLETED', agentName: 'Rekha S', startedAt: ago(12), endedAt: ago(12), durationSec: 240, disposition: 'INFO_PROVIDED', leadId: priyaLead });
console.log(' Priya — inbound enquiry (12d ago)');
await mk('call', { name: 'Priya — Appt Booked', direction: 'OUTBOUND', callStatus: 'COMPLETED', agentName: 'Rekha S', startedAt: ago(6), endedAt: ago(6), durationSec: 180, disposition: 'APPOINTMENT_BOOKED', leadId: priyaLead });
console.log(' Priya — appointment booked (6d ago)');
await mk('call', { name: 'Priya — Reminder', direction: 'OUTBOUND', callStatus: 'COMPLETED', agentName: 'Rekha S', startedAt: ago(2), endedAt: ago(2), durationSec: 120, disposition: 'FOLLOW_UP_SCHEDULED', leadId: priyaLead });
console.log(' Priya — reminder (2d ago)');
await mk('call', { name: 'Ravi — Missed Call', direction: 'INBOUND', callStatus: 'MISSED', agentName: 'Rekha S', startedAt: ago(0, 18), durationSec: 0, leadId: raviLead });
console.log(' Ravi — missed inbound (18h ago)');
await mk('call', { name: 'Deepa — Walk-in Enquiry', direction: 'INBOUND', callStatus: 'COMPLETED', agentName: 'Rekha S', startedAt: ago(7), endedAt: ago(7), durationSec: 300, disposition: 'INFO_PROVIDED', leadId: deepaLead });
console.log(' Deepa — enquiry (7d ago)');
await mk('call', { name: 'Deepa — Appt Booked', direction: 'OUTBOUND', callStatus: 'COMPLETED', agentName: 'Rekha S', startedAt: ago(3), endedAt: ago(3), durationSec: 150, disposition: 'APPOINTMENT_BOOKED', leadId: deepaLead });
console.log(' Deepa — booked (3d ago)');
await mk('call', { name: 'Vijay — Referral Call', direction: 'INBOUND', callStatus: 'COMPLETED', agentName: 'Rekha S', startedAt: ago(20), endedAt: ago(20), durationSec: 210, disposition: 'APPOINTMENT_BOOKED', leadId: vijayLead });
console.log(' Vijay — referral (20d ago)');
await mk('call', { name: 'Vijay — Follow-up', direction: 'OUTBOUND', callStatus: 'COMPLETED', agentName: 'Rekha S', startedAt: ago(10), endedAt: ago(10), durationSec: 90, disposition: 'FOLLOW_UP_SCHEDULED', leadId: vijayLead });
console.log(' Vijay — follow-up (10d ago)');
await mk('call', { name: 'Vijay — No Answer', direction: 'OUTBOUND', callStatus: 'COMPLETED', agentName: 'Rekha S', startedAt: ago(5), endedAt: ago(5), durationSec: 0, disposition: 'NO_ANSWER', leadId: vijayLead });
console.log(' Vijay — no answer (5d ago)');
await mk('call', { name: 'Vijay — Quarterly Confirmed', direction: 'OUTBOUND', callStatus: 'COMPLETED', agentName: 'Rekha S', startedAt: ago(3), endedAt: ago(3), durationSec: 180, disposition: 'APPOINTMENT_BOOKED', leadId: vijayLead });
console.log(' Vijay — confirmed (3d ago)\n');
await auth();
// ═══════════════════════════════════════════
// LEAD ACTIVITIES (full journey timeline)
// ═══════════════════════════════════════════
console.log('📋 Activities');
const acts: any[] = [
// Priya journey (8 activities)
{ name: 'Lead created', activityType: 'STATUS_CHANGE', summary: 'Lead created from Facebook IVF campaign', occurredAt: ago(14), performedBy: 'System', channel: 'SYSTEM', leadId: priyaLead },
{ name: 'Assigned', activityType: 'ASSIGNED', summary: 'Auto-assigned to Rekha S via round-robin', occurredAt: ago(14), performedBy: 'System', channel: 'SYSTEM', newValue: 'Rekha S', leadId: priyaLead },
{ name: 'WhatsApp sent', activityType: 'WHATSAPP_SENT', summary: 'IVF consultation template sent via WhatsApp', occurredAt: ago(13), performedBy: 'Sanjay Kumar', channel: 'WHATSAPP', leadId: priyaLead },
{ name: 'Enquiry call', activityType: 'CALL_RECEIVED', summary: 'Inbound call — enquired about IVF costs and process', occurredAt: ago(12), performedBy: 'Rekha S', channel: 'PHONE', leadId: priyaLead },
{ name: 'Status → CONTACTED', activityType: 'STATUS_CHANGE', summary: 'NEW → CONTACTED', occurredAt: ago(12), performedBy: 'System', channel: 'SYSTEM', previousValue: 'NEW', newValue: 'CONTACTED', leadId: priyaLead },
{ name: 'Appt booked', activityType: 'APPOINTMENT_BOOKED', summary: 'Booked IVF consultation with Dr. Patel at Indiranagar', occurredAt: ago(6), performedBy: 'Rekha S', channel: 'PHONE', leadId: priyaLead },
{ name: 'Status → APPOINTMENT_SET', activityType: 'STATUS_CHANGE', summary: 'CONTACTED → APPOINTMENT_SET', occurredAt: ago(6), performedBy: 'System', channel: 'SYSTEM', previousValue: 'CONTACTED', newValue: 'APPOINTMENT_SET', leadId: priyaLead },
{ name: 'Confirmed', activityType: 'NOTE_ADDED', summary: 'Patient confirmed follow-up attendance. Interested in premium IVF package (₹1.5L).', occurredAt: ago(2), performedBy: 'Rekha S', channel: 'PHONE', leadId: priyaLead },
// Ravi journey (3 activities)
{ name: 'Lead created', activityType: 'STATUS_CHANGE', summary: 'Lead created from Google cervical screening campaign', occurredAt: ago(3), performedBy: 'System', channel: 'SYSTEM', leadId: raviLead },
{ name: 'Assigned', activityType: 'ASSIGNED', summary: 'Assigned to Rekha S', occurredAt: ago(3), performedBy: 'System', channel: 'SYSTEM', newValue: 'Rekha S', leadId: raviLead },
{ name: 'Missed call', activityType: 'CALL_RECEIVED', summary: 'Missed inbound call — all agents busy', occurredAt: ago(0, 18), performedBy: 'System', channel: 'PHONE', leadId: raviLead },
// Deepa journey (4 activities)
{ name: 'Walk-in created', activityType: 'STATUS_CHANGE', summary: 'Walk-in enquiry lead created', occurredAt: ago(7), performedBy: 'System', channel: 'IN_PERSON', leadId: deepaLead },
{ name: 'IVF enquiry', activityType: 'CALL_RECEIVED', summary: 'Walk-in enquiry — interested in IVF packages and Dr. Patel availability', occurredAt: ago(7), performedBy: 'Rekha S', channel: 'PHONE', leadId: deepaLead },
{ name: 'Appt booked', activityType: 'APPOINTMENT_BOOKED', summary: 'Booked IVF consultation with Dr. Patel at Koramangala', occurredAt: ago(3), performedBy: 'Rekha S', channel: 'PHONE', leadId: deepaLead },
{ name: 'Converted', activityType: 'CONVERTED', summary: 'Completed first IVF consultation — converted to active patient', occurredAt: ago(2), performedBy: 'System', channel: 'SYSTEM', leadId: deepaLead },
// Vijay journey (6 activities)
{ name: 'Referral created', activityType: 'STATUS_CHANGE', summary: 'Referral lead — family doctor recommended cardiology check', occurredAt: ago(21), performedBy: 'System', channel: 'SYSTEM', leadId: vijayLead },
{ name: 'Initial booked', activityType: 'APPOINTMENT_BOOKED', summary: 'Cardiology consultation with Dr. Sharma at Koramangala booked', occurredAt: ago(20), performedBy: 'Rekha S', channel: 'PHONE', leadId: vijayLead },
{ name: 'Clinical note', activityType: 'NOTE_ADDED', summary: 'Family history of cardiac issues. Dr. Sharma ordered echocardiogram. Patient compliant.', occurredAt: ago(14), performedBy: 'Rekha S', channel: 'SYSTEM', leadId: vijayLead },
{ name: 'Follow-up booked', activityType: 'APPOINTMENT_BOOKED', summary: 'Echo review follow-up with Dr. Sharma booked', occurredAt: ago(10), performedBy: 'Rekha S', channel: 'PHONE', leadId: vijayLead },
{ name: 'Cancellation note', activityType: 'NOTE_ADDED', summary: 'Patient cancelled echo review once (work conflict) — successfully rescheduled to next day', occurredAt: ago(8), performedBy: 'Rekha S', channel: 'PHONE', leadId: vijayLead },
{ name: 'Quarterly confirmed', activityType: 'APPOINTMENT_BOOKED', summary: 'Quarterly cardiology check-up with Dr. Sharma confirmed', occurredAt: ago(3), performedBy: 'Rekha S', channel: 'PHONE', leadId: vijayLead },
// Kavitha (1 activity)
{ name: 'Lead created', activityType: 'STATUS_CHANGE', summary: "New lead from Women's Day Facebook campaign", occurredAt: ago(0, 2), performedBy: 'System', channel: 'SYSTEM', leadId: kavithaLead },
];
for (const a of acts) await mk('leadActivity', a);
console.log(` ${acts.length} activities created\n`);
await auth();
// ═══════════════════════════════════════════
// FOLLOW-UPS
// ═══════════════════════════════════════════
console.log('🔔 Follow-ups');
await mk('followUp', { name: 'Ravi — Urgent Callback', typeCustom: 'CALLBACK', status: 'OVERDUE', scheduledAt: ago(0, 6), assignedAgent: 'Rekha S', priority: 'URGENT' });
console.log(' Ravi — overdue callback (6h ago, URGENT)');
await mk('followUp', { name: 'Vijay — Appointment Reminder', typeCustom: 'APPOINTMENT_REMINDER', status: 'PENDING', scheduledAt: future(1, 9), assignedAgent: 'Rekha S', priority: 'HIGH' });
console.log(' Vijay — appointment reminder (tomorrow 9am)\n');
console.log('🎉 Seed complete!');
console.log(' 2 clinics · 5 doctors · 20 visit slots · 3 campaigns');
console.log(' 3 patients · 5 leads · 6 appointments · 10 calls · 22 activities · 2 follow-ups');
console.log(' Demo phones: Priya=9949879837, Ravi=6309248884');
console.log(' Doctors linked to clinics via visit slots (multi-clinic schedule)');
}
main().catch(e => { console.error('💥', e.message); process.exit(1); });