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:
@@ -3,6 +3,7 @@ import { ConfigService } from '@nestjs/config';
|
||||
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
|
||||
import { OzonetelAgentService } from '../ozonetel/ozonetel-agent.service';
|
||||
import { TelephonyConfigService } from '../config/telephony-config.service';
|
||||
import { CallerResolutionService } from '../caller/caller-resolution.service';
|
||||
|
||||
// Ozonetel sends all timestamps in IST — convert to UTC for storage
|
||||
export function istToUtc(istDateStr: string | null): string | null {
|
||||
@@ -35,6 +36,7 @@ export class MissedQueueService implements OnModuleInit {
|
||||
private readonly platform: PlatformGraphqlService,
|
||||
private readonly ozonetel: OzonetelAgentService,
|
||||
private readonly telephony: TelephonyConfigService,
|
||||
private readonly caller: CallerResolutionService,
|
||||
) {
|
||||
this.pollIntervalMs = this.config.get<number>('missedQueue.pollIntervalMs', 30000);
|
||||
}
|
||||
@@ -90,26 +92,29 @@ export class MissedQueueService implements OnModuleInit {
|
||||
const callTime = istToUtc(call.callTime) ?? new Date().toISOString();
|
||||
|
||||
try {
|
||||
// Look up lead by phone number — strip +91 prefix for flexible matching
|
||||
const phoneDigits = phone.replace(/^\+91/, '');
|
||||
// Resolve caller via the shared service — covers the case
|
||||
// where there's an existing patient but no lead yet (the
|
||||
// service creates the lead on the fly and returns the name).
|
||||
// Same source of truth as the webhook path.
|
||||
let leadId: string | null = null;
|
||||
let leadName: string | null = null;
|
||||
try {
|
||||
const leadResult = await this.platform.query<any>(
|
||||
`{ leads(first: 1, filter: {
|
||||
contactPhone: { primaryPhoneNumber: { like: "%${phoneDigits}" } }
|
||||
}) { edges { node { id contactName { firstName lastName } patientId } } } }`,
|
||||
);
|
||||
const matchedLead = leadResult?.leads?.edges?.[0]?.node;
|
||||
if (matchedLead) {
|
||||
leadId = matchedLead.id;
|
||||
const fn = matchedLead.contactName?.firstName ?? '';
|
||||
const ln = matchedLead.contactName?.lastName ?? '';
|
||||
leadName = `${fn} ${ln}`.trim() || null;
|
||||
this.logger.log(`Matched missed call ${phone} → lead ${leadId} (${leadName})`);
|
||||
const apiKey = this.config.get<string>('platform.apiKey') ?? '';
|
||||
const auth = apiKey ? `Bearer ${apiKey}` : '';
|
||||
const r = await this.caller.resolve(phone, auth);
|
||||
if (r.isNew) {
|
||||
// No existing Lead/Patient — write phone as leadName.
|
||||
// Record creation is deferred to the first agent
|
||||
// action (enquiry / appointment).
|
||||
leadName = phone;
|
||||
} else if (r.leadId) {
|
||||
leadId = r.leadId;
|
||||
const fullName = `${r.firstName} ${r.lastName}`.trim();
|
||||
leadName = fullName || null;
|
||||
this.logger.log(`Matched missed call ${phone} → lead ${leadId} (${leadName ?? 'no name'})`);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.warn(`Lead lookup failed for ${phone}: ${err}`);
|
||||
this.logger.warn(`Caller resolution failed for ${phone}: ${err}`);
|
||||
}
|
||||
|
||||
const existing = await this.platform.query<any>(
|
||||
|
||||
Reference in New Issue
Block a user