mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-12 02:18:18 +00:00
- Renamed appointmentStatus to status in search + call-assist queries - Missed calls worklist: removed agentName filter (shared FIFO queue) - Webhook sets callbackstatus: PENDING_CALLBACK on missed calls - AI chat: added KB content logging for debugging Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
95 lines
4.0 KiB
TypeScript
95 lines
4.0 KiB
TypeScript
import { Controller, Get, Query, Logger } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
|
|
|
|
@Controller('api/search')
|
|
export class SearchController {
|
|
private readonly logger = new Logger(SearchController.name);
|
|
private readonly platformApiKey: string;
|
|
|
|
constructor(
|
|
private readonly platform: PlatformGraphqlService,
|
|
private readonly config: ConfigService,
|
|
) {
|
|
this.platformApiKey = config.get<string>('platform.apiKey') ?? '';
|
|
}
|
|
|
|
@Get()
|
|
async search(@Query('q') query?: string) {
|
|
if (!query || query.length < 2) {
|
|
return { leads: [], patients: [], appointments: [] };
|
|
}
|
|
|
|
const authHeader = this.platformApiKey ? `Bearer ${this.platformApiKey}` : '';
|
|
if (!authHeader) {
|
|
return { leads: [], patients: [], appointments: [] };
|
|
}
|
|
|
|
this.logger.log(`Search: "${query}"`);
|
|
|
|
// Fetch all three in parallel, filter client-side for flexible matching
|
|
try {
|
|
const [leadsResult, patientsResult, appointmentsResult] = await Promise.all([
|
|
this.platform.queryWithAuth<any>(
|
|
`{ leads(first: 50) { edges { node {
|
|
id name contactName { firstName lastName }
|
|
contactPhone { primaryPhoneNumber }
|
|
source status interestedService
|
|
} } } }`,
|
|
undefined, authHeader,
|
|
).catch(() => ({ leads: { edges: [] } })),
|
|
|
|
this.platform.queryWithAuth<any>(
|
|
`{ patients(first: 50) { edges { node {
|
|
id name fullName { firstName lastName }
|
|
phones { primaryPhoneNumber }
|
|
gender dateOfBirth
|
|
} } } }`,
|
|
undefined, authHeader,
|
|
).catch(() => ({ patients: { edges: [] } })),
|
|
|
|
this.platform.queryWithAuth<any>(
|
|
`{ appointments(first: 50, orderBy: [{ scheduledAt: DescNullsLast }]) { edges { node {
|
|
id scheduledAt doctorName department status patientId
|
|
} } } }`,
|
|
undefined, authHeader,
|
|
).catch(() => ({ appointments: { edges: [] } })),
|
|
]);
|
|
|
|
const q = query.toLowerCase();
|
|
|
|
const leads = (leadsResult.leads?.edges ?? [])
|
|
.map((e: any) => e.node)
|
|
.filter((l: any) => {
|
|
const name = `${l.contactName?.firstName ?? ''} ${l.contactName?.lastName ?? ''}`.toLowerCase();
|
|
const phone = l.contactPhone?.primaryPhoneNumber ?? '';
|
|
return name.includes(q) || phone.includes(q) || (l.name ?? '').toLowerCase().includes(q);
|
|
})
|
|
.slice(0, 5);
|
|
|
|
const patients = (patientsResult.patients?.edges ?? [])
|
|
.map((e: any) => e.node)
|
|
.filter((p: any) => {
|
|
const name = `${p.fullName?.firstName ?? ''} ${p.fullName?.lastName ?? ''}`.toLowerCase();
|
|
const phone = p.phones?.primaryPhoneNumber ?? '';
|
|
return name.includes(q) || phone.includes(q) || (p.name ?? '').toLowerCase().includes(q);
|
|
})
|
|
.slice(0, 5);
|
|
|
|
const appointments = (appointmentsResult.appointments?.edges ?? [])
|
|
.map((e: any) => e.node)
|
|
.filter((a: any) => {
|
|
const doctor = (a.doctorName ?? '').toLowerCase();
|
|
const dept = (a.department ?? '').toLowerCase();
|
|
return doctor.includes(q) || dept.includes(q);
|
|
})
|
|
.slice(0, 5);
|
|
|
|
return { leads, patients, appointments };
|
|
} catch (err: any) {
|
|
this.logger.error(`Search failed: ${err.message}`);
|
|
return { leads: [], patients: [], appointments: [] };
|
|
}
|
|
}
|
|
}
|