import { AlertSource, AlertCategory, Severity, EntityTypes, NormalizedAlertInput, } from "@shieldai/types"; type EntityType = (typeof EntityTypes)[keyof typeof EntityTypes]; interface DarkWatchAlertPayload { exposureId: string; breachName: string; severity: string; channel: string; dataType?: string[]; dataSource?: string; } interface SpamShieldAlertPayload { phoneNumber: string; decision: string; confidence: number; reasons?: string[]; channel?: "call" | "sms"; hiyaReputationScore?: number; truecallerSpamScore?: number; } interface VoicePrintAlertPayload { jobId: string; verdict: string; syntheticScore: number; confidence: number; matchedEnrollmentId?: string; matchedSimilarity?: number; analysisType?: string; } interface CallAnalysisAlertPayload { callId: string; eventType?: string; mosScore?: number; anomaly?: string; sentiment?: { label: string; score: number }; } const SEVERITY_MAP: Record = { LOW: "LOW", INFO: "INFO", MEDIUM: "MEDIUM", WARNING: "WARNING", HIGH: "HIGH", CRITICAL: "CRITICAL", }; function mapSeverity(raw: string | number): Severity { if (typeof raw === "number") { if (raw >= 0.9) return "CRITICAL"; if (raw >= 0.7) return "HIGH"; if (raw >= 0.5) return "WARNING"; if (raw >= 0.3) return "MEDIUM"; if (raw >= 0.1) return "INFO"; return "LOW"; } const upper = raw.toUpperCase(); return SEVERITY_MAP[upper] ?? "INFO"; } export class AlertNormalizer { public normalizeDarkWatchAlert( userId: string, sourceAlertId: string, payload: DarkWatchAlertPayload, timestamp?: Date ): NormalizedAlertInput { const severity = mapSeverity(payload.severity); const entities: Array<{ type: EntityType; value: string }> = []; if (payload.dataSource) { entities.push({ type: EntityTypes.EMAIL, value: payload.breachName }); } return { source: AlertSource.DARKWATCH, category: AlertCategory.BREACH_EXPOSURE, severity, userId, title: `Breach Exposure: ${payload.breachName}`, description: payload.dataType ? `Data types exposed: ${payload.dataType.join(", ")} in ${payload.breachName}` : `Exposure detected in ${payload.breachName}`, entities, sourceAlertId, payload: payload as unknown as Record, timestamp, }; } public normalizeSpamShieldAlert( userId: string, sourceAlertId: string, payload: SpamShieldAlertPayload, timestamp?: Date ): NormalizedAlertInput { const decision = payload.decision.toUpperCase(); const severity = decision === "BLOCK" ? "HIGH" : decision === "FLAG" ? "WARNING" : "INFO"; const channel = payload.channel === "sms" ? "sms" : "call"; const category = channel === "sms" ? AlertCategory.SPAM_SMS : AlertCategory.SPAM_CALL; const entities: Array<{ type: EntityType; value: string }> = [ { type: EntityTypes.PHONE_NUMBER, value: payload.phoneNumber }, ]; return { source: AlertSource.SPAMSHIELD, category, severity, userId, title: `${channel === "sms" ? "SMS" : "Call"} ${decision}: ${payload.phoneNumber}`, description: payload.reasons ? `SpamShield ${decision} decision. Reasons: ${payload.reasons.join(", ")}` : `SpamShield ${decision} decision with confidence ${Math.round(payload.confidence * 100)}%`, entities, sourceAlertId, payload: payload as unknown as Record, timestamp, }; } public normalizeVoicePrintAlert( userId: string, sourceAlertId: string, payload: VoicePrintAlertPayload, timestamp?: Date ): NormalizedAlertInput { const verdict = payload.verdict.toUpperCase(); let severity: Severity; let category: AlertCategory; if (payload.analysisType === "VOICE_MATCH" && payload.matchedEnrollmentId) { category = AlertCategory.VOICE_MISMATCH; severity = payload.matchedSimilarity !== undefined && payload.matchedSimilarity > 0.85 ? "MEDIUM" : "LOW"; } else { category = AlertCategory.SYNTHETIC_VOICE; severity = verdict === "SYNTHETIC" ? mapSeverity(payload.syntheticScore) : verdict === "UNCERTAIN" ? "MEDIUM" : "INFO"; } const entities: Array<{ type: EntityType; value: string }> = []; if (payload.matchedEnrollmentId) { entities.push({ type: EntityTypes.USER_ID, value: payload.matchedEnrollmentId }); } return { source: AlertSource.VOICEPRINT, category, severity, userId, title: `Voice ${verdict}: Job ${payload.jobId}`, description: payload.analysisType ? `Analysis type: ${payload.analysisType}. Verdict: ${verdict} (confidence: ${Math.round(payload.confidence * 100)}%)` : `Synthetic voice detection: ${verdict} (score: ${payload.syntheticScore.toFixed(3)})`, entities, sourceAlertId, payload: payload as unknown as Record, timestamp, }; } public normalizeCallAnalysisAlert( userId: string, sourceAlertId: string, payload: CallAnalysisAlertPayload, timestamp?: Date ): NormalizedAlertInput { let category: AlertCategory; let severity: Severity; let title: string; let description: string; if (payload.anomaly) { category = AlertCategory.CALL_ANOMALY; severity = "WARNING"; title = `Call Anomaly: ${payload.anomaly}`; description = `Anomaly "${payload.anomaly}" detected in call ${payload.callId}`; } else if (payload.mosScore !== undefined) { category = AlertCategory.CALL_QUALITY; severity = payload.mosScore < 2.5 ? "CRITICAL" : payload.mosScore < 3.5 ? "HIGH" : payload.mosScore < 4.0 ? "MEDIUM" : "INFO"; title = `Call Quality: MOS ${payload.mosScore.toFixed(1)}`; description = `MOS score ${payload.mosScore.toFixed(1)} for call ${payload.callId}`; } else if (payload.eventType) { category = AlertCategory.CALL_EVENT; severity = "INFO"; title = `Call Event: ${payload.eventType}`; description = `Event "${payload.eventType}" during call ${payload.callId}`; } else { category = AlertCategory.CALL_EVENT; severity = "INFO"; title = `Call Alert: ${payload.callId}`; description = `Alert for call ${payload.callId}`; } const entities: Array<{ type: EntityType; value: string }> = [ { type: EntityTypes.CALL_ID, value: payload.callId }, ]; return { source: AlertSource.CALL_ANALYSIS, category, severity, userId, title, description, entities, sourceAlertId, payload: payload as unknown as Record, timestamp, }; } } export const alertNormalizer = new AlertNormalizer();