mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
fix+feat: morning QA fixes, worklist pagination, misc sidecar improvements
- caller-resolution: drop cache, use indexed phone filter (lead.contactPhone.primaryPhoneNumber.like) - worklist: externalize page size (WORKLIST_PAGE_SIZE × WORKLIST_MAX_PAGES), paginate getMissedCalls/getAssignedLeads/getPendingFollowUps - maint: unlock-agent, force-ready, backfill-caller-resolution, clear-analysis-cache, fix-timestamps - ozonetel agent.service: force logout+re-login on "already logged in" - ai chat: context expansion - livekit-agent: updates - widget: session handling - masterdata: clinic list cache Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,27 @@ async function gql<T = any>(query: string, variables?: Record<string, unknown>):
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve a phone to a {leadId, patientId} pair via the sidecar's
|
||||
// caller-resolution endpoint. Always returns populated IDs (creates
|
||||
// placeholder lead+patient when none exist).
|
||||
async function resolveCaller(phone: string): Promise<{ leadId: string; patientId: string; firstName: string; lastName: string; isNew: boolean } | null> {
|
||||
try {
|
||||
const res = await fetch(`${SIDECAR_URL}/api/caller/resolve`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone }),
|
||||
});
|
||||
if (!res.ok) {
|
||||
console.error('[AGENT-RESOLVE] Failed:', res.status, await res.text().catch(() => ''));
|
||||
return null;
|
||||
}
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
console.error('[AGENT-RESOLVE] Failed:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Hospital context — loaded on startup
|
||||
let hospitalContext = {
|
||||
doctors: [] as Array<{ name: string; department: string; specialty: string; id: string }>,
|
||||
@@ -133,24 +154,53 @@ const bookAppointment = llm.tool({
|
||||
},
|
||||
);
|
||||
|
||||
// Create or find lead
|
||||
// Resolve caller — if isNew, create Lead + Patient with the
|
||||
// AI-collected name; otherwise update the existing record.
|
||||
const cleanPhone = phoneNumber.replace(/[^0-9]/g, '').slice(-10);
|
||||
await gql(
|
||||
`mutation($data: LeadCreateInput!) { createLead(data: $data) { id } }`,
|
||||
{
|
||||
data: {
|
||||
name: `AI — ${patientName}`,
|
||||
contactName: {
|
||||
firstName: patientName.split(' ')[0],
|
||||
lastName: patientName.split(' ').slice(1).join(' ') || '',
|
||||
const resolved = await resolveCaller(cleanPhone);
|
||||
const fn = patientName.split(' ')[0];
|
||||
const ln = patientName.split(' ').slice(1).join(' ') || '';
|
||||
if (resolved?.isNew) {
|
||||
const p = await gql(
|
||||
`mutation($data: PatientCreateInput!) { createPatient(data: $data) { id } }`,
|
||||
{ data: { fullName: { firstName: fn, lastName: ln }, phones: { primaryPhoneNumber: `+91${cleanPhone}` }, patientType: 'NEW' } },
|
||||
);
|
||||
const newPatientId = p?.createPatient?.id;
|
||||
await gql(
|
||||
`mutation($data: LeadCreateInput!) { createLead(data: $data) { id } }`,
|
||||
{
|
||||
data: {
|
||||
name: `AI — ${patientName}`,
|
||||
contactName: { firstName: fn, lastName: ln },
|
||||
contactPhone: { primaryPhoneNumber: `+91${cleanPhone}` },
|
||||
source: 'PHONE',
|
||||
status: 'APPOINTMENT_SET',
|
||||
interestedService: department,
|
||||
...(newPatientId ? { patientId: newPatientId } : {}),
|
||||
},
|
||||
contactPhone: { primaryPhoneNumber: `+91${cleanPhone}` },
|
||||
source: 'PHONE',
|
||||
status: 'APPOINTMENT_SET',
|
||||
interestedService: department,
|
||||
},
|
||||
},
|
||||
);
|
||||
);
|
||||
} else if (resolved?.leadId) {
|
||||
await gql(
|
||||
`mutation($id: UUID!, $data: LeadUpdateInput!) { updateLead(id: $id, data: $data) { id } }`,
|
||||
{
|
||||
id: resolved.leadId,
|
||||
data: {
|
||||
name: `AI — ${patientName}`,
|
||||
contactName: { firstName: fn, lastName: ln },
|
||||
source: 'PHONE',
|
||||
status: 'APPOINTMENT_SET',
|
||||
interestedService: department,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (resolved.patientId) {
|
||||
await gql(
|
||||
`mutation($id: UUID!, $data: PatientUpdateInput!) { updatePatient(id: $id, data: $data) { id } }`,
|
||||
{ id: resolved.patientId, data: { fullName: { firstName: fn, lastName: ln } } },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const refNum = `GH-${Date.now().toString().slice(-6)}`;
|
||||
if (result?.createAppointment?.id) {
|
||||
@@ -172,25 +222,53 @@ const collectLeadInfo = llm.tool({
|
||||
console.log(`[LIVEKIT-AGENT] Lead: ${name} | ${phoneNumber} | ${interest}`);
|
||||
|
||||
const cleanPhone = phoneNumber.replace(/[^0-9]/g, '').slice(-10);
|
||||
const result = await gql(
|
||||
`mutation($data: LeadCreateInput!) { createLead(data: $data) { id } }`,
|
||||
{
|
||||
data: {
|
||||
name: `AI Enquiry — ${name}`,
|
||||
contactName: {
|
||||
firstName: name.split(' ')[0],
|
||||
lastName: name.split(' ').slice(1).join(' ') || '',
|
||||
},
|
||||
contactPhone: { primaryPhoneNumber: `+91${cleanPhone}` },
|
||||
source: 'PHONE',
|
||||
status: 'NEW',
|
||||
interestedService: interest,
|
||||
},
|
||||
},
|
||||
);
|
||||
const resolved = await resolveCaller(cleanPhone);
|
||||
const fn = name.split(' ')[0];
|
||||
const ln = name.split(' ').slice(1).join(' ') || '';
|
||||
|
||||
if (result?.createLead?.id) {
|
||||
console.log(`[LIVEKIT-AGENT] Lead created: ${result.createLead.id}`);
|
||||
if (resolved?.isNew) {
|
||||
// Net-new caller — create Patient + Lead with the AI-collected name.
|
||||
const p = await gql(
|
||||
`mutation($data: PatientCreateInput!) { createPatient(data: $data) { id } }`,
|
||||
{ data: { fullName: { firstName: fn, lastName: ln }, phones: { primaryPhoneNumber: `+91${cleanPhone}` }, patientType: 'NEW' } },
|
||||
);
|
||||
const newPatientId = p?.createPatient?.id;
|
||||
const created = await gql(
|
||||
`mutation($data: LeadCreateInput!) { createLead(data: $data) { id } }`,
|
||||
{
|
||||
data: {
|
||||
name: `AI Enquiry — ${name}`,
|
||||
contactName: { firstName: fn, lastName: ln },
|
||||
contactPhone: { primaryPhoneNumber: `+91${cleanPhone}` },
|
||||
source: 'PHONE',
|
||||
status: 'NEW',
|
||||
interestedService: interest,
|
||||
...(newPatientId ? { patientId: newPatientId } : {}),
|
||||
},
|
||||
},
|
||||
);
|
||||
console.log(`[LIVEKIT-AGENT] Lead created: ${created?.createLead?.id ?? 'none'} (patient ${newPatientId ?? 'none'})`);
|
||||
} else if (resolved?.leadId) {
|
||||
await gql(
|
||||
`mutation($id: UUID!, $data: LeadUpdateInput!) { updateLead(id: $id, data: $data) { id } }`,
|
||||
{
|
||||
id: resolved.leadId,
|
||||
data: {
|
||||
name: `AI Enquiry — ${name}`,
|
||||
contactName: { firstName: fn, lastName: ln },
|
||||
source: 'PHONE',
|
||||
status: 'NEW',
|
||||
interestedService: interest,
|
||||
},
|
||||
},
|
||||
);
|
||||
if (resolved.patientId) {
|
||||
await gql(
|
||||
`mutation($id: UUID!, $data: PatientUpdateInput!) { updatePatient(id: $id, data: $data) { id } }`,
|
||||
{ id: resolved.patientId, data: { fullName: { firstName: fn, lastName: ln } } },
|
||||
);
|
||||
}
|
||||
console.log(`[LIVEKIT-AGENT] Lead updated: ${resolved.leadId} (patient ${resolved.patientId})`);
|
||||
}
|
||||
return `Thank you ${name}. I have noted your enquiry about ${interest}. One of our team members will call you back on ${phoneNumber} shortly.`;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user