Files
makemd/server/src/core/ai/MerchantPredictionService.ts
wurenzhi b31591e04c feat: 实现多商户管理模块与前端服务
refactor: 优化服务层代码并修复类型问题

docs: 更新开发进度文档

feat(merchant): 新增商户监控与数据统计服务

feat(dashboard): 添加商户管理前端页面与服务

fix: 修复类型转换与可选参数处理

feat: 实现商户订单、店铺与结算管理功能

refactor: 重构审计日志格式与服务调用

feat: 新增商户入驻与身份注册功能

fix(controller): 修复路由参数类型问题

feat: 添加商户排名与统计报告功能

chore: 更新模拟数据与服务配置
2026-03-18 13:38:05 +08:00

403 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}
}