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_logs: { partitionKeyPath: '/pk', defaultTtl: 3 * 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)
|
||||
broadcasts: { partitionKeyPath: '/productId' },
|
||||
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