funel data

This commit is contained in:
2025-03-11 00:36:22 +08:00
parent 7857a9007a
commit bc42ff4dbf
13 changed files with 2171 additions and 11 deletions

View File

@@ -1341,7 +1341,7 @@ analyticsRouter.get('/reports/project/:id', async (c) => {
// 获取项目基本信息
const { data: project, error: projectError } = await supabase
.from('projects')
.select('id, name, description, created_at, created_by')
.select('id, name, description, created_at')
.eq('id', projectId)
.single();
@@ -1498,4 +1498,258 @@ analyticsRouter.get('/reports/project/:id', async (c) => {
}
});
// 获取KOL合作转换漏斗数据
analyticsRouter.get('/project/:id/conversion-funnel', async (c) => {
try {
const projectId = c.req.param('id');
const { timeframe = '30days' } = c.req.query();
// 获取项目信息
const { data: project, error: projectError } = await supabase
.from('projects')
.select('id, name, description, created_at')
.eq('id', projectId)
.single();
// 如果找不到项目或发生错误,返回模拟数据
if (projectError) {
console.log(`项目未找到或数据库错误返回模拟数据。项目ID: ${projectId}, 错误: ${projectError.message}`);
// 生成模拟的漏斗数据
const mockFunnelData = [
{ stage: 'Awareness', count: 100, rate: 100 },
{ stage: 'Interest', count: 75, rate: 75 },
{ stage: 'Consideration', count: 50, rate: 50 },
{ stage: 'Intent', count: 30, rate: 30 },
{ stage: 'Evaluation', count: 20, rate: 20 },
{ stage: 'Purchase', count: 10, rate: 10 }
];
return c.json({
project: {
id: projectId,
name: `模拟项目 (ID: ${projectId})`
},
timeframe,
funnel_data: mockFunnelData,
metrics: {
total_influencers: 100,
conversion_rate: 10,
avg_stage_dropoff: 18
},
is_mock_data: true
});
}
// 获取项目关联的网红及其详细信息
const { data: projectInfluencers, error: influencersError } = await supabase
.from('project_influencers')
.select(`
influencer_id,
influencers (
id,
name,
platform,
followers_count,
engagement_rate,
created_at
)
`)
.eq('project_id', projectId);
if (influencersError) {
console.error('Error fetching project influencers:', influencersError);
return c.json({ error: 'Failed to fetch project data' }, 500);
}
// 获取项目中的内容数据
const { data: projectPosts, error: postsError } = await supabase
.from('posts')
.select(`
id,
influencer_id,
platform,
published_at,
views_count,
likes_count,
comments_count,
shares_count
`)
.eq('project_id', projectId);
if (postsError) {
console.error('Error fetching project posts:', postsError);
return c.json({ error: 'Failed to fetch project posts' }, 500);
}
// 计算漏斗各阶段数据
const totalInfluencers = projectInfluencers.length;
// 1. 认知阶段 - 所有接触的KOL
const awarenessStage = {
stage: 'Awareness',
count: totalInfluencers,
rate: 100
};
// 2. 兴趣阶段 - 有互动的KOL (至少有一篇内容)
const influencersWithContent = new Set<string>();
projectPosts?.forEach(post => {
if (post.influencer_id) {
influencersWithContent.add(post.influencer_id);
}
});
const interestStage = {
stage: 'Interest',
count: influencersWithContent.size,
rate: Math.round((influencersWithContent.size / totalInfluencers) * 100)
};
// 3. 考虑阶段 - 有高互动的KOL (内容互动率高于平均值)
const engagementRates = projectInfluencers
.map(pi => pi.influencers?.[0]?.engagement_rate || 0)
.filter(rate => rate > 0);
const avgEngagementRate = engagementRates.length > 0
? engagementRates.reduce((sum, rate) => sum + rate, 0) / engagementRates.length
: 0;
const highEngagementInfluencers = projectInfluencers.filter(pi =>
(pi.influencers?.[0]?.engagement_rate || 0) > avgEngagementRate
);
const considerationStage = {
stage: 'Consideration',
count: highEngagementInfluencers.length,
rate: Math.round((highEngagementInfluencers.length / totalInfluencers) * 100)
};
// 4. 意向阶段 - 有多篇内容的KOL
const influencerContentCount: Record<string, number> = {};
projectPosts?.forEach(post => {
if (post.influencer_id) {
influencerContentCount[post.influencer_id] = (influencerContentCount[post.influencer_id] || 0) + 1;
}
});
const multiContentInfluencers = Object.keys(influencerContentCount).filter(
id => influencerContentCount[id] > 1
);
const intentStage = {
stage: 'Intent',
count: multiContentInfluencers.length,
rate: Math.round((multiContentInfluencers.length / totalInfluencers) * 100)
};
// 5. 评估阶段 - 内容表现良好的KOL (浏览量高于平均值)
const influencerViewsMap: Record<string, number> = {};
projectPosts?.forEach(post => {
if (post.influencer_id && post.views_count) {
influencerViewsMap[post.influencer_id] = (influencerViewsMap[post.influencer_id] || 0) + post.views_count;
}
});
const influencerViews = Object.values(influencerViewsMap);
const avgViews = influencerViews.length > 0
? influencerViews.reduce((sum, views) => sum + views, 0) / influencerViews.length
: 0;
const highViewsInfluencers = Object.keys(influencerViewsMap).filter(
id => influencerViewsMap[id] > avgViews
);
const evaluationStage = {
stage: 'Evaluation',
count: highViewsInfluencers.length,
rate: Math.round((highViewsInfluencers.length / totalInfluencers) * 100)
};
// 6. 购买/转化阶段 - 长期合作的KOL (3个月以上)
const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
const longTermInfluencers = projectInfluencers.filter(pi => {
const createdAt = pi.influencers?.[0]?.created_at;
if (!createdAt) return false;
const createdDate = new Date(createdAt);
return createdDate < threeMonthsAgo;
});
const purchaseStage = {
stage: 'Purchase',
count: longTermInfluencers.length,
rate: Math.round((longTermInfluencers.length / totalInfluencers) * 100)
};
// 构建完整漏斗数据
const funnelData = [
awarenessStage,
interestStage,
considerationStage,
intentStage,
evaluationStage,
purchaseStage
];
// 计算转化率
const conversionRate = totalInfluencers > 0
? Math.round((longTermInfluencers.length / totalInfluencers) * 100)
: 0;
// 计算平均转化率
const avgStageDropoff = funnelData.length > 1
? (100 - conversionRate) / (funnelData.length - 1)
: 0;
return c.json({
project: {
id: project.id,
name: project.name
},
timeframe,
funnel_data: funnelData,
metrics: {
total_influencers: totalInfluencers,
conversion_rate: conversionRate,
avg_stage_dropoff: Math.round(avgStageDropoff)
}
});
} catch (error) {
console.error('Error generating KOL conversion funnel:', error);
// 发生错误时也返回模拟数据
const projectId = c.req.param('id');
const { timeframe = '30days' } = c.req.query();
// 生成模拟的漏斗数据
const mockFunnelData = [
{ stage: 'Awareness', count: 100, rate: 100 },
{ stage: 'Interest', count: 75, rate: 75 },
{ stage: 'Consideration', count: 50, rate: 50 },
{ stage: 'Intent', count: 30, rate: 30 },
{ stage: 'Evaluation', count: 20, rate: 20 },
{ stage: 'Purchase', count: 10, rate: 10 }
];
return c.json({
project: {
id: projectId,
name: `模拟项目 (ID: ${projectId})`
},
timeframe,
funnel_data: mockFunnelData,
metrics: {
total_influencers: 100,
conversion_rate: 10,
avg_stage_dropoff: 18
},
is_mock_data: true,
error_message: '发生错误,返回模拟数据'
});
}
});
export default analyticsRouter;

View File

@@ -1987,6 +1987,137 @@ export const openAPISpec = {
}
}
},
'/api/analytics/project/{id}/conversion-funnel': {
get: {
summary: '获取KOL合作转换漏斗数据',
description: '获取项目中KOL合作的转换漏斗数据包括各个阶段的数量和比率',
tags: ['Analytics'],
security: [{ bearerAuth: [] }],
parameters: [
{
name: 'id',
in: 'path',
required: true,
description: '项目ID',
schema: {
type: 'string'
}
},
{
name: 'timeframe',
in: 'query',
required: false,
description: '时间范围 (7days, 30days, 90days, 6months)',
schema: {
type: 'string',
enum: ['7days', '30days', '90days', '6months'],
default: '30days'
}
}
],
responses: {
'200': {
description: '成功获取KOL合作转换漏斗数据',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
project: {
type: 'object',
properties: {
id: {
type: 'string',
description: '项目ID'
},
name: {
type: 'string',
description: '项目名称'
}
}
},
timeframe: {
type: 'string',
description: '时间范围'
},
funnel_data: {
type: 'array',
description: '漏斗数据',
items: {
type: 'object',
properties: {
stage: {
type: 'string',
description: '阶段名称'
},
count: {
type: 'integer',
description: 'KOL数量'
},
rate: {
type: 'integer',
description: '占总数的百分比'
}
}
}
},
metrics: {
type: 'object',
properties: {
total_influencers: {
type: 'integer',
description: 'KOL总数'
},
conversion_rate: {
type: 'integer',
description: '总体转化率'
},
avg_stage_dropoff: {
type: 'integer',
description: '平均阶段流失率'
}
}
}
}
}
}
}
},
'404': {
description: '项目未找到',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
error: {
type: 'string',
example: 'Project not found'
}
}
}
}
}
},
'500': {
description: '服务器错误',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
error: {
type: 'string',
example: 'Internal server error'
}
}
}
}
}
}
}
}
},
'/api/analytics/project/{id}/top-performers': {
get: {
tags: ['Analytics'],