From e01a4d7747e6688db1d83ec1f15b72a144e8909e Mon Sep 17 00:00:00 2001 From: saridsa2 Date: Wed, 18 Mar 2026 14:46:19 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20add=20AI=20flow=20test=20script=20?= =?UTF-8?q?=E2=80=94=20validates=20auth,=20lead=20lookup,=20patient,=20doc?= =?UTF-8?q?tor,=20appointments,=20calls,=20activities=20as=20Rekha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/test-ai-flow.ts | 252 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 scripts/test-ai-flow.ts diff --git a/scripts/test-ai-flow.ts b/scripts/test-ai-flow.ts new file mode 100644 index 0000000..14bf335 --- /dev/null +++ b/scripts/test-ai-flow.ts @@ -0,0 +1,252 @@ +/** + * 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 + branchClinic visitingHours + consultationFeeNew { amountMicros currencyCode } + consultationFeeFollowUp { amountMicros currencyCode } + active registrationNumber + portalUserId + } } } }`); + 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?.branchClinic}`); + 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); });