Files
makemd/server/src/core/ai/MerchantPredictionService.ts

403 lines
12 KiB
TypeScript
Raw Normal View History

import { Injectable, Logger } from '@nestjs/common';
import { Merchant, Order, Settlement } from '@prisma/client';
import { PrismaService } from '../../config/database';
interface MerchantBehaviorData {
merchantId: string;
orderCount: number;
totalSales: number;
averageOrderValue: number;
orderFrequency: number; // 日均订单数
refundRate: number;
activeDays: number; // 活跃天数
lastOrderDate: Date;
createdAt: Date;
}
interface BehaviorPredictionResult {
merchantId: string;
predictions: {
futureSales: number; // 预测未来30天销售额
orderTrend: 'increasing' | 'stable' | 'decreasing'; // 订单趋势
churnRisk: 'low' | 'medium' | 'high'; // 流失风险
growthPotential: 'high' | 'medium' | 'low'; // 增长潜力
};
confidence: number; // 预测置信度
factors: {
historicalTrend: number;
recentActivity: number;
customerRetention: number;
marketSeasonality: number;
};
recommendations: string[];
}
@Injectable()
export class MerchantPredictionService {
private readonly logger = new Logger(MerchantPredictionService.name);
constructor(private readonly prisma: PrismaService) {}
/**
*
* @param merchantId ID
* @param traceId ID
* @returns
*/
async predictMerchantBehavior(merchantId: string, traceId: string): Promise<BehaviorPredictionResult> {
this.logger.log(`开始预测商户行为: ${merchantId}`, { traceId, merchantId });
try {
// 获取商户历史行为数据
const behaviorData = await this.getMerchantBehaviorData(merchantId);
// 分析历史趋势
const historicalTrend = this.analyzeHistoricalTrend(merchantId);
// 分析最近活动
const recentActivity = this.analyzeRecentActivity(behaviorData);
// 分析客户留存
const customerRetention = this.analyzeCustomerRetention(merchantId);
// 分析市场季节性
const marketSeasonality = this.analyzeMarketSeasonality();
const factors = {
historicalTrend,
recentActivity,
customerRetention,
marketSeasonality,
};
// 预测未来销售额
const futureSales = this.predictFutureSales(behaviorData, factors);
// 预测订单趋势
const orderTrend = this.predictOrderTrend(historicalTrend, recentActivity);
// 预测流失风险
const churnRisk = this.predictChurnRisk(behaviorData, recentActivity);
// 评估增长潜力
const growthPotential = this.assessGrowthPotential(factors);
// 计算预测置信度
const confidence = this.calculateConfidence(factors);
// 生成建议
const recommendations = this.generateRecommendations({
...behaviorData,
...factors,
predictions: {
futureSales,
orderTrend,
churnRisk,
growthPotential,
},
});
const result: BehaviorPredictionResult = {
merchantId,
predictions: {
futureSales,
orderTrend,
churnRisk,
growthPotential,
},
confidence,
factors,
recommendations,
};
this.logger.log(`商户行为预测完成: ${merchantId}`, { traceId, result });
return result;
} catch (error) {
this.logger.error(`商户行为预测失败: ${error.message}`, { traceId, merchantId, error });
throw error;
}
}
/**
*
* @param merchantIds ID列表
* @param traceId ID
* @returns
*/
async batchPredictMerchantBehavior(merchantIds: string[], traceId: string): Promise<BehaviorPredictionResult[]> {
this.logger.log(`开始批量预测商户行为, 数量: ${merchantIds.length}`, { traceId });
const results: BehaviorPredictionResult[] = [];
for (const merchantId of merchantIds) {
try {
const result = await this.predictMerchantBehavior(merchantId, traceId);
results.push(result);
} catch (error) {
this.logger.error(`批量预测商户行为失败: ${merchantId}, 错误: ${error.message}`, { traceId, merchantId });
}
}
this.logger.log(`批量预测商户行为完成, 成功: ${results.length}/${merchantIds.length}`, { traceId });
return results;
}
/**
*
*/
private async getMerchantBehaviorData(merchantId: string): Promise<MerchantBehaviorData> {
// 获取商户基本信息
const merchant = await this.prisma.merchant.findUnique({
where: { id: merchantId },
});
if (!merchant) {
throw new Error(`商户不存在: ${merchantId}`);
}
// 获取商户订单数据
const orders = await this.prisma.order.findMany({
where: { merchantId },
select: {
id: true,
totalAmount: true,
status: true,
createdAt: true,
},
});
// 计算基本统计数据
const orderCount = orders.length;
const totalSales = orders.reduce((sum, order) => sum + order.totalAmount, 0);
const averageOrderValue = orderCount > 0 ? totalSales / orderCount : 0;
// 计算活跃天数
const activeDays = new Set(orders.map(order => order.createdAt.toISOString().split('T')[0])).size;
// 计算日均订单数
const daysSinceCreation = merchant.createdAt ?
Math.max(1, Math.floor((new Date().getTime() - merchant.createdAt.getTime()) / (1000 * 60 * 60 * 24))) : 1;
const orderFrequency = orderCount / daysSinceCreation;
// 计算退款率
const refundedOrders = orders.filter(order => order.status === 'REFUNDED').length;
const refundRate = orderCount > 0 ? refundedOrders / orderCount : 0;
// 获取最后订单日期
const lastOrder = orders.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())[0];
const lastOrderDate = lastOrder ? lastOrder.createdAt : merchant.createdAt;
return {
merchantId,
orderCount,
totalSales,
averageOrderValue,
orderFrequency,
refundRate,
activeDays,
lastOrderDate,
createdAt: merchant.createdAt,
};
}
/**
*
*/
private async analyzeHistoricalTrend(merchantId: string): Promise<number> {
// 这里可以实现更复杂的趋势分析逻辑
// 例如分析过去3个月的订单增长趋势
return Math.random() * 0.5 + 0.5; // 模拟趋势值 (0.5-1.0)
}
/**
*
*/
private analyzeRecentActivity(behaviorData: MerchantBehaviorData): number {
// 计算最近30天的活跃程度
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const daysSinceLastOrder = Math.floor((new Date().getTime() - behaviorData.lastOrderDate.getTime()) / (1000 * 60 * 60 * 24));
const recentActivity = Math.max(0, 1 - (daysSinceLastOrder / 30));
return recentActivity;
}
/**
*
*/
private async analyzeCustomerRetention(merchantId: string): Promise<number> {
// 这里可以实现更复杂的客户留存分析逻辑
// 例如:分析重复购买率
return Math.random() * 0.3 + 0.7; // 模拟留存率 (0.7-1.0)
}
/**
*
*/
private analyzeMarketSeasonality(): number {
// 这里可以实现更复杂的季节性分析逻辑
// 例如:根据当前月份分析季节性因素
const month = new Date().getMonth() + 1;
// 假设Q4是销售旺季
if (month >= 10 || month <= 2) {
return 1.2; // 旺季
} else if (month >= 3 && month <= 5) {
return 0.9; // 淡季
} else {
return 1.0; // 正常
}
}
/**
*
*/
private predictFutureSales(behaviorData: MerchantBehaviorData, factors: any): number {
// 基于历史数据和因素预测未来30天销售额
const dailySales = behaviorData.totalSales / Math.max(1, behaviorData.activeDays);
const basePrediction = dailySales * 30;
// 应用各种因素的影响
const adjustedPrediction = basePrediction *
(1 + (factors.historicalTrend - 0.5) * 0.3) *
(1 + (factors.recentActivity - 0.5) * 0.2) *
factors.marketSeasonality;
return Math.max(0, Math.round(adjustedPrediction));
}
/**
*
*/
private predictOrderTrend(historicalTrend: number, recentActivity: number): 'increasing' | 'stable' | 'decreasing' {
const combinedScore = (historicalTrend + recentActivity) / 2;
if (combinedScore > 0.7) {
return 'increasing';
} else if (combinedScore > 0.4) {
return 'stable';
} else {
return 'decreasing';
}
}
/**
*
*/
private predictChurnRisk(behaviorData: MerchantBehaviorData, recentActivity: number): 'low' | 'medium' | 'high' {
const daysSinceLastOrder = Math.floor((new Date().getTime() - behaviorData.lastOrderDate.getTime()) / (1000 * 60 * 60 * 24));
const activityScore = recentActivity;
if (daysSinceLastOrder > 60 || activityScore < 0.3) {
return 'high';
} else if (daysSinceLastOrder > 30 || activityScore < 0.6) {
return 'medium';
} else {
return 'low';
}
}
/**
*
*/
private assessGrowthPotential(factors: any): 'high' | 'medium' | 'low' {
const combinedScore = (factors.historicalTrend + factors.customerRetention) / 2;
if (combinedScore > 0.8) {
return 'high';
} else if (combinedScore > 0.6) {
return 'medium';
} else {
return 'low';
}
}
/**
*
*/
private calculateConfidence(factors: any): number {
// 基于数据完整性和因素稳定性计算置信度
const dataCompleteness = 0.9; // 假设数据完整性较好
const factorConsistency = (factors.historicalTrend + factors.recentActivity + factors.customerRetention) / 3;
const confidence = dataCompleteness * factorConsistency;
return Math.round(confidence * 100);
}
/**
*
*/
private generateRecommendations(data: any): string[] {
const recommendations: string[] = [];
if (data.predictions.churnRisk === 'high') {
recommendations.push('建议主动联系商户,了解经营情况并提供支持');
}
if (data.predictions.orderTrend === 'decreasing') {
recommendations.push('建议优化产品展示和营销策略,提升订单量');
}
if (data.predictions.growthPotential === 'high') {
recommendations.push('建议为商户提供高级功能和增值服务,支持其业务增长');
}
if (data.refundRate > 0.1) {
recommendations.push('建议帮助商户优化产品质量和客户服务,降低退款率');
}
if (data.averageOrderValue < 100) {
recommendations.push('建议引导商户增加高价值产品或实施捆绑销售策略');
}
return recommendations.length > 0 ? recommendations : ['商户经营状况稳定,继续保持'];
}
/**
*
* @param limit
* @param traceId ID
* @returns
*/
async getHighRiskMerchants(limit: number = 10, traceId: string): Promise<Array<{ merchantId: string; companyName: string; riskLevel: string; reason: string }>> {
this.logger.log(`获取高风险商户列表, 限制: ${limit}`, { traceId });
try {
// 获取所有活跃商户
const merchants = await this.prisma.merchant.findMany({
where: { status: 'ACTIVE' },
select: {
id: true,
companyName: true,
},
});
const merchantIds = merchants.map(m => m.id);
const predictions = await this.batchPredictMerchantBehavior(merchantIds, traceId);
// 筛选高风险商户
const highRiskMerchants = predictions
.filter(p => p.predictions.churnRisk === 'high')
.map(prediction => {
const merchant = merchants.find(m => m.id === prediction.merchantId);
return {
merchantId: prediction.merchantId,
companyName: merchant?.companyName || '未知商户',
riskLevel: prediction.predictions.churnRisk,
reason: prediction.recommendations[0] || '商户活动度低,存在流失风险',
};
})
.slice(0, limit);
this.logger.log(`获取高风险商户列表完成, 数量: ${highRiskMerchants.length}`, { traceId });
return highRiskMerchants;
} catch (error) {
this.logger.error(`获取高风险商户列表失败: ${error.message}`, { traceId, error });
throw error;
}
}
}