mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
feat(agent-lookup): resolve by Ozonetel display name too
Inbound webhook rows store agentName as Ozonetel's display string
("Ganesh Bandi", "GlobalHealthX") which doesn't match either
ozonetelAgentId or platform Agent.name. Add ozonetelDisplayName as a
third index — populated on each platform Agent with the Full Name from
the Ozonetel admin UI.
After this + setting display names on Global + Ramaiah agents:
backfill matched 122/136 (~90%) on Global; remaining are calls handled
by agents that don't exist on this workspace.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -611,16 +611,21 @@ export class MaintController {
|
|||||||
this.logger.log('[MAINT] Backfill call agents by name — matching agentName last-segment to Agent entity');
|
this.logger.log('[MAINT] Backfill call agents by name — matching agentName last-segment to Agent entity');
|
||||||
|
|
||||||
// Pull all active agents — cheap, cached at service level but we
|
// Pull all active agents — cheap, cached at service level but we
|
||||||
// also need a name→UUID map for this pass.
|
// also need name → UUID maps for this pass. Three indexes:
|
||||||
|
// - ozonetelAgentId (e.g. "globalhealthx") — matches outbound dispose rows
|
||||||
|
// - ozonetelDisplayName (e.g. "Ganesh Bandi") — matches inbound webhook rows
|
||||||
|
// - platform Agent.name (e.g. "Ganesh Iyer") — last-resort fallback
|
||||||
const agentData = await this.platform.query<any>(
|
const agentData = await this.platform.query<any>(
|
||||||
`{ agents(first: 100) { edges { node { id name ozonetelAgentId } } } }`,
|
`{ agents(first: 100) { edges { node { id name ozonetelAgentId ozonetelDisplayName } } } }`,
|
||||||
);
|
);
|
||||||
const agentUuidByName = new Map<string, string>(); // lowercase name → UUID
|
const agentUuidByName = new Map<string, string>();
|
||||||
const agentUuidByOzonetelId = new Map<string, string>(); // lowercase ozonetelAgentId → UUID
|
const agentUuidByOzonetelId = new Map<string, string>();
|
||||||
|
const agentUuidByDisplayName = new Map<string, string>();
|
||||||
for (const edge of agentData?.agents?.edges ?? []) {
|
for (const edge of agentData?.agents?.edges ?? []) {
|
||||||
const a = edge.node;
|
const a = edge.node;
|
||||||
if (a.name) agentUuidByName.set(a.name.toLowerCase().trim(), a.id);
|
if (a.name) agentUuidByName.set(a.name.toLowerCase().trim(), a.id);
|
||||||
if (a.ozonetelAgentId) agentUuidByOzonetelId.set(a.ozonetelAgentId.toLowerCase().trim(), a.id);
|
if (a.ozonetelAgentId) agentUuidByOzonetelId.set(a.ozonetelAgentId.toLowerCase().trim(), a.id);
|
||||||
|
if (a.ozonetelDisplayName) agentUuidByDisplayName.set(a.ozonetelDisplayName.toLowerCase().trim(), a.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
let scanned = 0;
|
let scanned = 0;
|
||||||
@@ -654,9 +659,14 @@ export class MaintController {
|
|||||||
const last = segments[segments.length - 1];
|
const last = segments[segments.length - 1];
|
||||||
if (!last) { skipped++; continue; }
|
if (!last) { skipped++; continue; }
|
||||||
|
|
||||||
// Prefer ozonetelAgentId match; fall back to display name.
|
// Prefer ozonetelAgentId match (outbound rows store
|
||||||
|
// agentName=agentId); fall back to ozonetelDisplayName
|
||||||
|
// (inbound webhook rows store the Ozonetel display string);
|
||||||
|
// last-resort match on platform Agent.name.
|
||||||
const key = last.toLowerCase();
|
const key = last.toLowerCase();
|
||||||
const uuid = agentUuidByOzonetelId.get(key) ?? agentUuidByName.get(key);
|
const uuid = agentUuidByOzonetelId.get(key)
|
||||||
|
?? agentUuidByDisplayName.get(key)
|
||||||
|
?? agentUuidByName.get(key);
|
||||||
if (!uuid) {
|
if (!uuid) {
|
||||||
unmatched++;
|
unmatched++;
|
||||||
if (unmatchedSamples.size < 10) unmatchedSamples.add(last);
|
if (unmatchedSamples.size < 10) unmatchedSamples.add(last);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { PlatformGraphqlService } from './platform-graphql.service';
|
|||||||
export class AgentLookupService implements OnModuleInit {
|
export class AgentLookupService implements OnModuleInit {
|
||||||
private readonly logger = new Logger(AgentLookupService.name);
|
private readonly logger = new Logger(AgentLookupService.name);
|
||||||
private readonly uuidByOzonetelId = new Map<string, string>();
|
private readonly uuidByOzonetelId = new Map<string, string>();
|
||||||
|
private readonly uuidByDisplayName = new Map<string, string>();
|
||||||
|
|
||||||
constructor(private readonly platform: PlatformGraphqlService) {}
|
constructor(private readonly platform: PlatformGraphqlService) {}
|
||||||
|
|
||||||
@@ -26,17 +27,21 @@ export class AgentLookupService implements OnModuleInit {
|
|||||||
async refresh(): Promise<void> {
|
async refresh(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const data = await this.platform.query<any>(
|
const data = await this.platform.query<any>(
|
||||||
`{ agents(first: 100) { edges { node { id ozonetelAgentId } } } }`,
|
`{ agents(first: 100) { edges { node { id ozonetelAgentId ozonetelDisplayName } } } }`,
|
||||||
);
|
);
|
||||||
const edges = data?.agents?.edges ?? [];
|
const edges = data?.agents?.edges ?? [];
|
||||||
this.uuidByOzonetelId.clear();
|
this.uuidByOzonetelId.clear();
|
||||||
|
this.uuidByDisplayName.clear();
|
||||||
for (const edge of edges) {
|
for (const edge of edges) {
|
||||||
const n = edge.node;
|
const n = edge.node;
|
||||||
if (n.ozonetelAgentId) {
|
if (n.ozonetelAgentId) {
|
||||||
this.uuidByOzonetelId.set(n.ozonetelAgentId.toLowerCase(), n.id);
|
this.uuidByOzonetelId.set(n.ozonetelAgentId.toLowerCase(), n.id);
|
||||||
}
|
}
|
||||||
|
if (n.ozonetelDisplayName) {
|
||||||
|
this.uuidByDisplayName.set(n.ozonetelDisplayName.toLowerCase().trim(), n.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.logger.log(`[AGENT-LOOKUP] Loaded ${this.uuidByOzonetelId.size} agents`);
|
this.logger.log(`[AGENT-LOOKUP] Loaded ${this.uuidByOzonetelId.size} agents (${this.uuidByDisplayName.size} with display name)`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.warn(`[AGENT-LOOKUP] Refresh failed: ${err}`);
|
this.logger.warn(`[AGENT-LOOKUP] Refresh failed: ${err}`);
|
||||||
}
|
}
|
||||||
@@ -51,4 +56,15 @@ export class AgentLookupService implements OnModuleInit {
|
|||||||
await this.refresh();
|
await this.refresh();
|
||||||
return this.uuidByOzonetelId.get(key) ?? null;
|
return this.uuidByOzonetelId.get(key) ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve by Ozonetel display name (e.g. "Ganesh Bandi") — used by
|
||||||
|
// missed-call webhook backfill where only AgentName (display) is available.
|
||||||
|
async resolveByDisplayName(displayName: string | null | undefined): Promise<string | null> {
|
||||||
|
if (!displayName) return null;
|
||||||
|
const key = displayName.toLowerCase().trim();
|
||||||
|
const cached = this.uuidByDisplayName.get(key);
|
||||||
|
if (cached) return cached;
|
||||||
|
await this.refresh();
|
||||||
|
return this.uuidByDisplayName.get(key) ?? null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user