feat: agent state endpoint + search module

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-21 14:21:40 +05:30
parent 8ba326589c
commit 4963a698d9
4 changed files with 128 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
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 appointmentStatus 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: [] };
}
}
}