mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage-server
synced 2026-05-18 20:08:19 +00:00
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>
71 lines
3.0 KiB
TypeScript
71 lines
3.0 KiB
TypeScript
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
import { PlatformGraphqlService } from './platform-graphql.service';
|
|
|
|
/**
|
|
* Maps Ozonetel agent identifiers (unique — e.g. "ramaiahadmin",
|
|
* "globalhealthx", "global") to the platform Agent entity UUID. Used by
|
|
* ingest paths (webhook, dispose, CDR enrichment, backfill) so every Call
|
|
* ends up with the correct `agent` relation regardless of how Ozonetel
|
|
* formats the display name (AgentName collisions, transfer chains like
|
|
* "A -> B -> C", etc.).
|
|
*
|
|
* The cache is case-insensitive because Ozonetel occasionally mixes
|
|
* casing ("global" vs "Global" vs "GLOBAL") across webhook/CDR responses.
|
|
*/
|
|
@Injectable()
|
|
export class AgentLookupService implements OnModuleInit {
|
|
private readonly logger = new Logger(AgentLookupService.name);
|
|
private readonly uuidByOzonetelId = new Map<string, string>();
|
|
private readonly uuidByDisplayName = new Map<string, string>();
|
|
|
|
constructor(private readonly platform: PlatformGraphqlService) {}
|
|
|
|
async onModuleInit() {
|
|
await this.refresh();
|
|
}
|
|
|
|
async refresh(): Promise<void> {
|
|
try {
|
|
const data = await this.platform.query<any>(
|
|
`{ agents(first: 100) { edges { node { id ozonetelAgentId ozonetelDisplayName } } } }`,
|
|
);
|
|
const edges = data?.agents?.edges ?? [];
|
|
this.uuidByOzonetelId.clear();
|
|
this.uuidByDisplayName.clear();
|
|
for (const edge of edges) {
|
|
const n = edge.node;
|
|
if (n.ozonetelAgentId) {
|
|
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.uuidByDisplayName.size} with display name)`);
|
|
} catch (err) {
|
|
this.logger.warn(`[AGENT-LOOKUP] Refresh failed: ${err}`);
|
|
}
|
|
}
|
|
|
|
async resolveByOzonetelId(ozonetelId: string | null | undefined): Promise<string | null> {
|
|
if (!ozonetelId) return null;
|
|
const key = ozonetelId.toLowerCase();
|
|
const cached = this.uuidByOzonetelId.get(key);
|
|
if (cached) return cached;
|
|
// Cache miss — refresh once (handles late-provisioned agents)
|
|
await this.refresh();
|
|
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;
|
|
}
|
|
}
|