feat(ai-diagnostics): add error clustering types and cosmos containers [1.1.1-1.1.2]
This commit is contained in:
parent
e98380003b
commit
4de01269b5
@ -76,6 +76,12 @@ const CONTAINER_DEFS: Record<string, ContainerConfig> = {
|
|||||||
debug_traces: { partitionKeyPath: '/pk', defaultTtl: 7 * 86400 },
|
debug_traces: { partitionKeyPath: '/pk', defaultTtl: 7 * 86400 },
|
||||||
debug_logs: { partitionKeyPath: '/pk', defaultTtl: 3 * 86400 },
|
debug_logs: { partitionKeyPath: '/pk', defaultTtl: 3 * 86400 },
|
||||||
debug_screenshots: { partitionKeyPath: '/sessionId', defaultTtl: 7 * 86400 },
|
debug_screenshots: { partitionKeyPath: '/sessionId', defaultTtl: 7 * 86400 },
|
||||||
|
// AI Diagnostics (see docs/roadmaps/AI_DIAGNOSTIC_ASSISTANT_ROADMAP.md)
|
||||||
|
error_clusters: { partitionKeyPath: '/productId', defaultTtl: 90 * 86400 },
|
||||||
|
error_fingerprints: { partitionKeyPath: '/fingerprintHash' },
|
||||||
|
diagnostic_insights: { partitionKeyPath: '/clusterId', defaultTtl: 90 * 86400 },
|
||||||
|
diagnostic_queries: { partitionKeyPath: '/userId', defaultTtl: 30 * 86400 },
|
||||||
|
proactive_alerts: { partitionKeyPath: '/productId', defaultTtl: 30 * 86400 },
|
||||||
// Broadcast Messaging & Surveys (see docs/roadmaps/not-started/platform_BROADCAST_SURVEY_ROADMAP.md)
|
// Broadcast Messaging & Surveys (see docs/roadmaps/not-started/platform_BROADCAST_SURVEY_ROADMAP.md)
|
||||||
broadcasts: { partitionKeyPath: '/productId' },
|
broadcasts: { partitionKeyPath: '/productId' },
|
||||||
broadcast_deliveries: { partitionKeyPath: '/userId', defaultTtl: 90 * 86400 },
|
broadcast_deliveries: { partitionKeyPath: '/userId', defaultTtl: 90 * 86400 },
|
||||||
|
|||||||
422
services/platform-service/src/modules/ab-testing/types.ts
Normal file
422
services/platform-service/src/modules/ab-testing/types.ts
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
/**
|
||||||
|
* Intelligent A/B Testing — Extended types and schemas.
|
||||||
|
* Bayesian statistics, Thompson sampling, early stopping, AI hypothesis generation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Enums and Constants
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export type ExperimentStatus = 'draft' | 'running' | 'paused' | 'stopped' | 'completed';
|
||||||
|
|
||||||
|
export type AllocationStrategy = 'random' | 'thompson' | 'epsilon_greedy' | 'ucb';
|
||||||
|
|
||||||
|
export type MetricType = 'conversion' | 'count' | 'duration' | 'revenue' | 'custom';
|
||||||
|
|
||||||
|
export type MetricAggregation = 'sum' | 'mean' | 'count' | 'unique';
|
||||||
|
|
||||||
|
export type MetricDirection = 'increase' | 'decrease';
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Core Types
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface ExperimentVariant {
|
||||||
|
id: string; // var_<uuid>
|
||||||
|
key: string; // e.g., 'control', 'variant_a'
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
isControl: boolean;
|
||||||
|
flagConfig: Record<string, unknown>; // Arbitrary config payload
|
||||||
|
currentAllocationPercent: number; // 0–100%, dynamic for bandit
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrimaryMetric {
|
||||||
|
name: string;
|
||||||
|
type: MetricType;
|
||||||
|
eventName: string; // Telemetry event to track
|
||||||
|
aggregation: MetricAggregation;
|
||||||
|
direction: MetricDirection; // Is higher better?
|
||||||
|
minimumDetectableEffect: number; // % change we want to detect
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SecondaryMetric {
|
||||||
|
name: string;
|
||||||
|
type: MetricType;
|
||||||
|
eventName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TargetingConfig {
|
||||||
|
platforms?: string[]; // ios, android, web
|
||||||
|
appVersions?: { min: string; max?: string };
|
||||||
|
regions?: string[];
|
||||||
|
userSegments?: string[]; // pro, free, enterprise
|
||||||
|
userProperties?: Record<string, string | number | boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GuardrailsConfig {
|
||||||
|
minSampleSizePerVariant: number; // Default: 100
|
||||||
|
maxDurationDays: number; // Safety limit, default: 30
|
||||||
|
autoStopEnabled: boolean;
|
||||||
|
winnerThreshold: number; // % probability to auto-stop, default: 95
|
||||||
|
requireApprovalFor: 'none' | 'revenue' | 'all';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Document Types (Cosmos DB)
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface ExperimentDoc {
|
||||||
|
id: string; // exp_<uuid>
|
||||||
|
productId: string; // partition key
|
||||||
|
|
||||||
|
// Experiment definition
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
hypothesis: string;
|
||||||
|
aiGeneratedHypothesis?: boolean;
|
||||||
|
|
||||||
|
// Status lifecycle
|
||||||
|
status: ExperimentStatus;
|
||||||
|
|
||||||
|
// Variants
|
||||||
|
controlVariantId: string;
|
||||||
|
variantIds: string[];
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
allocationStrategy: AllocationStrategy;
|
||||||
|
targetPercent: number; // % of eligible traffic
|
||||||
|
|
||||||
|
// Audience targeting
|
||||||
|
targeting: TargetingConfig;
|
||||||
|
|
||||||
|
// Metrics
|
||||||
|
primaryMetric: PrimaryMetric;
|
||||||
|
secondaryMetrics: SecondaryMetric[];
|
||||||
|
|
||||||
|
// Guardrails
|
||||||
|
guardrails: GuardrailsConfig;
|
||||||
|
|
||||||
|
// Scheduling
|
||||||
|
startAt?: string;
|
||||||
|
endAt?: string;
|
||||||
|
|
||||||
|
// Stats (denormalized for fast reads)
|
||||||
|
totalParticipants: number;
|
||||||
|
totalEvents: number;
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
startedAt?: string;
|
||||||
|
completedAt?: string;
|
||||||
|
|
||||||
|
// TTL for completed experiments (2 years)
|
||||||
|
ttl?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VariantDoc {
|
||||||
|
id: string; // var_<uuid>
|
||||||
|
experimentId: string; // partition key
|
||||||
|
|
||||||
|
// Variant definition
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
isControl: boolean;
|
||||||
|
|
||||||
|
// Feature flag configuration
|
||||||
|
flagConfig: Record<string, unknown>;
|
||||||
|
|
||||||
|
// Traffic allocation (dynamic for bandit)
|
||||||
|
currentAllocationPercent: number;
|
||||||
|
|
||||||
|
// Statistics (real-time computed)
|
||||||
|
stats: {
|
||||||
|
participants: number;
|
||||||
|
events: number;
|
||||||
|
primaryMetricValue: number;
|
||||||
|
primaryMetricStdDev?: number;
|
||||||
|
conversions?: number;
|
||||||
|
conversionRate?: number;
|
||||||
|
|
||||||
|
// Bayesian posterior parameters
|
||||||
|
betaAlpha?: number;
|
||||||
|
betaBeta?: number;
|
||||||
|
gammaShape?: number;
|
||||||
|
gammaScale?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bayesian results
|
||||||
|
bayesianResults?: {
|
||||||
|
probabilityBeatsControl: number;
|
||||||
|
probabilityBeatsAll: number;
|
||||||
|
expectedLiftPercent: number;
|
||||||
|
expectedLoss: number;
|
||||||
|
credibleInterval: {
|
||||||
|
lower: number;
|
||||||
|
mean: number;
|
||||||
|
upper: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExperimentAssignmentDoc {
|
||||||
|
id: string; // ea_<uuid>
|
||||||
|
userId: string; // partition key
|
||||||
|
|
||||||
|
experimentId: string;
|
||||||
|
variantId: string;
|
||||||
|
|
||||||
|
// Assignment metadata
|
||||||
|
assignedAt: string;
|
||||||
|
firstExposedAt?: string;
|
||||||
|
|
||||||
|
// Context at assignment
|
||||||
|
assignmentContext: {
|
||||||
|
platform: string;
|
||||||
|
appVersion: string;
|
||||||
|
osVersion: string;
|
||||||
|
deviceModel?: string;
|
||||||
|
region?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Events attributed
|
||||||
|
eventCount: number;
|
||||||
|
lastEventAt?: string;
|
||||||
|
|
||||||
|
// TTL: Remove after experiment completes + analysis period
|
||||||
|
ttl?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExperimentEventDoc {
|
||||||
|
id: string; // ee_<uuid>
|
||||||
|
experimentId: string; // partition key
|
||||||
|
timestamp: string; // Sort key for time-series
|
||||||
|
|
||||||
|
// Attribution
|
||||||
|
userId: string;
|
||||||
|
variantId: string;
|
||||||
|
assignmentId: string;
|
||||||
|
|
||||||
|
// Event details
|
||||||
|
metricName: string;
|
||||||
|
metricType: MetricType;
|
||||||
|
value: number;
|
||||||
|
|
||||||
|
// Conversion tracking
|
||||||
|
converted: boolean;
|
||||||
|
|
||||||
|
// Context
|
||||||
|
eventMetadata?: Record<string, unknown>;
|
||||||
|
|
||||||
|
// Denormalized for filtering
|
||||||
|
platform: string;
|
||||||
|
appVersion: string;
|
||||||
|
|
||||||
|
// TTL
|
||||||
|
ttl?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExperimentMetricDoc {
|
||||||
|
id: string; // em_<uuid>
|
||||||
|
experimentId: string; // partition key
|
||||||
|
|
||||||
|
metricName: string;
|
||||||
|
variantId: string;
|
||||||
|
|
||||||
|
// Aggregated values
|
||||||
|
count: number;
|
||||||
|
sum: number;
|
||||||
|
mean: number;
|
||||||
|
stdDev: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
|
||||||
|
// Conversion-specific
|
||||||
|
conversions: number;
|
||||||
|
conversionRate: number;
|
||||||
|
|
||||||
|
// Last updated
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExperimentResult {
|
||||||
|
experimentId: string;
|
||||||
|
status: 'in_progress' | 'winner_found' | 'no_winner' | 'stopped';
|
||||||
|
|
||||||
|
// Overall stats
|
||||||
|
totalParticipants: number;
|
||||||
|
totalEvents: number;
|
||||||
|
daysRunning: number;
|
||||||
|
|
||||||
|
// Winner info
|
||||||
|
winnerVariantId?: string;
|
||||||
|
winnerProbability?: number;
|
||||||
|
|
||||||
|
// Variant results
|
||||||
|
variantResults: Array<{
|
||||||
|
variantId: string;
|
||||||
|
variantName: string;
|
||||||
|
isControl: boolean;
|
||||||
|
participants: number;
|
||||||
|
primaryMetricValue: number;
|
||||||
|
probabilityBeatsControl: number;
|
||||||
|
expectedLiftPercent: number;
|
||||||
|
credibleInterval: { lower: number; mean: number; upper: number };
|
||||||
|
}>;
|
||||||
|
|
||||||
|
// Statistical summary
|
||||||
|
statisticalSummary: {
|
||||||
|
probabilityAnyBeatsControl: number;
|
||||||
|
expectedLossIfShipped: number;
|
||||||
|
recommendedAction: 'ship' | 'rollback' | 'continue' | 'stop';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Early stopping
|
||||||
|
earlyStopped: boolean;
|
||||||
|
stopReason?: string;
|
||||||
|
|
||||||
|
generatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// AI Hypothesis Types
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export interface HypothesisInput {
|
||||||
|
featureName: string;
|
||||||
|
adoptionRate: number;
|
||||||
|
baselineRate: number;
|
||||||
|
segmentData: Record<string, number>;
|
||||||
|
feedbackSamples: string[];
|
||||||
|
competitorFeatures?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeneratedHypothesis {
|
||||||
|
primary: string;
|
||||||
|
alternatives: string[];
|
||||||
|
expectedEffectSize: number;
|
||||||
|
successMetric: string;
|
||||||
|
riskAssessment: 'low' | 'medium' | 'high';
|
||||||
|
impactScore: number; // 0–100
|
||||||
|
difficultyScore: number; // 0–100
|
||||||
|
powerPrediction: number; // Statistical power 0–100
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExperimentSuggestion {
|
||||||
|
id: string;
|
||||||
|
hypothesis: GeneratedHypothesis;
|
||||||
|
suggestedVariants: Array<{ name: string; description: string }>;
|
||||||
|
suggestedMetrics: PrimaryMetric[];
|
||||||
|
suggestedDuration: number;
|
||||||
|
suggestedSampleSize: number;
|
||||||
|
priority: number;
|
||||||
|
aiGenerated: boolean;
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Zod Schemas
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const VariantInputSchema = z.object({
|
||||||
|
key: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.regex(/^[a-z0-9_]+$/),
|
||||||
|
name: z.string().min(1).max(200),
|
||||||
|
description: z.string().default(''),
|
||||||
|
isControl: z.boolean().default(false),
|
||||||
|
flagConfig: z.record(z.unknown()).default({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const PrimaryMetricSchema = z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
type: z.enum(['conversion', 'count', 'duration', 'revenue', 'custom']),
|
||||||
|
eventName: z.string().min(1),
|
||||||
|
aggregation: z.enum(['sum', 'mean', 'count', 'unique']),
|
||||||
|
direction: z.enum(['increase', 'decrease']),
|
||||||
|
minimumDetectableEffect: z.number().min(0.01).max(100).default(5),
|
||||||
|
});
|
||||||
|
|
||||||
|
const SecondaryMetricSchema = z.object({
|
||||||
|
name: z.string().min(1),
|
||||||
|
type: z.enum(['conversion', 'count', 'duration', 'revenue', 'custom']),
|
||||||
|
eventName: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
const TargetingSchema = z.object({
|
||||||
|
platforms: z.array(z.enum(['ios', 'android', 'web'])).optional(),
|
||||||
|
appVersions: z
|
||||||
|
.object({
|
||||||
|
min: z.string(),
|
||||||
|
max: z.string().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
regions: z.array(z.string()).optional(),
|
||||||
|
userSegments: z.array(z.string()).optional(),
|
||||||
|
userProperties: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const GuardrailsSchema = z.object({
|
||||||
|
minSampleSizePerVariant: z.number().int().min(10).default(100),
|
||||||
|
maxDurationDays: z.number().int().min(1).max(90).default(30),
|
||||||
|
autoStopEnabled: z.boolean().default(true),
|
||||||
|
winnerThreshold: z.number().int().min(80).max(99).default(95),
|
||||||
|
requireApprovalFor: z.enum(['none', 'revenue', 'all']).default('none'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateExperimentSchema = z.object({
|
||||||
|
name: z.string().min(1).max(200),
|
||||||
|
description: z.string().default(''),
|
||||||
|
hypothesis: z.string().min(1),
|
||||||
|
variants: z.array(VariantInputSchema).min(2).max(10),
|
||||||
|
allocationStrategy: z.enum(['random', 'thompson', 'epsilon_greedy', 'ucb']).default('random'),
|
||||||
|
targetPercent: z.number().int().min(1).max(100).default(100),
|
||||||
|
targeting: TargetingSchema.default({}),
|
||||||
|
primaryMetric: PrimaryMetricSchema,
|
||||||
|
secondaryMetrics: z.array(SecondaryMetricSchema).default([]),
|
||||||
|
guardrails: GuardrailsSchema.default({}),
|
||||||
|
startAt: z.string().datetime().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateExperimentSchema = z.object({
|
||||||
|
name: z.string().min(1).max(200).optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
hypothesis: z.string().optional(),
|
||||||
|
status: z.enum(['draft', 'running', 'paused', 'stopped', 'completed']).optional(),
|
||||||
|
targetPercent: z.number().int().min(1).max(100).optional(),
|
||||||
|
targeting: TargetingSchema.optional(),
|
||||||
|
guardrails: GuardrailsSchema.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const TrackEventSchema = z.object({
|
||||||
|
experimentId: z.string(),
|
||||||
|
userId: z.string(),
|
||||||
|
metricName: z.string(),
|
||||||
|
metricType: z.enum(['conversion', 'count', 'duration', 'revenue', 'custom']),
|
||||||
|
value: z.number(),
|
||||||
|
converted: z.boolean().default(true),
|
||||||
|
eventMetadata: z.record(z.unknown()).optional(),
|
||||||
|
platform: z.string().default('unknown'),
|
||||||
|
appVersion: z.string().default('unknown'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AdjustAllocationSchema = z.object({
|
||||||
|
variantId: z.string(),
|
||||||
|
newAllocationPercent: z.number().int().min(0).max(100),
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Type Exports
|
||||||
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export type CreateExperimentInput = z.infer<typeof CreateExperimentSchema>;
|
||||||
|
export type UpdateExperimentInput = z.infer<typeof UpdateExperimentSchema>;
|
||||||
|
export type TrackEventInput = z.infer<typeof TrackEventSchema>;
|
||||||
|
export type AdjustAllocationInput = z.infer<typeof AdjustAllocationSchema>;
|
||||||
411
services/platform-service/src/modules/ai-diagnostics/types.ts
Normal file
411
services/platform-service/src/modules/ai-diagnostics/types.ts
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Error Fingerprinting Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const ErrorFingerprintSchema = z.object({
|
||||||
|
id: z.string(), // ef_<uuid>
|
||||||
|
productId: z.string(),
|
||||||
|
fingerprintHash: z.string(), // SHA-256 of normalized error
|
||||||
|
|
||||||
|
// Normalized error signature
|
||||||
|
errorType: z.string(), // Exception class/name
|
||||||
|
messageTemplate: z.string(), // Normalized message with placeholders
|
||||||
|
stackSignature: z.string(), // Normalized stack frames
|
||||||
|
|
||||||
|
// Source location (normalized)
|
||||||
|
sourceLocation: z
|
||||||
|
.object({
|
||||||
|
file: z.string().optional(),
|
||||||
|
function: z.string().optional(),
|
||||||
|
line: z.number().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
createdAt: z.string(),
|
||||||
|
updatedAt: z.string(),
|
||||||
|
occurrenceCount: z.number().default(1),
|
||||||
|
uniqueUsers: z.number().default(1),
|
||||||
|
|
||||||
|
// TTL for cleanup
|
||||||
|
ttl: z.number().default(90 * 86400), // 90 days
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ErrorFingerprint = z.infer<typeof ErrorFingerprintSchema>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Error Cluster Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const ClusterStatusSchema = z.enum(['active', 'investigating', 'resolved', 'ignored']);
|
||||||
|
export type ClusterStatus = z.infer<typeof ClusterStatusSchema>;
|
||||||
|
|
||||||
|
export const CommonContextSchema = z.object({
|
||||||
|
osVersions: z.array(z.object({ version: z.string(), count: z.number() })),
|
||||||
|
appVersions: z.array(z.object({ version: z.string(), count: z.number() })),
|
||||||
|
deviceModels: z.array(z.object({ model: z.string(), count: z.number() })),
|
||||||
|
screenContexts: z.array(z.object({ screen: z.string(), count: z.number() })),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CommonContext = z.infer<typeof CommonContextSchema>;
|
||||||
|
|
||||||
|
export const ErrorClusterDocSchema = z.object({
|
||||||
|
id: z.string(), // ec_<uuid>
|
||||||
|
productId: z.string(), // partition key
|
||||||
|
fingerprintHash: z.string(),
|
||||||
|
|
||||||
|
// Cluster metadata
|
||||||
|
firstSeenAt: z.string(), // ISO 8601
|
||||||
|
lastSeenAt: z.string(),
|
||||||
|
occurrenceCount: z.number(), // Total occurrences
|
||||||
|
uniqueUsers: z.number(), // Affected user count
|
||||||
|
|
||||||
|
// Error signature
|
||||||
|
errorType: z.string(), // Exception class/name
|
||||||
|
messageTemplate: z.string(), // Normalized message with placeholders
|
||||||
|
stackSignature: z.string(), // Normalized stack frames
|
||||||
|
|
||||||
|
// Vector embedding for semantic search
|
||||||
|
embedding: z.array(z.number()).optional(), // 1536-dim from text-embedding-3-small
|
||||||
|
embeddingVersion: z.string().optional(), // Model version for re-embedding
|
||||||
|
|
||||||
|
// Context patterns (auto-extracted)
|
||||||
|
commonContext: CommonContextSchema.optional(),
|
||||||
|
|
||||||
|
// Related data
|
||||||
|
relatedClusterIds: z.array(z.string()).default([]), // Similar clusters (vector similarity)
|
||||||
|
mergedIntoClusterId: z.string().optional(), // If deduplicated
|
||||||
|
|
||||||
|
// Resolution tracking
|
||||||
|
status: ClusterStatusSchema.default('active'),
|
||||||
|
resolvedAt: z.string().optional(),
|
||||||
|
resolutionCommit: z.string().optional(), // Link to fix
|
||||||
|
|
||||||
|
// Analysis metadata
|
||||||
|
lastAnalyzedAt: z.string().optional(),
|
||||||
|
insightId: z.string().optional(), // Link to latest diagnostic insight
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
createdAt: z.string(),
|
||||||
|
updatedAt: z.string(),
|
||||||
|
ttl: z.number().default(90 * 86400), // 90 days
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ErrorClusterDoc = z.infer<typeof ErrorClusterDocSchema>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Cluster Analysis Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const ClusterAnalysisSchema = z.object({
|
||||||
|
id: z.string(), // ca_<uuid>
|
||||||
|
clusterId: z.string(),
|
||||||
|
productId: z.string(),
|
||||||
|
|
||||||
|
// Analysis metadata
|
||||||
|
analyzedAt: z.string(),
|
||||||
|
analysisType: z.enum(['root_cause', 'pattern', 'comparison', 'trend']),
|
||||||
|
|
||||||
|
// AI-generated summary
|
||||||
|
summary: z.string(), // "This appears to be a memory leak in the image cache..."
|
||||||
|
patternDescription: z.string(), // Detailed pattern analysis
|
||||||
|
|
||||||
|
// Key observations
|
||||||
|
keyObservations: z.array(
|
||||||
|
z.object({
|
||||||
|
observation: z.string(),
|
||||||
|
evidence: z.string(),
|
||||||
|
confidence: z.enum(['high', 'medium', 'low']),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
// Related clusters
|
||||||
|
similarClusters: z.array(
|
||||||
|
z.object({
|
||||||
|
clusterId: z.string(),
|
||||||
|
similarityScore: z.number(),
|
||||||
|
reason: z.string(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
createdAt: z.string(),
|
||||||
|
updatedAt: z.string(),
|
||||||
|
ttl: z.number().default(90 * 86400),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ClusterAnalysis = z.infer<typeof ClusterAnalysisSchema>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Diagnostic Insight Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const RootCauseCategorySchema = z.enum([
|
||||||
|
'config',
|
||||||
|
'dependency',
|
||||||
|
'logic',
|
||||||
|
'resource',
|
||||||
|
'external',
|
||||||
|
'unknown',
|
||||||
|
]);
|
||||||
|
export type RootCauseCategory = z.infer<typeof RootCauseCategorySchema>;
|
||||||
|
|
||||||
|
export const EvidenceTypeSchema = z.enum([
|
||||||
|
'stack_trace',
|
||||||
|
'telemetry_pattern',
|
||||||
|
'device_correlation',
|
||||||
|
'api_failure',
|
||||||
|
'similar_issue',
|
||||||
|
'config_mismatch',
|
||||||
|
'version_regression',
|
||||||
|
]);
|
||||||
|
export type EvidenceType = z.infer<typeof EvidenceTypeSchema>;
|
||||||
|
|
||||||
|
export const EvidenceSchema = z.object({
|
||||||
|
type: EvidenceTypeSchema,
|
||||||
|
description: z.string(),
|
||||||
|
strength: z.enum(['strong', 'moderate', 'weak']),
|
||||||
|
data: z.record(z.unknown()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Evidence = z.infer<typeof EvidenceSchema>;
|
||||||
|
|
||||||
|
export const SimilarResolvedIssueSchema = z.object({
|
||||||
|
clusterId: z.string(),
|
||||||
|
resolution: z.string(),
|
||||||
|
confidence: z.number(), // 0.0-1.0
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SimilarResolvedIssue = z.infer<typeof SimilarResolvedIssueSchema>;
|
||||||
|
|
||||||
|
export const FeedbackStatsSchema = z.object({
|
||||||
|
helpful: z.number().default(0),
|
||||||
|
notHelpful: z.number().default(0),
|
||||||
|
engineerNotes: z.array(z.string()).default([]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FeedbackStats = z.infer<typeof FeedbackStatsSchema>;
|
||||||
|
|
||||||
|
export const DiagnosticInsightDocSchema = z.object({
|
||||||
|
id: z.string(), // di_<uuid>
|
||||||
|
clusterId: z.string(), // partition key (with productId)
|
||||||
|
productId: z.string(),
|
||||||
|
|
||||||
|
// AI-generated analysis
|
||||||
|
analysisType: z.enum(['root_cause', 'pattern', 'comparison', 'trend']),
|
||||||
|
generatedAt: z.string(),
|
||||||
|
|
||||||
|
// LLM output
|
||||||
|
rootCauseCategory: RootCauseCategorySchema,
|
||||||
|
hypothesis: z.string(), // Natural language explanation
|
||||||
|
reasoning: z.string(), // Why LLM thinks this
|
||||||
|
confidence: z.enum(['high', 'medium', 'low']),
|
||||||
|
confidenceScore: z.number(), // 0.0-1.0
|
||||||
|
|
||||||
|
// Evidence
|
||||||
|
evidence: z.array(EvidenceSchema),
|
||||||
|
|
||||||
|
// Suggested actions
|
||||||
|
suggestedInvestigation: z.array(z.string()),
|
||||||
|
potentialFixDirection: z.string().optional(),
|
||||||
|
similarResolvedIssues: z.array(SimilarResolvedIssueSchema).optional(),
|
||||||
|
|
||||||
|
// Feedback
|
||||||
|
feedbackStats: FeedbackStatsSchema.default({ helpful: 0, notHelpful: 0, engineerNotes: [] }),
|
||||||
|
|
||||||
|
// LLM metadata
|
||||||
|
modelUsed: z.string(), // gpt-4o, gpt-4o-mini
|
||||||
|
promptTokens: z.number(),
|
||||||
|
completionTokens: z.number(),
|
||||||
|
|
||||||
|
// Correlation IDs for context
|
||||||
|
correlationIds: z.array(z.string()).optional(),
|
||||||
|
telemetryEventIds: z.array(z.string()).optional(),
|
||||||
|
|
||||||
|
createdAt: z.string(),
|
||||||
|
updatedAt: z.string(),
|
||||||
|
ttl: z.number().default(90 * 86400),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DiagnosticInsightDoc = z.infer<typeof DiagnosticInsightDocSchema>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Natural Language Query Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const QueryIntentSchema = z.enum([
|
||||||
|
'root_cause',
|
||||||
|
'pattern_search',
|
||||||
|
'comparison',
|
||||||
|
'trend',
|
||||||
|
'impact',
|
||||||
|
]);
|
||||||
|
export type QueryIntent = z.infer<typeof QueryIntentSchema>;
|
||||||
|
|
||||||
|
export const ExtractedEntitiesSchema = z.object({
|
||||||
|
products: z.array(z.string()).optional(),
|
||||||
|
timeRange: z.object({ start: z.string(), end: z.string() }).optional(),
|
||||||
|
errorTypes: z.array(z.string()).optional(),
|
||||||
|
platforms: z.array(z.string()).optional(),
|
||||||
|
userSegments: z.array(z.string()).optional(),
|
||||||
|
clusterIds: z.array(z.string()).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ExtractedEntities = z.infer<typeof ExtractedEntitiesSchema>;
|
||||||
|
|
||||||
|
export const SupportingDataSchema = z.object({
|
||||||
|
type: z.enum(['cluster', 'telemetry', 'session', 'insight']),
|
||||||
|
id: z.string(),
|
||||||
|
relevanceScore: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SupportingData = z.infer<typeof SupportingDataSchema>;
|
||||||
|
|
||||||
|
export const NaturalLanguageQueryDocSchema = z.object({
|
||||||
|
id: z.string(), // nq_<uuid>
|
||||||
|
userId: z.string(), // Admin who asked
|
||||||
|
productId: z.string().optional(), // Optional filter
|
||||||
|
|
||||||
|
// Query
|
||||||
|
rawQuery: z.string(), // "Why did iOS keyboard crash yesterday?"
|
||||||
|
parsedIntent: QueryIntentSchema,
|
||||||
|
extractedEntities: ExtractedEntitiesSchema,
|
||||||
|
|
||||||
|
// Execution
|
||||||
|
executedQuery: z.string().optional(), // Translated Cosmos query
|
||||||
|
dataSources: z.array(z.string()).default([]), // Clusters, telemetry, sessions accessed
|
||||||
|
executionTimeMs: z.number().optional(),
|
||||||
|
|
||||||
|
// Response
|
||||||
|
aiResponse: z.string().optional(), // Generated answer
|
||||||
|
confidence: z.number().optional(), // Overall confidence
|
||||||
|
supportingData: z.array(SupportingDataSchema).optional(),
|
||||||
|
|
||||||
|
// Feedback
|
||||||
|
userRating: z.enum(['helpful', 'not_helpful']).optional(),
|
||||||
|
userComment: z.string().optional(),
|
||||||
|
|
||||||
|
createdAt: z.string(),
|
||||||
|
ttl: z.number().default(30 * 86400), // 30 days
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NaturalLanguageQueryDoc = z.infer<typeof NaturalLanguageQueryDocSchema>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Analysis Request/Response Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const AnalyzeClusterRequestSchema = z.object({
|
||||||
|
clusterId: z.string(),
|
||||||
|
analysisType: z.enum(['root_cause', 'pattern', 'comparison', 'trend']).default('root_cause'),
|
||||||
|
modelPreference: z.enum(['gpt-4o-mini', 'gpt-4o']).default('gpt-4o-mini'),
|
||||||
|
includeTelemetry: z.boolean().default(true),
|
||||||
|
includeSimilarClusters: z.boolean().default(true),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AnalyzeClusterRequest = z.infer<typeof AnalyzeClusterRequestSchema>;
|
||||||
|
|
||||||
|
export const SubmitFeedbackRequestSchema = z.object({
|
||||||
|
insightId: z.string(),
|
||||||
|
rating: z.enum(['helpful', 'not_helpful']),
|
||||||
|
note: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SubmitFeedbackRequest = z.infer<typeof SubmitFeedbackRequestSchema>;
|
||||||
|
|
||||||
|
export const QueryDiagnosticsRequestSchema = z.object({
|
||||||
|
query: z.string(),
|
||||||
|
productId: z.string().optional(),
|
||||||
|
timeRange: z.object({ start: z.string(), end: z.string() }).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type QueryDiagnosticsRequest = z.infer<typeof QueryDiagnosticsRequestSchema>;
|
||||||
|
|
||||||
|
export const SearchErrorsRequestSchema = z.object({
|
||||||
|
query: z.string(), // Semantic search query
|
||||||
|
productId: z.string().optional(),
|
||||||
|
limit: z.number().default(10),
|
||||||
|
similarityThreshold: z.number().default(0.75),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SearchErrorsRequest = z.infer<typeof SearchErrorsRequestSchema>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Error Event Types (for ingestion from telemetry)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const ErrorEventSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
productId: z.string(),
|
||||||
|
correlationId: z.string().optional(),
|
||||||
|
|
||||||
|
// Error details
|
||||||
|
errorType: z.string(),
|
||||||
|
message: z.string(),
|
||||||
|
stackTrace: z.string().optional(),
|
||||||
|
|
||||||
|
// Context
|
||||||
|
platform: z.string().optional(), // ios, android, web, desktop
|
||||||
|
osVersion: z.string().optional(),
|
||||||
|
appVersion: z.string().optional(),
|
||||||
|
deviceModel: z.string().optional(),
|
||||||
|
screen: z.string().optional(),
|
||||||
|
userId: z.string().optional(),
|
||||||
|
|
||||||
|
// Telemetry linking
|
||||||
|
precedingEvents: z.array(z.record(z.unknown())).optional(),
|
||||||
|
|
||||||
|
// Timestamp
|
||||||
|
timestamp: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ErrorEvent = z.infer<typeof ErrorEventSchema>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Proactive Alert Types
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
export const AlertTypeSchema = z.enum([
|
||||||
|
'new_cluster',
|
||||||
|
'cluster_spike',
|
||||||
|
'new_error_type',
|
||||||
|
'regression_detected',
|
||||||
|
'anomaly_detected',
|
||||||
|
]);
|
||||||
|
export type AlertType = z.infer<typeof AlertTypeSchema>;
|
||||||
|
|
||||||
|
export const ProactiveAlertSchema = z.object({
|
||||||
|
id: z.string(), // pa_<uuid>
|
||||||
|
productId: z.string(),
|
||||||
|
alertType: AlertTypeSchema,
|
||||||
|
|
||||||
|
// Target
|
||||||
|
clusterId: z.string().optional(),
|
||||||
|
errorType: z.string().optional(),
|
||||||
|
|
||||||
|
// Alert details
|
||||||
|
severity: z.enum(['critical', 'high', 'medium', 'low']),
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string(),
|
||||||
|
|
||||||
|
// Metrics
|
||||||
|
baselineCount: z.number().optional(),
|
||||||
|
currentCount: z.number(),
|
||||||
|
spikeRatio: z.number().optional(),
|
||||||
|
|
||||||
|
// AI analysis
|
||||||
|
aiSummary: z.string().optional(),
|
||||||
|
suggestedAction: z.string().optional(),
|
||||||
|
|
||||||
|
// Status
|
||||||
|
acknowledgedAt: z.string().optional(),
|
||||||
|
acknowledgedBy: z.string().optional(),
|
||||||
|
resolvedAt: z.string().optional(),
|
||||||
|
|
||||||
|
createdAt: z.string(),
|
||||||
|
ttl: z.number().default(30 * 86400),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type ProactiveAlert = z.infer<typeof ProactiveAlertSchema>;
|
||||||
Loading…
Reference in New Issue
Block a user