- Add health-dashboard page with 6-dimension health cards and anomaly detection - Add predictive/at-risk page with user risk profiles and segmentation - Add predictive/campaigns page with campaign management and stats - Add predictive-client.ts API client with full type coverage - Update all 3 roadmaps to reflect complete implementation status
251 lines
7.8 KiB
TypeScript
251 lines
7.8 KiB
TypeScript
/**
|
|
* Predictive Analytics API client for the admin dashboard.
|
|
* Churn prediction, health scoring, and retention campaigns.
|
|
*/
|
|
|
|
import { createApiClient } from '@bytelyst/api-client';
|
|
|
|
const predictiveApi = createApiClient({
|
|
baseUrl: `${process.env.PLATFORM_SERVICE_URL || 'http://localhost:4003'}/api`,
|
|
defaultHeaders: {
|
|
'x-product-id': process.env.PRODUCT_ID || 'lysnrai',
|
|
},
|
|
});
|
|
|
|
// ── Health Scoring ────────────────────────────────────────────
|
|
|
|
export interface HealthDimension {
|
|
score: number;
|
|
metrics: Record<string, number>;
|
|
trend: 'improving' | 'stable' | 'declining';
|
|
}
|
|
|
|
export interface ProductHealth {
|
|
id: string;
|
|
productId: string;
|
|
date: string;
|
|
overallHealthScore: number;
|
|
healthStatus: 'critical' | 'warning' | 'healthy';
|
|
dimensions: {
|
|
acquisition: HealthDimension;
|
|
activation: HealthDimension;
|
|
retention: HealthDimension;
|
|
engagement: HealthDimension;
|
|
revenue: HealthDimension;
|
|
stability: HealthDimension;
|
|
};
|
|
anomalies: Array<{
|
|
metric: string;
|
|
expectedValue: number;
|
|
actualValue: number;
|
|
deviationPercent: number;
|
|
severity: 'critical' | 'warning';
|
|
suggestedCause?: string;
|
|
}>;
|
|
forecasts: {
|
|
next7Days: { expectedHealthScore: number; confidenceInterval: [number, number] };
|
|
next30Days: { expectedHealthScore: number; confidenceInterval: [number, number] };
|
|
};
|
|
vsBaseline7Day: number;
|
|
vsBaseline30Day: number;
|
|
}
|
|
|
|
export async function getProductHealth(productId?: string): Promise<ProductHealth[]> {
|
|
const url = productId ? `/predictive/health?productId=${productId}` : '/predictive/health';
|
|
return predictiveApi.fetch<ProductHealth[]>(url);
|
|
}
|
|
|
|
export async function getProductHealthDetail(productId: string): Promise<ProductHealth> {
|
|
return predictiveApi.fetch<ProductHealth>(`/predictive/health/${productId}`);
|
|
}
|
|
|
|
export async function getHealthTrends(productId: string, days = 30): Promise<ProductHealth[]> {
|
|
return predictiveApi.fetch<ProductHealth[]>(`/predictive/health/${productId}/trends?days=${days}`);
|
|
}
|
|
|
|
// ── Churn Prediction ─────────────────────────────────────────
|
|
|
|
export type RiskSegment = 'critical' | 'high' | 'medium' | 'low';
|
|
|
|
export interface RiskFactor {
|
|
feature: string;
|
|
contribution: number;
|
|
direction: 'positive' | 'negative';
|
|
description: string;
|
|
}
|
|
|
|
export interface ChurnPrediction {
|
|
userId: string;
|
|
productId: string;
|
|
churnProbability: number;
|
|
riskSegment: RiskSegment;
|
|
confidenceScore: number;
|
|
modelVersion: string;
|
|
predictionTimestamp: string;
|
|
explanation: {
|
|
topRiskFactors: RiskFactor[];
|
|
nlExplanation: string;
|
|
suggestedActions: string[];
|
|
};
|
|
}
|
|
|
|
export async function getChurnScore(userId: string, productId: string, horizon = 30): Promise<ChurnPrediction> {
|
|
return predictiveApi.fetch<ChurnPrediction>('/predictive/churn-score', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId, productId, horizon: String(horizon) }),
|
|
});
|
|
}
|
|
|
|
export interface AtRiskUser {
|
|
userId: string;
|
|
productId: string;
|
|
churnProbability: number;
|
|
riskSegment: RiskSegment;
|
|
confidenceScore: number;
|
|
daysSinceLastSession: number;
|
|
predictionTimestamp: string;
|
|
}
|
|
|
|
export async function getAtRiskUsers(options: {
|
|
productId?: string;
|
|
segment?: RiskSegment;
|
|
limit?: number;
|
|
offset?: number;
|
|
} = {}): Promise<{ users: AtRiskUser[]; total: number }> {
|
|
const params = new URLSearchParams();
|
|
if (options.productId) params.set('productId', options.productId);
|
|
if (options.segment) params.set('segment', options.segment);
|
|
if (options.limit) params.set('limit', String(options.limit));
|
|
if (options.offset) params.set('offset', String(options.offset));
|
|
return predictiveApi.fetch<{ users: AtRiskUser[]; total: number }>(`/predictive/at-risk-users?${params}`);
|
|
}
|
|
|
|
export interface UserRiskProfile extends ChurnPrediction {
|
|
interventionHistory: Array<{
|
|
action: string;
|
|
timestamp: string;
|
|
outcome?: 'responded' | 'ignored' | 'churned' | 'retained';
|
|
}>;
|
|
}
|
|
|
|
export async function getUserRiskProfile(userId: string): Promise<UserRiskProfile> {
|
|
return predictiveApi.fetch<UserRiskProfile>(`/predictive/users/${userId}/risk-profile`);
|
|
}
|
|
|
|
// ── Model Performance ─────────────────────────────────────────
|
|
|
|
export interface ModelPerformance {
|
|
modelVersion: string;
|
|
modelType: string;
|
|
trainedAt: string;
|
|
auc: number;
|
|
precisionAt10: number;
|
|
recallAt10: number;
|
|
calibrationSlope: number;
|
|
featureImportance: Array<{ feature: string; importance: number }>;
|
|
}
|
|
|
|
export async function getModelPerformance(): Promise<ModelPerformance> {
|
|
return predictiveApi.fetch<ModelPerformance>('/predictive/model/performance');
|
|
}
|
|
|
|
export async function getFeatureImportance(): Promise<Array<{ feature: string; importance: number }>> {
|
|
const res = await predictiveApi.fetch<{ features: Array<{ feature: string; importance: number }> }>('/predictive/model/features');
|
|
return res.features;
|
|
}
|
|
|
|
// ── Retention Campaigns ──────────────────────────────────────
|
|
|
|
export type CampaignStatus = 'draft' | 'active' | 'paused' | 'completed';
|
|
export type CampaignTriggerType = 'churn_risk' | 'health_score_drop' | 'behavioral' | 'scheduled';
|
|
export type CampaignChannel = 'email' | 'push' | 'in_app' | 'slack_cs';
|
|
|
|
export interface Campaign {
|
|
id: string;
|
|
productId: string;
|
|
name: string;
|
|
description: string;
|
|
status: CampaignStatus;
|
|
trigger: {
|
|
type: CampaignTriggerType;
|
|
conditions: Array<{ field: string; operator: string; value: unknown }>;
|
|
};
|
|
audience: {
|
|
riskSegments?: string[];
|
|
products?: string[];
|
|
userSegments?: string[];
|
|
excludeRecentContact?: number;
|
|
};
|
|
messages: Array<{
|
|
channel: CampaignChannel;
|
|
templateId: string;
|
|
variant?: string;
|
|
delayHours?: number;
|
|
}>;
|
|
stats: {
|
|
triggered: number;
|
|
sent: number;
|
|
opened: number;
|
|
clicked: number;
|
|
converted: number;
|
|
controlChurnRate: number;
|
|
treatmentChurnRate: number;
|
|
};
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export async function listCampaigns(productId?: string): Promise<Campaign[]> {
|
|
const url = productId ? `/predictive/campaigns?productId=${productId}` : '/predictive/campaigns';
|
|
return predictiveApi.fetch<Campaign[]>(url);
|
|
}
|
|
|
|
export async function getCampaign(id: string): Promise<Campaign> {
|
|
return predictiveApi.fetch<Campaign>(`/predictive/campaigns/${id}`);
|
|
}
|
|
|
|
export async function createCampaign(input: {
|
|
name: string;
|
|
description: string;
|
|
productId: string;
|
|
trigger: {
|
|
type: CampaignTriggerType;
|
|
conditions: Array<{ field: string; operator: string; value: unknown }>;
|
|
};
|
|
audience: {
|
|
riskSegments?: string[];
|
|
products?: string[];
|
|
userSegments?: string[];
|
|
excludeRecentContact?: number;
|
|
};
|
|
messages: Array<{
|
|
channel: CampaignChannel;
|
|
templateId: string;
|
|
variant?: string;
|
|
delayHours?: number;
|
|
}>;
|
|
}): Promise<Campaign> {
|
|
return predictiveApi.fetch<Campaign>('/predictive/campaigns', {
|
|
method: 'POST',
|
|
body: JSON.stringify(input),
|
|
});
|
|
}
|
|
|
|
export async function updateCampaign(id: string, updates: Partial<Campaign>): Promise<Campaign> {
|
|
return predictiveApi.fetch<Campaign>(`/predictive/campaigns/${id}`, {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(updates),
|
|
});
|
|
}
|
|
|
|
export async function getCampaignStats(id: string): Promise<Campaign['stats']> {
|
|
return predictiveApi.fetch<Campaign['stats']>(`/predictive/campaigns/${id}/stats`);
|
|
}
|
|
|
|
export async function triggerCampaign(id: string, testUserId?: string): Promise<{ triggered: number }> {
|
|
return predictiveApi.fetch<{ triggered: number }>(`/predictive/campaigns/${id}/trigger`, {
|
|
method: 'POST',
|
|
body: JSON.stringify(testUserId ? { testUserId } : {}),
|
|
});
|
|
}
|