feat: add caller connection and answer rate analytics
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
import { Prisma, PrismaClient } from '@prisma/client';
|
||||
import { analyticsHash, foldAnalytics, projectAnalytics, COUNT_KEYS, type AnalyticsCall, type AnalyticsEvent, type AnalyticsProjection } from './caller-analytics-model.js';
|
||||
|
||||
type Tx = Prisma.TransactionClient;
|
||||
const json = <T>(value: T | string): T => typeof value === 'string' ? JSON.parse(value) as T : value;
|
||||
export class CallerAnalyticsStore {
|
||||
constructor(private readonly db: PrismaClient) {}
|
||||
|
||||
async consume(event: AnalyticsEvent): Promise<'processed' | 'duplicate'> {
|
||||
// The legacy router puts diagnostic markers in customer_id for unauthenticated traffic.
|
||||
// Such traffic is outside the business population and must not create analysis calls.
|
||||
if (!await this.db.customer.findUnique({ where: { id: event.customerId }, select: { id: true } })) return 'duplicate';
|
||||
return this.db.$transaction(async tx => {
|
||||
const callKey = analyticsHash(event.callUid);
|
||||
await tx.$executeRaw`INSERT IGNORE INTO caller_analysis_calls (id, payload, updated_at) VALUES (${callKey}, NULL, NOW(3))`;
|
||||
const rows = await tx.$queryRaw<Array<{ payload: AnalyticsCall | string | null }>>`SELECT payload FROM caller_analysis_calls WHERE id=${callKey} FOR UPDATE`;
|
||||
const eventKey = analyticsHash(event.eventId);
|
||||
const previous = await tx.$queryRaw<Array<{ id: string }>>`SELECT id FROM caller_analysis_events WHERE id=${eventKey}`;
|
||||
if (previous.length) return 'duplicate';
|
||||
const old = rows[0].payload ? json<AnalyticsCall>(rows[0].payload) : null;
|
||||
const next = foldAnalytics(old, event);
|
||||
const before = new Map((old ? projectAnalytics(old) : []).map(row => [row.id, row]));
|
||||
for (const row of projectAnalytics(next)) {
|
||||
const prior = before.get(row.id);
|
||||
if (prior) await this.bucket(tx, prior, -1);
|
||||
await this.bucket(tx, row, 1);
|
||||
await this.state(tx, row);
|
||||
}
|
||||
await tx.$executeRaw`UPDATE caller_analysis_calls SET payload=${JSON.stringify(next)}, updated_at=NOW(3) WHERE id=${callKey}`;
|
||||
await tx.$executeRaw`INSERT INTO caller_analysis_events (id, call_key, occurred_at, payload, created_at) VALUES (${eventKey}, ${callKey}, ${new Date(event.at)}, ${JSON.stringify(event)}, NOW(3))`;
|
||||
return 'processed';
|
||||
}, { timeout: 15000 });
|
||||
}
|
||||
|
||||
private async state(tx: Tx, row: AnalyticsProjection) {
|
||||
await tx.$executeRaw`INSERT INTO caller_analysis_states
|
||||
(id, call_key, view, started_at, customer_id, customer_gateway_id, vendor_id, vendor_gateway_id, caller, callee, city, carrier, counts, payload, updated_at)
|
||||
VALUES (${row.id}, ${row.callKey}, ${row.view}, ${new Date(row.startedAt)}, ${row.customerId}, ${row.customerGatewayId}, ${row.vendorId}, ${row.vendorGatewayId}, ${row.caller}, ${row.callee}, ${row.city}, ${row.carrier}, ${JSON.stringify(Object.fromEntries(COUNT_KEYS.map(k => [k, row[k]])))}, ${JSON.stringify(row.payload)}, NOW(3))
|
||||
ON DUPLICATE KEY UPDATE started_at=VALUES(started_at), customer_gateway_id=VALUES(customer_gateway_id), vendor_id=VALUES(vendor_id), vendor_gateway_id=VALUES(vendor_gateway_id), caller=VALUES(caller), callee=VALUES(callee), city=VALUES(city), carrier=VALUES(carrier), counts=VALUES(counts), payload=VALUES(payload), updated_at=NOW(3)`;
|
||||
}
|
||||
|
||||
private async bucket(tx: Tx, row: AnalyticsProjection, sign: number) {
|
||||
const minute = new Date(Math.floor(row.startedAt / 60000) * 60000);
|
||||
const id = analyticsHash([minute.toISOString(), row.view, row.customerId, row.customerGatewayId, row.vendorId, row.vendorGatewayId, row.caller, row.city, row.carrier]);
|
||||
const delta = JSON.stringify(Object.fromEntries(COUNT_KEYS.map(k => [k, sign * row[k]])));
|
||||
const additions = Prisma.join(COUNT_KEYS.flatMap(k => [Prisma.sql`${`$.${k}`}`, Prisma.sql`CAST(JSON_UNQUOTE(JSON_EXTRACT(counts, ${`$.${k}`})) AS SIGNED) + ${sign * row[k]}`]));
|
||||
await tx.$executeRaw(Prisma.sql`INSERT INTO caller_analysis_minute_buckets
|
||||
(id, started_at, view, customer_id, customer_gateway_id, vendor_id, vendor_gateway_id, caller, city, carrier, counts)
|
||||
VALUES (${id}, ${minute}, ${row.view}, ${row.customerId}, ${row.customerGatewayId}, ${row.vendorId}, ${row.vendorGatewayId}, ${row.caller}, ${row.city}, ${row.carrier}, ${delta})
|
||||
ON DUPLICATE KEY UPDATE counts=JSON_OBJECT(${additions})`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user