feat: suggestion rules engine + caller context evaluation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-17 11:09:47 +05:30
parent a576552f8a
commit 2d18110786
2 changed files with 194 additions and 8 deletions

View File

@@ -1,6 +1,7 @@
import { Injectable, Logger } from '@nestjs/common';
import { PlatformGraphqlService } from '../platform/platform-graphql.service';
import { SessionService } from '../auth/session.service';
import { evaluateSuggestionRules, type SuggestionTrigger } from '../rules-engine/suggestion-rules';
export type CallerContext = {
leadId: string;
@@ -39,6 +40,8 @@ export type CallerContext = {
occurredAt: string;
outcome: string | null;
}>;
// Rule-driven suggestion triggers
suggestionTriggers: SuggestionTrigger[];
};
const CACHE_KEY_PREFIX = 'caller:context:';
@@ -123,6 +126,26 @@ export class CallerContextService {
const firstName = lead.contactName?.firstName ?? '';
const lastName = lead.contactName?.lastName ?? '';
const appointments = (appointmentsData?.appointments?.edges ?? []).map((e: any) => e.node);
const calls = (callsData?.calls?.edges ?? []).map((e: any) => ({
startedAt: e.node.startedAt,
direction: e.node.direction,
duration: e.node.durationSec,
disposition: e.node.disposition,
agentName: e.node.agentName,
}));
const suggestionTriggers = evaluateSuggestionRules({
isNew: false,
interestedService: lead.interestedService ?? null,
leadStatus: lead.status ?? null,
contactAttempts: lead.contactAttempts ?? 0,
appointments,
calls: calls.map((c: any) => ({ direction: c.direction, disposition: c.disposition, startedAt: c.startedAt })),
utmCampaign: lead.utmCampaign ?? null,
leadSource: lead.source ?? null,
});
return {
leadId,
patientId: patientId || lead.patientId || '',
@@ -136,15 +159,10 @@ export class CallerContextService {
contactAttempts: lead.contactAttempts ?? 0,
lastContacted: lead.lastContacted ?? null,
utmCampaign: lead.utmCampaign ?? null,
appointments: (appointmentsData?.appointments?.edges ?? []).map((e: any) => e.node),
calls: (callsData?.calls?.edges ?? []).map((e: any) => ({
startedAt: e.node.startedAt,
direction: e.node.direction,
duration: e.node.durationSec,
disposition: e.node.disposition,
agentName: e.node.agentName,
})),
appointments,
calls,
activities: (activitiesData?.leadActivities?.edges ?? []).map((e: any) => e.node),
suggestionTriggers,
};
} catch (err: any) {
this.logger.warn(`[CALLER-CTX] Build failed: ${err.message}`);
@@ -152,6 +170,22 @@ export class CallerContextService {
}
}
renderSuggestionsForPrompt(triggers: SuggestionTrigger[]): string {
if (triggers.length === 0) return '';
const lines = [
'',
'SUGGESTION RULES (from business configuration):',
'Based on this caller\'s profile, the following suggestions should be offered.',
'Generate a natural, conversational script for each that the agent can read aloud.',
'Return them in the `suggestions` array of your JSON response.',
'',
];
triggers.forEach((t, i) => {
lines.push(`${i + 1}. [${t.type}/${t.priority}] ${t.title}${t.reason}`);
});
return lines.join('\n');
}
renderForPrompt(ctx: CallerContext): string {
const lines: string[] = [];
lines.push(`## CURRENT CALLER: ${ctx.name}`);