refactor: restructure project into 5 core modules

Organize project by 5 core modules based on requirement docs.

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2026-01-31 04:18:24 +00:00
parent b4aa620c93
commit 22e725887a
74 changed files with 4219 additions and 9234 deletions

View File

@@ -1,249 +0,0 @@
/**
* RFM MongoDB 服务
* 从 MongoDB 获取真实 RFM 数据
*/
import {
getCollection,
getRFMGroupSummary,
getDistinctTags,
DB_NAMES,
COLLECTION_NAMES,
UserValuationDoc
} from '@/lib/mongodb'
export interface RFMStats {
gradeCount: Record<string, number>
valueCount: Record<string, number>
totalUsers: number
avgScore: number
}
/**
* 获取 RFM 分组统计(从 MongoDB
*/
export async function getMongoRFMGroupSummary(): Promise<RFMStats> {
try {
const result = await getRFMGroupSummary()
// 转换为前端期望的格式
const gradeCount: Record<string, number> = {
'S': 0,
'A': 0,
'B': 0,
'C': 0,
'D': 0
}
const valueCount: Record<string, number> = {
'高': 0,
'中': 0,
'低': 0
}
// 映射用户等级到 S/A/B/C/D
for (const item of result.levels) {
const level = item.level
if (level === 'S' || level === '钻石用户' || level === 'S级用户') {
gradeCount['S'] += item.count
valueCount['高'] += item.count
} else if (level === 'A' || level === '黄金用户' || level === 'A级用户') {
gradeCount['A'] += item.count
valueCount['高'] += item.count
} else if (level === 'B' || level === '白银用户' || level === 'B级用户') {
gradeCount['B'] += item.count
valueCount['中'] += item.count
} else if (level === 'C' || level === '青铜用户' || level === 'C级用户') {
gradeCount['C'] += item.count
valueCount['中'] += item.count
} else {
gradeCount['D'] += item.count
valueCount['低'] += item.count
}
}
// 计算平均分
const coll = await getCollection<UserValuationDoc>(DB_NAMES.KR, COLLECTION_NAMES.USER_VALUATION)
const avgResult = await coll.aggregate([
{ $match: { rfm_composite_score: { $exists: true, $ne: null } } },
{ $group: { _id: null, avgScore: { $avg: '$rfm_composite_score' } } }
]).toArray()
const avgScore = avgResult[0]?.avgScore || 0
return {
gradeCount,
valueCount,
totalUsers: result.total,
avgScore: Math.round(avgScore * 100) / 100
}
} catch (error) {
console.error('RFM stats error:', error)
return {
gradeCount: { S: 0, A: 0, B: 0, C: 0, D: 0 },
valueCount: { '高': 0, '中': 0, '低': 0 },
totalUsers: 0,
avgScore: 0
}
}
}
/**
* 获取用户 RFM 分析
*/
export async function analyzeUserRFM(phone: string): Promise<{
found: boolean
data: any
}> {
try {
const coll = await getCollection<UserValuationDoc>(DB_NAMES.KR, COLLECTION_NAMES.USER_VALUATION)
const user = await coll.findOne({ phone })
if (!user) {
return { found: false, data: null }
}
// 计算 RFM 等级
const rfmScore = user.rfm_composite_score || 0
let grade: 'S' | 'A' | 'B' | 'C' | 'D' = 'D'
if (rfmScore >= 4.5) grade = 'S'
else if (rfmScore >= 3.5) grade = 'A'
else if (rfmScore >= 2.5) grade = 'B'
else if (rfmScore >= 1.5) grade = 'C'
// 计算价值等级
let value: '高' | '中' | '低' = '低'
if (rfmScore >= 4.0) value = '高'
else if (rfmScore >= 2.8) value = '中'
// 生命周期标签
let lifecycle = '未知'
const rScore = user.rfm_r_score || 0
if (rScore >= 5) lifecycle = '活跃用户'
else if (rScore >= 3) lifecycle = '新用户'
else if (rScore === 2) lifecycle = '沉睡用户'
else lifecycle = '流失风险'
return {
found: true,
data: {
user_id: phone,
rfm_score: {
R: user.rfm_r_score || 0,
F: user.rfm_f_score || 0,
M: user.rfm_m_score || 0,
total: rfmScore,
grade
},
tags: {
lifecycle,
value,
behavior: user.tags || []
},
weights: { R: 0.3, F: 0.3, M: 0.4 },
raw: {
name: user.name,
province: user.province,
city: user.city,
userLevel: user.user_level,
totalAmount: user.total_amount,
orderCount: user.order_count
}
}
}
} catch (error) {
console.error('Analyze user RFM error:', error)
return { found: false, data: null }
}
}
/**
* 获取 RFM 标签分布
*/
export async function getRFMTagsDistribution(): Promise<{
tags: { name: string, count: number }[]
}> {
try {
const tags = await getDistinctTags()
// 对每个标签计数
const coll = await getCollection<UserValuationDoc>(DB_NAMES.KR, COLLECTION_NAMES.USER_VALUATION)
const tagCounts: { name: string, count: number }[] = []
// 只统计前 20 个标签
for (const tag of tags.slice(0, 20)) {
const count = await coll.countDocuments({ tags: tag })
tagCounts.push({ name: tag, count })
}
// 按数量排序
tagCounts.sort((a, b) => b.count - a.count)
return { tags: tagCounts }
} catch (error) {
console.error('Tags distribution error:', error)
return { tags: [] }
}
}
/**
* 获取 RFM 用户列表
*/
export async function getRFMUserList(options: {
grade?: string
page?: number
pageSize?: number
}): Promise<{
users: any[]
total: number
}> {
const { grade, page = 1, pageSize = 20 } = options
try {
const coll = await getCollection<UserValuationDoc>(DB_NAMES.KR, COLLECTION_NAMES.USER_VALUATION)
const query: any = {}
if (grade) {
// 映射等级
if (grade === 'S') {
query.rfm_composite_score = { $gte: 4.5 }
} else if (grade === 'A') {
query.rfm_composite_score = { $gte: 3.5, $lt: 4.5 }
} else if (grade === 'B') {
query.rfm_composite_score = { $gte: 2.5, $lt: 3.5 }
} else if (grade === 'C') {
query.rfm_composite_score = { $gte: 1.5, $lt: 2.5 }
} else {
query.rfm_composite_score = { $lt: 1.5 }
}
}
const [users, total] = await Promise.all([
coll.find(query)
.skip((page - 1) * pageSize)
.limit(pageSize)
.sort({ rfm_composite_score: -1 })
.toArray(),
coll.countDocuments(query)
])
return {
users: users.map(u => ({
id: u._id?.toString(),
phone: u.phone,
phone_masked: u.phone_masked || (u.phone ? `${u.phone.slice(0, 3)}****${u.phone.slice(-4)}` : ''),
name: u.name,
rfmScore: u.rfm_composite_score,
userLevel: u.user_level,
province: u.province,
tags: u.tags
})),
total
}
} catch (error) {
console.error('RFM user list error:', error)
return { users: [], total: 0 }
}
}