fix(backend): 修复详情页数据结构与前端不匹配的问题
将 get_video_analysis_data 返回的字段名改为匹配前端 VideoAnalysisData 类型: - cost_metrics_raw -> cost_metrics - cost_metrics_calculated -> calculated_metrics - 字段名统一使用前端期望的命名 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
7cd29c5980
commit
3ae63ff27a
@ -120,13 +120,13 @@ async def get_video_analysis_data(
|
|||||||
"""
|
"""
|
||||||
获取视频分析数据(T-024主接口)。
|
获取视频分析数据(T-024主接口)。
|
||||||
|
|
||||||
包含:
|
返回6大类指标(匹配前端 VideoAnalysisData 类型):
|
||||||
- 基础信息(从数据库)
|
- base_info: 基础信息
|
||||||
- 触达指标(从巨量云图API)
|
- reach_metrics: 触达指标
|
||||||
- A3指标
|
- a3_metrics: A3指标
|
||||||
- 搜索指标
|
- search_metrics: 搜索指标
|
||||||
- 费用指标
|
- cost_metrics: 费用指标
|
||||||
- 成本指标(计算得出)
|
- calculated_metrics: 成本指标(实时计算)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session: 数据库会话
|
session: 数据库会话
|
||||||
@ -137,26 +137,26 @@ async def get_video_analysis_data(
|
|||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ValueError: 视频不存在时抛出
|
ValueError: 视频不存在时抛出
|
||||||
YuntuAPIError: API调用失败时抛出
|
|
||||||
"""
|
"""
|
||||||
|
from app.services.brand_api import get_brand_names
|
||||||
|
|
||||||
# 1. 从数据库获取基础信息
|
# 1. 从数据库获取基础信息
|
||||||
video = await get_video_base_info(session, item_id)
|
video = await get_video_base_info(session, item_id)
|
||||||
if video is None:
|
if video is None:
|
||||||
raise ValueError(f"Video not found: {item_id}")
|
raise ValueError(f"Video not found: {item_id}")
|
||||||
|
|
||||||
# 2. 构建基础信息
|
# 2. 获取品牌名称
|
||||||
base_info = {
|
brand_name = ""
|
||||||
"item_id": video.item_id,
|
if video.brand_id:
|
||||||
"title": video.title,
|
brand_map = await get_brand_names([video.brand_id])
|
||||||
"video_url": video.video_url,
|
brand_name = brand_map.get(video.brand_id, video.brand_id)
|
||||||
"star_id": video.star_id,
|
|
||||||
"star_unique_id": video.star_unique_id,
|
# 3. 调用巨量云图API获取实时 A3 数据和 cost
|
||||||
"star_nickname": video.star_nickname,
|
a3_increase_cnt = 0
|
||||||
"publish_time": video.publish_time.isoformat() if video.publish_time else None,
|
ad_a3_increase_cnt = 0
|
||||||
"industry_name": video.industry_name,
|
natural_a3_increase_cnt = 0
|
||||||
}
|
api_cost = 0.0
|
||||||
|
|
||||||
# 3. 调用巨量云图API获取实时数据
|
|
||||||
try:
|
try:
|
||||||
publish_time = video.publish_time or datetime.now()
|
publish_time = video.publish_time or datetime.now()
|
||||||
industry_id = video.industry_id or ""
|
industry_id = video.industry_id or ""
|
||||||
@ -166,75 +166,101 @@ async def get_video_analysis_data(
|
|||||||
publish_time=publish_time,
|
publish_time=publish_time,
|
||||||
industry_id=industry_id,
|
industry_id=industry_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4. 解析API响应
|
|
||||||
analysis_data = parse_analysis_response(api_response)
|
analysis_data = parse_analysis_response(api_response)
|
||||||
|
a3_increase_cnt = analysis_data.get("a3_increase_cnt", 0)
|
||||||
|
ad_a3_increase_cnt = analysis_data.get("ad_a3_increase_cnt", 0)
|
||||||
|
natural_a3_increase_cnt = analysis_data.get("natural_a3_increase_cnt", 0)
|
||||||
|
api_cost = analysis_data.get("cost", 0)
|
||||||
|
|
||||||
except YuntuAPIError as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get yuntu analysis for {item_id}: {e.message}")
|
logger.warning(f"API failed for {item_id}: {e}, using DB data")
|
||||||
# API失败时,使用数据库中的数据
|
a3_increase_cnt = video.total_new_a3_cnt or 0
|
||||||
analysis_data = {
|
ad_a3_increase_cnt = video.heated_new_a3_cnt or 0
|
||||||
"total_show_cnt": video.total_play_cnt or 0,
|
natural_a3_increase_cnt = video.natural_new_a3_cnt or 0
|
||||||
"natural_show_cnt": video.natural_play_cnt or 0,
|
api_cost = video.total_cost or 0.0
|
||||||
"ad_show_cnt": video.heated_play_cnt or 0,
|
|
||||||
"total_play_cnt": video.total_play_cnt or 0,
|
# 4. 数据库字段
|
||||||
"natural_play_cnt": video.natural_play_cnt or 0,
|
estimated_video_cost = video.estimated_video_cost or 0.0
|
||||||
"ad_play_cnt": video.heated_play_cnt or 0,
|
natural_play_cnt = video.natural_play_cnt or 0
|
||||||
"effective_play_cnt": 0,
|
heated_play_cnt = video.heated_play_cnt or 0
|
||||||
"a3_increase_cnt": 0,
|
total_play_cnt = video.total_play_cnt or 0
|
||||||
"ad_a3_increase_cnt": 0,
|
after_view_search_uv = video.after_view_search_uv or 0
|
||||||
"natural_a3_increase_cnt": 0,
|
|
||||||
"after_view_search_uv": video.after_view_search_uv or 0,
|
|
||||||
"after_view_search_pv": 0,
|
|
||||||
"brand_search_uv": 0,
|
|
||||||
"product_search_uv": 0,
|
|
||||||
"return_search_cnt": video.return_search_cnt or 0,
|
|
||||||
"cost": video.estimated_video_cost or 0,
|
|
||||||
"natural_cost": 0,
|
|
||||||
"ad_cost": 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
# 5. 计算成本指标
|
# 5. 计算成本指标
|
||||||
cost = analysis_data.get("cost", 0) or (video.estimated_video_cost or 0)
|
# 预估加热费用 = max(total_cost - estimated_video_cost, 0)
|
||||||
cost_metrics = calculate_cost_metrics(
|
heated_cost = max(api_cost - estimated_video_cost, 0) if api_cost > estimated_video_cost else 0
|
||||||
cost=cost,
|
|
||||||
natural_play_cnt=analysis_data.get("natural_play_cnt", 0),
|
|
||||||
a3_increase_cnt=analysis_data.get("a3_increase_cnt", 0),
|
|
||||||
natural_a3_increase_cnt=analysis_data.get("natural_a3_increase_cnt", 0),
|
|
||||||
after_view_search_uv=analysis_data.get("after_view_search_uv", 0),
|
|
||||||
total_play_cnt=analysis_data.get("total_play_cnt", 0),
|
|
||||||
)
|
|
||||||
|
|
||||||
# 6. 组装返回数据
|
# 预估自然看后搜人数
|
||||||
|
estimated_natural_search_uv = None
|
||||||
|
if total_play_cnt > 0 and after_view_search_uv > 0:
|
||||||
|
estimated_natural_search_uv = round((natural_play_cnt / total_play_cnt) * after_view_search_uv, 2)
|
||||||
|
|
||||||
|
# 预估CPM = (total_cost / total_play_cnt) * 1000
|
||||||
|
estimated_cpm = round((api_cost / total_play_cnt) * 1000, 2) if total_play_cnt > 0 else None
|
||||||
|
|
||||||
|
# 预估自然CPM = (estimated_video_cost / natural_play_cnt) * 1000
|
||||||
|
estimated_natural_cpm = round((estimated_video_cost / natural_play_cnt) * 1000, 2) if natural_play_cnt > 0 else None
|
||||||
|
|
||||||
|
# 预估CPA3 = total_cost / a3_increase_cnt
|
||||||
|
estimated_cp_a3 = round(api_cost / a3_increase_cnt, 2) if a3_increase_cnt > 0 else None
|
||||||
|
|
||||||
|
# 预估自然CPA3 = estimated_video_cost / natural_a3_increase_cnt
|
||||||
|
estimated_natural_cp_a3 = round(estimated_video_cost / natural_a3_increase_cnt, 2) if natural_a3_increase_cnt > 0 else None
|
||||||
|
|
||||||
|
# 预估CPsearch = total_cost / after_view_search_uv
|
||||||
|
estimated_cp_search = round(api_cost / after_view_search_uv, 2) if after_view_search_uv > 0 else None
|
||||||
|
|
||||||
|
# 自然CPsearch = estimated_video_cost / estimated_natural_search_uv
|
||||||
|
estimated_natural_cp_search = round(estimated_video_cost / estimated_natural_search_uv, 2) if estimated_natural_search_uv and estimated_natural_search_uv > 0 else None
|
||||||
|
|
||||||
|
# 6. 组装返回数据(匹配前端 VideoAnalysisData 类型)
|
||||||
return {
|
return {
|
||||||
"base_info": base_info,
|
"base_info": {
|
||||||
|
"star_nickname": video.star_nickname or "",
|
||||||
|
"star_unique_id": video.star_unique_id or "",
|
||||||
|
"vid": video.item_id,
|
||||||
|
"title": video.title or "",
|
||||||
|
"create_date": video.publish_time.isoformat() if video.publish_time else None,
|
||||||
|
"hot_type": video.viral_type or "",
|
||||||
|
"industry_id": video.industry_id or "",
|
||||||
|
"brand_id": video.brand_id or "",
|
||||||
|
"brand_name": brand_name,
|
||||||
|
"video_url": video.video_url or "",
|
||||||
|
},
|
||||||
"reach_metrics": {
|
"reach_metrics": {
|
||||||
"total_show_cnt": analysis_data.get("total_show_cnt", 0),
|
"natural_play_cnt": natural_play_cnt,
|
||||||
"natural_show_cnt": analysis_data.get("natural_show_cnt", 0),
|
"heated_play_cnt": heated_play_cnt,
|
||||||
"ad_show_cnt": analysis_data.get("ad_show_cnt", 0),
|
"total_play_cnt": total_play_cnt,
|
||||||
"total_play_cnt": analysis_data.get("total_play_cnt", 0),
|
"total_interaction_cnt": video.total_interact or 0,
|
||||||
"natural_play_cnt": analysis_data.get("natural_play_cnt", 0),
|
"digg_cnt": video.like_cnt or 0,
|
||||||
"ad_play_cnt": analysis_data.get("ad_play_cnt", 0),
|
"share_cnt": video.share_cnt or 0,
|
||||||
"effective_play_cnt": analysis_data.get("effective_play_cnt", 0),
|
"comment_cnt": video.comment_cnt or 0,
|
||||||
},
|
},
|
||||||
"a3_metrics": {
|
"a3_metrics": {
|
||||||
"a3_increase_cnt": analysis_data.get("a3_increase_cnt", 0),
|
"total_new_a3_cnt": a3_increase_cnt,
|
||||||
"ad_a3_increase_cnt": analysis_data.get("ad_a3_increase_cnt", 0),
|
"heated_new_a3_cnt": ad_a3_increase_cnt,
|
||||||
"natural_a3_increase_cnt": analysis_data.get("natural_a3_increase_cnt", 0),
|
"natural_new_a3_cnt": natural_a3_increase_cnt,
|
||||||
},
|
},
|
||||||
"search_metrics": {
|
"search_metrics": {
|
||||||
"after_view_search_uv": analysis_data.get("after_view_search_uv", 0),
|
"back_search_uv": video.return_search_cnt or 0,
|
||||||
"after_view_search_pv": analysis_data.get("after_view_search_pv", 0),
|
"back_search_cnt": video.return_search_cnt or 0,
|
||||||
"brand_search_uv": analysis_data.get("brand_search_uv", 0),
|
"after_view_search_uv": after_view_search_uv,
|
||||||
"product_search_uv": analysis_data.get("product_search_uv", 0),
|
"after_view_search_cnt": after_view_search_uv,
|
||||||
"return_search_cnt": analysis_data.get("return_search_cnt", 0),
|
"estimated_natural_search_uv": estimated_natural_search_uv,
|
||||||
},
|
},
|
||||||
"cost_metrics_raw": {
|
"cost_metrics": {
|
||||||
"cost": analysis_data.get("cost", 0),
|
"total_cost": api_cost,
|
||||||
"natural_cost": analysis_data.get("natural_cost", 0),
|
"heated_cost": heated_cost,
|
||||||
"ad_cost": analysis_data.get("ad_cost", 0),
|
"estimated_video_cost": estimated_video_cost,
|
||||||
|
},
|
||||||
|
"calculated_metrics": {
|
||||||
|
"estimated_cpm": estimated_cpm,
|
||||||
|
"estimated_natural_cpm": estimated_natural_cpm,
|
||||||
|
"estimated_cp_a3": estimated_cp_a3,
|
||||||
|
"estimated_natural_cp_a3": estimated_natural_cp_a3,
|
||||||
|
"estimated_cp_search": estimated_cp_search,
|
||||||
|
"estimated_natural_cp_search": estimated_natural_cp_search,
|
||||||
},
|
},
|
||||||
"cost_metrics_calculated": cost_metrics,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user