/** * Test script — simulates a CC agent (Rekha) receiving a call and using the AI assistant. * Tests: auth → lead lookup → patient data → doctor data → appointments → calls → activities → AI response * * Run: cd helix-engage && npx tsx scripts/test-ai-flow.ts */ const GQL = 'http://localhost:4000/graphql'; const SUB = 'fortytwo-dev'; const ORIGIN = 'http://fortytwo-dev.localhost:4010'; // Rekha's credentials const AGENT_EMAIL = 'rekha.cc@globalhospital.com'; const AGENT_PASSWORD = 'Global@123'; // Simulated incoming call — Priya's phone const CALLER_PHONE = '9949879837'; let token = ''; let failures = 0; async function gql(query: string, variables?: any) { const h: Record = { '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) throw new Error(d.errors[0].message); return d.data; } function assert(condition: boolean, label: string, detail?: string) { if (condition) { console.log(` ✅ ${label}${detail ? ` — ${detail}` : ''}`); } else { console.log(` ❌ ${label}${detail ? ` — ${detail}` : ''}`); failures++; } } async function main() { console.log('🧪 Helix Engage — AI Flow Test\n'); console.log(`Agent: ${AGENT_EMAIL}`); console.log(`Incoming call from: ${CALLER_PHONE}\n`); // ═══════════════════════════════════════ // STEP 1: Login as Rekha // ═══════════════════════════════════════ console.log('1️⃣ LOGIN'); const d1 = await gql(`mutation { getLoginTokenFromCredentials(email: "${AGENT_EMAIL}", password: "${AGENT_PASSWORD}", 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; assert(!!token, 'Authenticated as Rekha'); // Verify role const profile = await gql('{ currentUser { email workspaceMember { name { firstName lastName } roles { label } } } }'); const roles = profile.currentUser.workspaceMember.roles.map((r: any) => r.label); const name = `${profile.currentUser.workspaceMember.name.firstName} ${profile.currentUser.workspaceMember.name.lastName}`; assert(roles.includes('HelixEngage User'), `Role: ${roles.join(', ')}`); assert(name.includes('Rekha'), `Name: ${name}`); // ═══════════════════════════════════════ // STEP 2: Look up caller by phone number // ═══════════════════════════════════════ console.log('\n2️⃣ LEAD LOOKUP (by phone: 9949879837)'); const leadsData = await gql(`{ leads(first: 50) { edges { node { id name contactName { firstName lastName } contactPhone { primaryPhoneNumber } contactEmail { primaryEmail } source status interestedService assignedAgent leadScore contactAttempts firstContacted lastContacted aiSummary aiSuggestedAction patientId campaignId } } } }`); const leads = leadsData.leads.edges.map((e: any) => e.node); assert(leads.length > 0, `Found ${leads.length} leads total`); // Match by phone const matchedLead = leads.find((l: any) => { const phone = l.contactPhone?.primaryPhoneNumber ?? ''; return phone.includes(CALLER_PHONE) || CALLER_PHONE.includes(phone); }); assert(!!matchedLead, 'Matched lead by phone', matchedLead?.name); assert(matchedLead?.contactName?.firstName === 'Priya', `First name: ${matchedLead?.contactName?.firstName}`); assert(matchedLead?.status === 'APPOINTMENT_SET', `Status: ${matchedLead?.status}`); assert(!!matchedLead?.aiSummary, 'Has AI summary', matchedLead?.aiSummary?.substring(0, 80) + '...'); assert(!!matchedLead?.aiSuggestedAction, 'Has AI suggested action', matchedLead?.aiSuggestedAction); assert(!!matchedLead?.patientId, 'Linked to patient', matchedLead?.patientId); assert(!!matchedLead?.campaignId, 'Linked to campaign', matchedLead?.campaignId); const leadId = matchedLead?.id; const patientId = matchedLead?.patientId; // ═══════════════════════════════════════ // STEP 3: Fetch patient record // ═══════════════════════════════════════ console.log('\n3️⃣ PATIENT RECORD'); if (patientId) { const patientData = await gql(`{ patients(first: 1, filter: { id: { eq: "${patientId}" } }) { edges { node { id name fullName { firstName lastName } phones { primaryPhoneNumber } emails { primaryEmail } dateOfBirth gender patientType } } } }`); const patient = patientData.patients.edges[0]?.node; assert(!!patient, 'Patient record found', patient?.name); assert(patient?.gender === 'FEMALE', `Gender: ${patient?.gender}`); assert(patient?.patientType === 'RETURNING', `Type: ${patient?.patientType}`); assert(!!patient?.dateOfBirth, `DOB: ${patient?.dateOfBirth}`); } else { assert(false, 'No patient ID to look up'); } // ═══════════════════════════════════════ // STEP 4: Fetch appointments for this patient // ═══════════════════════════════════════ console.log('\n4️⃣ APPOINTMENTS'); if (patientId) { const apptData = await gql(`{ appointments(first: 20, filter: { patientId: { eq: "${patientId}" } }) { edges { node { id name scheduledAt durationMin appointmentType status doctorName department reasonForVisit doctorId } } } }`); const appts = apptData.appointments.edges.map((e: any) => e.node); assert(appts.length >= 2, `Found ${appts.length} appointments`); const completed = appts.filter((a: any) => a.status === 'COMPLETED'); const scheduled = appts.filter((a: any) => a.status === 'SCHEDULED'); assert(completed.length >= 1, `${completed.length} completed`); assert(scheduled.length >= 1, `${scheduled.length} upcoming`); for (const a of appts) { const status = a.status === 'COMPLETED' ? '✓' : '📅'; assert(!!a.doctorId, `${status} ${a.name} — doctor linked`, a.doctorId); } } // ═══════════════════════════════════════ // STEP 5: Fetch doctor record (Dr. Patel) // ═══════════════════════════════════════ console.log('\n5️⃣ DOCTOR LOOKUP'); const doctorsData = await gql(`{ doctors(first: 10) { edges { node { id name fullName { firstName lastName } department specialty qualifications yearsOfExperience visitingHours consultationFeeNew { amountMicros currencyCode } consultationFeeFollowUp { amountMicros currencyCode } active registrationNumber portalUserId clinic { id clinicName } } } } }`); const doctors = doctorsData.doctors.edges.map((e: any) => e.node); assert(doctors.length === 5, `Found ${doctors.length} doctors`); const drPatel = doctors.find((d: any) => d.name?.includes('Patel')); assert(!!drPatel, 'Found Dr. Patel', drPatel?.specialty); assert(!!drPatel?.visitingHours, `Hours: ${drPatel?.visitingHours}`); assert(!!drPatel?.consultationFeeNew, `Fee (new): ₹${(drPatel?.consultationFeeNew?.amountMicros ?? 0) / 1_000_000}`); assert(!!drPatel?.portalUserId, 'Linked to workspace member', drPatel?.portalUserId); const drSharma = doctors.find((d: any) => d.name?.includes('Sharma')); assert(!!drSharma, 'Found Dr. Sharma', drSharma?.specialty); // ═══════════════════════════════════════ // STEP 6: Fetch call history for this lead // ═══════════════════════════════════════ console.log('\n6️⃣ CALL HISTORY'); if (leadId) { const callsData = await gql(`{ calls(first: 20, filter: { leadId: { eq: "${leadId}" } }) { edges { node { id name direction callStatus agentName startedAt durationSec disposition } } } }`); const calls = callsData.calls.edges.map((e: any) => e.node); assert(calls.length >= 3, `Found ${calls.length} calls for Priya`); for (const c of calls) { console.log(` ${c.direction} | ${c.disposition ?? c.callStatus} | ${c.name}`); } } // ═══════════════════════════════════════ // STEP 7: Fetch lead activities // ═══════════════════════════════════════ console.log('\n7️⃣ LEAD ACTIVITIES'); if (leadId) { const activitiesData = await gql(`{ leadActivities(first: 30, filter: { leadId: { eq: "${leadId}" } }, orderBy: [{ occurredAt: DescNullsLast }]) { edges { node { id activityType summary occurredAt performedBy channel } } } }`); const activities = activitiesData.leadActivities.edges.map((e: any) => e.node); assert(activities.length >= 8, `Found ${activities.length} activities for Priya`); for (const a of activities.slice(0, 5)) { console.log(` ${a.activityType} | ${a.summary}`); } if (activities.length > 5) console.log(` ... and ${activities.length - 5} more`); } // ═══════════════════════════════════════ // STEP 8: Simulate AI context assembly // ═══════════════════════════════════════ console.log('\n8️⃣ AI CONTEXT ASSEMBLY'); console.log(' What the AI assistant would receive:\n'); console.log(` Caller: ${matchedLead?.contactName?.firstName} ${matchedLead?.contactName?.lastName}`); console.log(` Phone: ${CALLER_PHONE}`); console.log(` Status: ${matchedLead?.status}`); console.log(` Service: ${matchedLead?.interestedService}`); console.log(` AI Summary: ${matchedLead?.aiSummary}`); console.log(` Suggested Action: ${matchedLead?.aiSuggestedAction}`); console.log(` Doctor: Dr. Patel — ${drPatel?.specialty}`); console.log(` Next Appointment: ${drPatel?.visitingHours} at ${drPatel?.clinic?.clinicName ?? 'N/A'}`); console.log(` Fee: ₹${(drPatel?.consultationFeeNew?.amountMicros ?? 0) / 1_000_000} (new) / ₹${(drPatel?.consultationFeeFollowUp?.amountMicros ?? 0) / 1_000_000} (follow-up)`); // ═══════════════════════════════════════ // STEP 9: Test AI chat endpoint (if sidecar is running) // ═══════════════════════════════════════ console.log('\n9️⃣ AI CHAT (via sidecar — skipped if not running)'); try { const aiRes = await fetch('http://localhost:4100/api/ai/chat', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ message: 'Tell me about the patient on the line. When is their next appointment?', context: { callerPhone: CALLER_PHONE, leadId, leadName: `${matchedLead?.contactName?.firstName} ${matchedLead?.contactName?.lastName}` }, }), signal: AbortSignal.timeout(15000), }); if (aiRes.ok) { const aiData = await aiRes.json(); assert(!!aiData.reply, 'AI responded'); console.log(`\n 🤖 AI says:\n "${aiData.reply}"\n`); assert(aiData.sources?.length > 0, `Sources: ${aiData.sources?.join(', ')}`); } else { console.log(` ⏭️ Sidecar returned ${aiRes.status} — skipping AI test`); } } catch { console.log(' ⏭️ Sidecar not running — AI chat test skipped'); } // ═══════════════════════════════════════ // RESULTS // ═══════════════════════════════════════ console.log('\n' + '═'.repeat(50)); if (failures === 0) { console.log('🎉 ALL TESTS PASSED'); } else { console.log(`⚠️ ${failures} test(s) failed`); } console.log('═'.repeat(50)); } main().catch(e => { console.error('💥', e.message); process.exit(1); });