chore: 以本地为准,上传全部并替换 GitHub

This commit is contained in:
卡若
2026-02-03 11:36:53 +08:00
parent 1219166526
commit b404bf546e
131 changed files with 37618 additions and 3930 deletions

303
app/api/ai-tagging/route.ts Normal file
View File

@@ -0,0 +1,303 @@
/**
* AI 打标 API
* 提供智能标签生成、批量打标、标签推荐等功能
*/
import { NextRequest, NextResponse } from 'next/server'
import { getMongoClient, queryFullProfile, maskPhone } from '@/lib/mongodb'
// AI 标签规则
interface TagRule {
id: string
name: string
description: string
conditions: {
field: string
operator: 'eq' | 'ne' | 'gt' | 'lt' | 'gte' | 'lte' | 'contains' | 'in'
value: any
}[]
resultTag: string
category: string
priority: number
enabled: boolean
}
// 预定义的 AI 标签规则
const AI_TAG_RULES: TagRule[] = [
{
id: 'rule_1',
name: '钻石用户识别',
description: 'RFM评分≥80的用户标记为钻石用户',
conditions: [
{ field: 'rfm_composite_score', operator: 'gte', value: 80 }
],
resultTag: '钻石用户',
category: 'value',
priority: 1,
enabled: true
},
{
id: 'rule_2',
name: '高频活跃用户',
description: '近30天活跃次数>10的用户',
conditions: [
{ field: 'activity_count_30d', operator: 'gt', value: 10 }
],
resultTag: '高频活跃',
category: 'behavior',
priority: 2,
enabled: true
},
{
id: 'rule_3',
name: '流失风险用户',
description: '90天未活跃的用户',
conditions: [
{ field: 'last_active_days', operator: 'gt', value: 90 }
],
resultTag: '流失风险',
category: 'lifecycle',
priority: 3,
enabled: true
},
{
id: 'rule_4',
name: '一线城市用户',
description: '来自北上广深的用户',
conditions: [
{ field: 'city', operator: 'in', value: ['北京', '上海', '广州', '深圳'] }
],
resultTag: '一线城市',
category: 'region',
priority: 4,
enabled: true
},
{
id: 'rule_5',
name: '厦门本地用户',
description: '来自厦门的用户',
conditions: [
{ field: 'city', operator: 'eq', value: '厦门' }
],
resultTag: '厦门本地',
category: 'region',
priority: 5,
enabled: true
}
]
// 根据规则计算用户标签
function computeTagsForUser(userData: any, rules: TagRule[]): string[] {
const tags: string[] = []
for (const rule of rules) {
if (!rule.enabled) continue
let match = true
for (const condition of rule.conditions) {
const fieldValue = userData[condition.field]
switch (condition.operator) {
case 'eq':
match = match && fieldValue === condition.value
break
case 'ne':
match = match && fieldValue !== condition.value
break
case 'gt':
match = match && fieldValue > condition.value
break
case 'lt':
match = match && fieldValue < condition.value
break
case 'gte':
match = match && fieldValue >= condition.value
break
case 'lte':
match = match && fieldValue <= condition.value
break
case 'contains':
match = match && String(fieldValue).includes(condition.value)
break
case 'in':
match = match && Array.isArray(condition.value) && condition.value.includes(fieldValue)
break
}
}
if (match) {
tags.push(rule.resultTag)
}
}
return tags
}
// 为单个用户生成AI标签
async function generateTagsForUser(phone: string): Promise<{
phone: string
phoneMasked: string
originalTags: string[]
newTags: string[]
allTags: string[]
}> {
const profile = await queryFullProfile(phone)
const userData = {
...profile.valuation,
city: profile.valuation?.city || profile.qqPhone?.,
province: profile.valuation?.province || profile.qqPhone?.
}
const originalTags = profile.valuation?.tags || []
const newTags = computeTagsForUser(userData, AI_TAG_RULES)
const allTags = [...new Set([...originalTags, ...newTags])]
return {
phone,
phoneMasked: maskPhone(phone),
originalTags,
newTags,
allTags
}
}
// GET: 获取 AI 标签规则或统计
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const action = searchParams.get('action')
const phone = searchParams.get('phone')
try {
// 获取标签规则
if (action === 'rules') {
return NextResponse.json({
success: true,
rules: AI_TAG_RULES,
total: AI_TAG_RULES.length
})
}
// 为单个用户生成标签
if (phone) {
const result = await generateTagsForUser(phone)
return NextResponse.json({
success: true,
result
})
}
// AI 打标统计
const client = await getMongoClient()
const collection = client.db('KR').collection('用户估值')
const stats = await collection.aggregate([
{ $match: { tags: { $exists: true, $ne: [] } } },
{ $unwind: '$tags' },
{ $group: { _id: '$tags', count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 20 }
]).toArray()
return NextResponse.json({
success: true,
rules: AI_TAG_RULES,
tagDistribution: stats.map(s => ({
tag: s._id,
count: s.count
}))
})
} catch (error: any) {
console.error('AI 打标 API 错误:', error)
return NextResponse.json({
success: false,
error: error.message
}, { status: 500 })
}
}
// POST: 执行批量打标或创建规则
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { action, phones, rule, filters } = body
// 批量打标
if (action === 'batch') {
const results = []
const phonesToProcess = phones || []
for (const phone of phonesToProcess.slice(0, 100)) {
try {
const result = await generateTagsForUser(phone)
results.push(result)
} catch (e) {
results.push({
phone,
phoneMasked: maskPhone(phone),
error: '处理失败'
})
}
}
return NextResponse.json({
success: true,
processed: results.length,
results
})
}
// 按条件批量打标
if (action === 'batch_by_filter') {
const client = await getMongoClient()
const collection = client.db('KR').collection('用户估值')
// 构建查询条件
const query: any = {}
if (filters?.province) query.province = filters.province
if (filters?.city) query.city = filters.city
if (filters?.userLevel) query.user_level = filters.userLevel
const count = await collection.countDocuments(query)
// 模拟批量任务创建
const taskId = `task_${Date.now()}`
return NextResponse.json({
success: true,
taskId,
estimatedUsers: count,
message: `已创建批量打标任务,预计处理 ${count} 个用户`
})
}
// 创建标签规则
if (action === 'create_rule') {
const newRule: TagRule = {
id: `rule_${Date.now()}`,
...rule,
enabled: true
}
// TODO: 保存到数据库
return NextResponse.json({
success: true,
rule: newRule,
message: '标签规则创建成功'
})
}
return NextResponse.json({
success: false,
error: '未知操作'
}, { status: 400 })
} catch (error: any) {
return NextResponse.json({
success: false,
error: error.message
}, { status: 500 })
}
}