From 59f6ac1b9ad125b94715e361664d5c853b27221a Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Mon, 23 Mar 2026 16:21:08 -0700 Subject: [PATCH] fix(ai-diagnostics): keep cluster filters numeric --- .../src/modules/ai-diagnostics/repository.ts | 55 ++++----- .../ai-diagnostics/telemetry-linking.ts | 107 ++++++++---------- 2 files changed, 71 insertions(+), 91 deletions(-) diff --git a/services/platform-service/src/modules/ai-diagnostics/repository.ts b/services/platform-service/src/modules/ai-diagnostics/repository.ts index 7bf6946f..c5527948 100644 --- a/services/platform-service/src/modules/ai-diagnostics/repository.ts +++ b/services/platform-service/src/modules/ai-diagnostics/repository.ts @@ -1,6 +1,4 @@ import { getRegisteredContainer } from '@bytelyst/cosmos'; -import { CosmosClient, Container } from '@azure/cosmos'; -import { config } from '../../lib/config.js'; import type { ErrorClusterDoc, ErrorFingerprint, @@ -13,23 +11,23 @@ import type { // Container Access // ============================================================================ -function getErrorClustersContainer(): Container { +function getErrorClustersContainer() { return getRegisteredContainer('error_clusters'); } -function getErrorFingerprintsContainer(): Container { +function getErrorFingerprintsContainer() { return getRegisteredContainer('error_fingerprints'); } -function getDiagnosticInsightsContainer(): Container { +function getDiagnosticInsightsContainer() { return getRegisteredContainer('diagnostic_insights'); } -function getDiagnosticQueriesContainer(): Container { +function getDiagnosticQueriesContainer() { return getRegisteredContainer('diagnostic_queries'); } -function getProactiveAlertsContainer(): Container { +function getProactiveAlertsContainer() { return getRegisteredContainer('proactive_alerts'); } @@ -37,9 +35,7 @@ function getProactiveAlertsContainer(): Container { // Error Cluster Repository // ============================================================================ -export async function createErrorCluster( - cluster: ErrorClusterDoc -): Promise { +export async function createErrorCluster(cluster: ErrorClusterDoc): Promise { const container = getErrorClustersContainer(); const { resource } = await container.items.create(cluster); return resource as ErrorClusterDoc; @@ -58,9 +54,7 @@ export async function getErrorClusterById( } } -export async function updateErrorCluster( - cluster: ErrorClusterDoc -): Promise { +export async function updateErrorCluster(cluster: ErrorClusterDoc): Promise { const container = getErrorClustersContainer(); const { resource } = await container.items.upsert(cluster); return resource as unknown as ErrorClusterDoc; @@ -77,7 +71,12 @@ export async function findClustersByProduct( const container = getErrorClustersContainer(); let query = 'SELECT * FROM c WHERE c.productId = @productId'; - const parameters = [{ name: '@productId', value: productId }]; + const parameters: Array<{ name: string; value: string | number }> = [ + { + name: '@productId', + value: productId, + }, + ]; if (options.status) { query += ' AND c.status = @status'; @@ -86,7 +85,7 @@ export async function findClustersByProduct( if (options.minOccurrences) { query += ' AND c.occurrenceCount >= @minOccurrences'; - parameters.push({ name: '@minOccurrences', value: options.minOccurrences.toString() }); + parameters.push({ name: '@minOccurrences', value: options.minOccurrences }); } query += ' ORDER BY c.occurrenceCount DESC'; @@ -150,13 +149,11 @@ export async function searchSimilarClusters( // Calculate cosine similarity for each cluster const results: VectorSearchResult[] = clusters - .map((cluster) => ({ + .map(cluster => ({ cluster, - similarity: cluster.embedding - ? cosineSimilarity(queryEmbedding, cluster.embedding) - : 0, + similarity: cluster.embedding ? cosineSimilarity(queryEmbedding, cluster.embedding) : 0, })) - .filter((result) => result.similarity >= threshold) + .filter(result => result.similarity >= threshold) .sort((a, b) => b.similarity - a.similarity) .slice(0, limit); @@ -212,7 +209,7 @@ export async function findRelatedClusters( excludeClusterId: clusterId, }); - return results.map((r) => r.cluster); + return results.map(r => r.cluster); } // ============================================================================ @@ -231,9 +228,7 @@ export async function getFingerprintByHash( } } -export async function saveFingerprint( - fingerprint: ErrorFingerprint -): Promise { +export async function saveFingerprint(fingerprint: ErrorFingerprint): Promise { const container = getErrorFingerprintsContainer(); const { resource } = await container.items.upsert(fingerprint); return resource as unknown as ErrorFingerprint; @@ -378,19 +373,19 @@ export async function getActiveAlerts( const container = getProactiveAlertsContainer(); let whereClause = 'NOT IS_DEFINED(c.resolvedAt)'; - + if (productId) { whereClause += ' AND c.productId = @productId'; } - + if (options?.acknowledged === false) { whereClause += ' AND NOT IS_DEFINED(c.acknowledgedAt)'; } else if (options?.acknowledged === true) { whereClause += ' AND IS_DEFINED(c.acknowledgedAt)'; } - + if (options?.severity) { - whereClause += " AND c.severity = @severity"; + whereClause += ' AND c.severity = @severity'; } const parameters: Array<{ name: string; value: string | number | boolean }> = []; @@ -415,7 +410,7 @@ export async function getActiveAlerts( c.createdAt DESC OFFSET 0 LIMIT @limit `; - + parameters.push({ name: '@limit', value: options?.limit ?? 50 }); const { resources } = await container.items @@ -449,7 +444,6 @@ export async function acknowledgeAlert( if (resources.length === 0) return; const alert = resources[0] as ProactiveAlert; - const partitionKey = alert.productId; await container.items.upsert({ ...alert, @@ -477,7 +471,6 @@ export async function resolveAlert(alertId: string): Promise { if (resources.length === 0) return; const alert = resources[0] as ProactiveAlert; - const partitionKey = alert.productId; await container.items.upsert({ ...alert, diff --git a/services/platform-service/src/modules/ai-diagnostics/telemetry-linking.ts b/services/platform-service/src/modules/ai-diagnostics/telemetry-linking.ts index dd74e5af..17b80cb4 100644 --- a/services/platform-service/src/modules/ai-diagnostics/telemetry-linking.ts +++ b/services/platform-service/src/modules/ai-diagnostics/telemetry-linking.ts @@ -1,5 +1,4 @@ import { getRegisteredContainer } from '@bytelyst/cosmos'; -import type { Container } from '@azure/cosmos'; import type { ErrorEvent, ErrorClusterDoc } from './types.js'; // ============================================================================ @@ -83,7 +82,7 @@ interface SessionState { // Container Access // ============================================================================ -function getTelemetryContainer(): Container { +function getTelemetryContainer() { return getRegisteredContainer('telemetry_events'); } @@ -137,13 +136,9 @@ export async function linkErrorToTelemetry( } // Find the error event position - const errorIndex = events.findIndex( - (e) => e.timestamp === errorEvent.timestamp - ); + const errorIndex = events.findIndex(e => e.timestamp === errorEvent.timestamp); - const windowStart = errorIndex >= 0 - ? Math.max(0, errorIndex - Math.floor(maxEvents / 2)) - : 0; + const windowStart = errorIndex >= 0 ? Math.max(0, errorIndex - Math.floor(maxEvents / 2)) : 0; const windowEnd = Math.min(events.length, windowStart + maxEvents); const windowEvents = events.slice(windowStart, windowEnd); @@ -202,15 +197,18 @@ async function linkByUserAndTime( `; const { resources } = await container.items - .query({ - query, - parameters: [ - { name: '@userId', value: errorEvent.userId }, - { name: '@productId', value: errorEvent.productId }, - { name: '@windowStart', value: windowStart }, - { name: '@windowEnd', value: windowEnd }, - ], - }, { maxItemCount: maxEvents }) + .query( + { + query, + parameters: [ + { name: '@userId', value: errorEvent.userId }, + { name: '@productId', value: errorEvent.productId }, + { name: '@windowStart', value: windowStart }, + { name: '@windowEnd', value: windowEnd }, + ], + }, + { maxItemCount: maxEvents } + ) .fetchAll(); const events = resources as TelemetryEvent[]; @@ -224,9 +222,7 @@ async function linkByUserAndTime( // Find events before and after error timestamp const errorTimeMs = errorTime.getTime(); - const errorIndex = events.findIndex( - (e) => new Date(e.timestamp).getTime() >= errorTimeMs - ); + const errorIndex = events.findIndex(e => new Date(e.timestamp).getTime() >= errorTimeMs); const precedingEvents = errorIndex > 0 ? events.slice(0, errorIndex) : []; const followingEvents = errorIndex >= 0 ? events.slice(errorIndex + 1) : events; @@ -259,7 +255,7 @@ function extractUserJourney(events: TelemetryEvent[]): UserJourneyStep[] { journey.push({ timestamp: event.timestamp, - screen: event.screen || event.properties?.screen as string || 'unknown', + screen: event.screen || (event.properties?.screen as string) || 'unknown', action: event.eventName, durationMs, }); @@ -300,12 +296,10 @@ function extractDeviceContext(event?: TelemetryEvent): DeviceContext { function extractApiCalls(events: TelemetryEvent[]): ApiCall[] { return events .filter( - (e) => - e.eventType === 'api_call' || - e.eventName.includes('api') || - e.eventName.includes('request') + e => + e.eventType === 'api_call' || e.eventName.includes('api') || e.eventName.includes('request') ) - .map((e) => ({ + .map(e => ({ endpoint: (e.properties?.endpoint as string) || 'unknown', method: (e.properties?.method as string) || 'GET', statusCode: e.properties?.statusCode as number, @@ -322,9 +316,7 @@ function extractApiCalls(events: TelemetryEvent[]): ApiCall[] { /** * Enriches error event with full telemetry context */ -export async function enrichErrorContext( - errorEvent: ErrorEvent -): Promise { +export async function enrichErrorContext(errorEvent: ErrorEvent): Promise { // Link to telemetry const telemetryContext = await linkErrorToTelemetry(errorEvent, { windowMinutes: 5, @@ -338,9 +330,10 @@ export async function enrichErrorContext( const recentActions = extractRecentActions(telemetryContext); // Extract API failures - const apiFailures = telemetryContext?.apiCalls.filter((call) => - call.error || (call.statusCode && call.statusCode >= 400) - ) || []; + const apiFailures = + telemetryContext?.apiCalls.filter( + call => call.error || (call.statusCode && call.statusCode >= 400) + ) || []; // Extract feature flags from telemetry const featureFlags = extractFeatureFlags(telemetryContext); @@ -373,18 +366,20 @@ function buildSessionState(telemetryContext: TelemetryContext | null): SessionSt // Find last screen view const screenViews = events.filter( - (e) => e.eventType === 'screen_view' || e.eventName.includes('screen') + e => e.eventType === 'screen_view' || e.eventName.includes('screen') ); - const currentScreen = screenViews.length > 0 - ? screenViews[screenViews.length - 1].screen || - (screenViews[screenViews.length - 1].properties?.screen as string) - : 'unknown'; + const currentScreen = + screenViews.length > 0 + ? screenViews[screenViews.length - 1].screen || + (screenViews[screenViews.length - 1].properties?.screen as string) + : 'unknown'; - const previousScreen = screenViews.length > 1 - ? screenViews[screenViews.length - 2].screen || - (screenViews[screenViews.length - 2].properties?.screen as string) - : undefined; + const previousScreen = + screenViews.length > 1 + ? screenViews[screenViews.length - 2].screen || + (screenViews[screenViews.length - 2].properties?.screen as string) + : undefined; // Calculate duration on current screen let durationOnScreen = 0; @@ -396,8 +391,8 @@ function buildSessionState(telemetryContext: TelemetryContext | null): SessionSt // Extract user actions const userActions = events - .filter((e) => e.eventType === 'action' || e.eventType === 'interaction') - .map((e) => e.eventName); + .filter(e => e.eventType === 'action' || e.eventType === 'interaction') + .map(e => e.eventName); return { screen: currentScreen || 'unknown', @@ -414,13 +409,11 @@ function extractRecentActions(telemetryContext: TelemetryContext | null): string return telemetryContext.precedingEvents .slice(-10) // Last 10 actions before error - .filter((e) => e.eventType === 'action' || e.eventType === 'interaction') - .map((e) => e.eventName); + .filter(e => e.eventType === 'action' || e.eventType === 'interaction') + .map(e => e.eventName); } -function extractFeatureFlags( - telemetryContext: TelemetryContext | null -): Record { +function extractFeatureFlags(telemetryContext: TelemetryContext | null): Record { if (!telemetryContext) return {}; const flags: Record = {}; @@ -434,9 +427,7 @@ function extractFeatureFlags( return flags; } -function buildConfigSnapshot( - telemetryContext: TelemetryContext | null -): Record { +function buildConfigSnapshot(telemetryContext: TelemetryContext | null): Record { if (!telemetryContext) return {}; const config: Record = {}; @@ -464,9 +455,7 @@ export interface Breadcrumb { /** * Generates a breadcrumb trail from telemetry context */ -export function generateBreadcrumbTrail( - telemetryContext: TelemetryContext | null -): Breadcrumb[] { +export function generateBreadcrumbTrail(telemetryContext: TelemetryContext | null): Breadcrumb[] { if (!telemetryContext) return []; const breadcrumbs: Breadcrumb[] = []; @@ -496,9 +485,7 @@ export function generateBreadcrumbTrail( } // Sort by timestamp - breadcrumbs.sort( - (a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime() - ); + breadcrumbs.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()); return breadcrumbs; } @@ -538,8 +525,8 @@ export async function aggregateClusterContext( const apiErrors = new Map(); const flagCorrelations = new Map(); - let earliestTime = new Date(cluster.firstSeenAt); - let latestTime = new Date(cluster.lastSeenAt); + const earliestTime = new Date(cluster.firstSeenAt); + const latestTime = new Date(cluster.lastSeenAt); for (const error of errorEvents) { if (error.userId) { @@ -607,7 +594,7 @@ export async function aggregateClusterContext( enabled: data.enabled > 0, errorCorrelation: data.enabled / data.total, })) - .filter((f) => f.errorCorrelation > 0.5) // Only include flags with >50% correlation + .filter(f => f.errorCorrelation > 0.5) // Only include flags with >50% correlation .sort((a, b) => b.errorCorrelation - a.errorCorrelation) .slice(0, 5);