mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-04-12 02:18:18 +00:00
feat: POST /api/lead/:id/enrich for on-demand AI summary regen
Adds a new sidecar endpoint that forces regeneration of a lead's
aiSummary + aiSuggestedAction. Triggered by the call-desk Appointment
and Enquiry forms when an agent explicitly edits the caller's name —
the previous summary was built against stale identity and needs to be
refreshed from the corrected record.
Scope:
- src/call-events/lead-enrich.controller.ts (new): POST
/api/lead/:id/enrich. Fetches the lead fresh via
findLeadByIdWithToken, runs AiEnrichmentService.enrichLead() with
recent activities for context, persists the new summary via
updateLeadWithToken, and optionally invalidates the Redis
caller-resolution cache for the phone (if provided in the request
body) so the next incoming call does a fresh platform lookup.
- src/platform/platform-graphql.service.ts:
- Added findLeadByIdWithToken. Selects staging-aligned field names
(status/source/lastContacted) rather than the older
leadStatus/leadSource/lastContactedAt names — otherwise the query
is rejected by the deployed schema. Includes a fallback query
shape in case a future platform version exposes `lead(id)`
directly instead of `leads(filter: ...)`.
- Fixed updateLeadWithToken response fragment to drop the broken
`leadStatus` field selection. Every call to this method was
failing against staging because the fragment asked for a field
the schema no longer has.
- src/call-events/call-events.module.ts: registered
LeadEnrichController and imported CallerResolutionModule so the
new controller can inject CallerResolutionService for Redis cache
invalidation.
The other field-rename issues in platform-graphql.service.ts
(findLeadByPhone/findLeadByPhoneWithToken/updateLead still select
leadStatus+leadSource and will keep failing against staging) are
deliberately untouched here — separate follow-up hotfix to keep this
commit focused on the enrich flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -180,10 +180,16 @@ export class PlatformGraphqlService {
|
||||
}
|
||||
|
||||
async updateLeadWithToken(id: string, input: UpdateLeadInput, authHeader: string): Promise<LeadNode> {
|
||||
// Response fragment deliberately excludes `leadStatus` — the staging
|
||||
// platform schema has this field renamed to `status`. Selecting the
|
||||
// old name rejects the whole mutation. Callers don't use the
|
||||
// returned fragment today, so returning just the id + AI fields
|
||||
// keeps this working across both schema shapes without a wider
|
||||
// rename hotfix.
|
||||
const data = await this.queryWithAuth<{ updateLead: LeadNode }>(
|
||||
`mutation UpdateLead($id: ID!, $data: LeadUpdateInput!) {
|
||||
updateLead(id: $id, data: $data) {
|
||||
id leadStatus aiSummary aiSuggestedAction
|
||||
id aiSummary aiSuggestedAction
|
||||
}
|
||||
}`,
|
||||
{ id, data: input },
|
||||
@@ -192,6 +198,67 @@ export class PlatformGraphqlService {
|
||||
return data.updateLead;
|
||||
}
|
||||
|
||||
// Fetch a single lead by id with the caller's JWT. Used by the
|
||||
// lead-enrich flow when the agent explicitly renames a caller from
|
||||
// the appointment/enquiry form and we need to regenerate the lead's
|
||||
// AI summary against fresh identity.
|
||||
//
|
||||
// The selected fields deliberately use the staging-aligned names
|
||||
// (`status`, `source`, `lastContacted`) rather than the older
|
||||
// `leadStatus`/`leadSource`/`lastContactedAt` names — otherwise the
|
||||
// query would be rejected on staging.
|
||||
async findLeadByIdWithToken(id: string, authHeader: string): Promise<any | null> {
|
||||
try {
|
||||
const data = await this.queryWithAuth<{ lead: any }>(
|
||||
`query FindLead($id: UUID!) {
|
||||
lead(filter: { id: { eq: $id } }) {
|
||||
id
|
||||
createdAt
|
||||
contactName { firstName lastName }
|
||||
contactPhone { primaryPhoneNumber primaryPhoneCallingCode }
|
||||
source
|
||||
status
|
||||
interestedService
|
||||
contactAttempts
|
||||
lastContacted
|
||||
aiSummary
|
||||
aiSuggestedAction
|
||||
}
|
||||
}`,
|
||||
{ id },
|
||||
authHeader,
|
||||
);
|
||||
return data.lead ?? null;
|
||||
} catch {
|
||||
// Fall back to edge-style query in case the singular field
|
||||
// doesn't exist on this platform version.
|
||||
const data = await this.queryWithAuth<{ leads: { edges: { node: any }[] } }>(
|
||||
`query FindLead($id: UUID!) {
|
||||
leads(filter: { id: { eq: $id } }, first: 1) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
createdAt
|
||||
contactName { firstName lastName }
|
||||
contactPhone { primaryPhoneNumber primaryPhoneCallingCode }
|
||||
source
|
||||
status
|
||||
interestedService
|
||||
contactAttempts
|
||||
lastContacted
|
||||
aiSummary
|
||||
aiSuggestedAction
|
||||
}
|
||||
}
|
||||
}
|
||||
}`,
|
||||
{ id },
|
||||
authHeader,
|
||||
);
|
||||
return data.leads.edges[0]?.node ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Server-to-server versions (for webhooks, background jobs) ---
|
||||
|
||||
async getLeadActivities(leadId: string, limit = 3): Promise<LeadActivityNode[]> {
|
||||
|
||||
Reference in New Issue
Block a user