重做斗鱼粉丝日报信息模板
- 将粉丝日报改为信息优先布局,新增重点信息、话题簇、英雄焦点和热点窗口区块 - 让模板直接展示本地提纯出的有效信息,不再只依赖少量乐子文案 - 补充粉丝日报渲染辅助函数,提升证据簇和高频信息的承载能力
This commit is contained in:
@@ -462,6 +462,159 @@ def _render_fans_metric_cards(metrics: List[Dict[str, str]]) -> str:
|
||||
return "".join(blocks)
|
||||
|
||||
|
||||
def _build_fans_effective_info_lines(payload: Dict[str, Any], limit: int = 6) -> List[str]:
|
||||
"""
|
||||
为粉丝日报补一层“有效信息速览”。
|
||||
这里优先从本地提纯后的主题证据簇中拿事实,不依赖 LLM 自己归纳,
|
||||
这样模板本身就能稳定承载更多有效信息。
|
||||
"""
|
||||
lines: List[str] = []
|
||||
seen = set()
|
||||
|
||||
def push(text: str) -> None:
|
||||
value = str(text or "").strip()
|
||||
if not value or value in seen:
|
||||
return
|
||||
seen.add(value)
|
||||
lines.append(value)
|
||||
|
||||
for item in (payload.get("topic_evidence_clusters", []) or [])[:6]:
|
||||
label = str(item.get("label") or "").strip()
|
||||
count = int(item.get("count", 0) or 0)
|
||||
time_range = str(item.get("time_range") or "").strip()
|
||||
samples = item.get("samples", []) or []
|
||||
sample_text = ""
|
||||
if samples:
|
||||
first_sample = samples[0]
|
||||
sample_text = str(first_sample.get("content") or "").strip()[:42]
|
||||
if label and sample_text:
|
||||
push(f"{label}在 {time_range or '全场'} 持续被讨论,相关弹幕约 {count} 条,代表内容是「{sample_text}」。")
|
||||
elif label:
|
||||
push(f"{label}是今天的高关注话题之一,相关弹幕约 {count} 条。")
|
||||
if len(lines) >= limit:
|
||||
return lines[:limit]
|
||||
|
||||
hero_mentions = payload.get("compact_scene_material", {}).get("semantic_fact_hints", {}).get("hero_mentions", []) or []
|
||||
if hero_mentions:
|
||||
hero_names = [str(item.get("hero") or "").strip() for item in hero_mentions[:4] if str(item.get("hero") or "").strip()]
|
||||
if hero_names:
|
||||
push(f"英雄讨论主要集中在 {'、'.join(hero_names)}。")
|
||||
|
||||
hot_windows = payload.get("local_stats", {}).get("peak_windows", []) or []
|
||||
if hot_windows:
|
||||
top_window = hot_windows[0]
|
||||
push(
|
||||
f"最热窗口出现在 {str(top_window.get('start_time') or '')[-8:-3]},"
|
||||
f"该时段累计弹幕 {int(top_window.get('message_count', 0) or 0)} 条。"
|
||||
)
|
||||
return lines[:limit]
|
||||
|
||||
|
||||
def _render_fans_info_cards(items: List[str]) -> str:
|
||||
blocks = []
|
||||
for item in items[:6]:
|
||||
blocks.append(
|
||||
'<div class="fans-info-card">'
|
||||
f'<div class="fans-info-text">{_escape(item)}</div>'
|
||||
'</div>'
|
||||
)
|
||||
return "".join(blocks)
|
||||
|
||||
|
||||
def _render_topic_clusters(topic_clusters: List[Dict[str, Any]]) -> str:
|
||||
blocks = []
|
||||
for item in topic_clusters[:5]:
|
||||
label = str(item.get("label") or "").strip()
|
||||
if not label:
|
||||
continue
|
||||
count = int(item.get("count", 0) or 0)
|
||||
user_count = int(item.get("user_count", 0) or 0)
|
||||
time_range = str(item.get("time_range") or "").strip()
|
||||
samples = item.get("samples", []) or []
|
||||
sample_lines = []
|
||||
for sample in samples[:3]:
|
||||
content = str(sample.get("content") or "").strip()
|
||||
nickname = str(sample.get("nickname") or "").strip() or "观众"
|
||||
hm = str(sample.get("hm") or "").strip()
|
||||
if content:
|
||||
sample_lines.append(f"{hm} {nickname}:{content[:42]}")
|
||||
sample_html = "".join(f'<li>{_escape(line)}</li>' for line in sample_lines)
|
||||
blocks.append(
|
||||
'<div class="topic-cluster-card">'
|
||||
f'<div class="topic-cluster-title">{_escape(label)}</div>'
|
||||
f'<div class="topic-cluster-meta">{_escape(time_range or "全场")} · {count} 条讨论 · {user_count} 人参与</div>'
|
||||
f'<ul class="topic-cluster-list">{sample_html}</ul>'
|
||||
'</div>'
|
||||
)
|
||||
return "".join(blocks)
|
||||
|
||||
|
||||
def _render_hero_mentions(hero_mentions: List[Dict[str, Any]]) -> str:
|
||||
blocks = []
|
||||
for item in hero_mentions[:4]:
|
||||
hero_name = str(item.get("hero") or "").strip()
|
||||
if not hero_name:
|
||||
continue
|
||||
mention_count = int(item.get("mention_count", 0) or 0)
|
||||
user_count = int(item.get("user_count", 0) or 0)
|
||||
sample_text = ""
|
||||
samples = item.get("samples", []) or []
|
||||
if samples:
|
||||
sample = samples[0]
|
||||
sample_text = str(sample.get("content") or "").strip()[:46]
|
||||
blocks.append(
|
||||
'<div class="hero-mention-card">'
|
||||
f'<div class="hero-mention-name">{_escape(hero_name)}</div>'
|
||||
f'<div class="hero-mention-meta">{mention_count} 次提及 / {user_count} 人讨论</div>'
|
||||
f'<div class="hero-mention-sample">{_escape(sample_text)}</div>'
|
||||
'</div>'
|
||||
)
|
||||
return "".join(blocks)
|
||||
|
||||
|
||||
def _render_hot_window_cards(hot_windows: List[Dict[str, Any]]) -> str:
|
||||
blocks = []
|
||||
for item in hot_windows[:4]:
|
||||
start_time = str(item.get("start_time") or "")[-8:-3]
|
||||
message_count = int(item.get("message_count", 0) or 0)
|
||||
user_count = int(item.get("user_count", 0) or 0)
|
||||
blocks.append(
|
||||
'<div class="fans-hot-window-card">'
|
||||
f'<div class="fans-hot-window-time">{_escape(start_time)}</div>'
|
||||
f'<div class="fans-hot-window-meta">{message_count} 条弹幕 / {user_count} 人参与</div>'
|
||||
'</div>'
|
||||
)
|
||||
return "".join(blocks)
|
||||
|
||||
|
||||
def _render_repeat_digest(payload: Dict[str, Any]) -> str:
|
||||
local_stats = payload.get("local_stats", {}) or {}
|
||||
repeated_messages = local_stats.get("top_repeated_messages", []) or []
|
||||
emotion_bursts = local_stats.get("top_emotion_bursts", []) or []
|
||||
blocks = []
|
||||
for item in repeated_messages[:4]:
|
||||
text = str(item.get("text") or "").strip()
|
||||
count = int(item.get("count", 0) or 0)
|
||||
if text:
|
||||
blocks.append(
|
||||
'<div class="repeat-chip">'
|
||||
f'<span class="repeat-chip-text">{_escape(text[:28])}</span>'
|
||||
f'<span class="repeat-chip-count">{count} 次</span>'
|
||||
'</div>'
|
||||
)
|
||||
for item in emotion_bursts[:4]:
|
||||
text = str(item.get("text") or "").strip()
|
||||
count = int(item.get("count", 0) or 0)
|
||||
if text:
|
||||
blocks.append(
|
||||
'<div class="repeat-chip emotion">'
|
||||
f'<span class="repeat-chip-text">{_escape(text[:18])}</span>'
|
||||
f'<span class="repeat-chip-count">{count} 次</span>'
|
||||
'</div>'
|
||||
)
|
||||
return "".join(blocks)
|
||||
|
||||
|
||||
def _render_badges(top_badges: List[Dict[str, Any]]) -> str:
|
||||
blocks = []
|
||||
for item in top_badges[:6]:
|
||||
@@ -783,6 +936,14 @@ def render_fans_daily_report_html(
|
||||
lead_text = str(sections.get("lead") or "").strip()
|
||||
if not lead_text:
|
||||
lead_text = "今天这场直播的弹幕主打一个集体上头,观众一边盯着画面,一边忙着把梗越刷越离谱。"
|
||||
topic_clusters = payload.get("topic_evidence_clusters", []) or []
|
||||
hero_mentions = (
|
||||
payload.get("compact_scene_material", {})
|
||||
.get("semantic_fact_hints", {})
|
||||
.get("hero_mentions", [])
|
||||
or []
|
||||
)
|
||||
local_stats = payload.get("local_stats", {}) or {}
|
||||
|
||||
renderer = HtmlTemplateRenderer()
|
||||
return renderer.render(
|
||||
@@ -791,8 +952,13 @@ def render_fans_daily_report_html(
|
||||
"title_name": title_name,
|
||||
"subtitle": subtitle,
|
||||
"lead_text": lead_text,
|
||||
# 粉丝版刻意弱化“分析感”,下面几块都只展示娱乐化后的结果。
|
||||
# 粉丝版不再只做“乐子文案展示”,而是补进本地提纯后的有效信息区。
|
||||
"fans_metrics_html": Markup(_render_fans_metric_cards(_build_fans_fun_metrics(payload))),
|
||||
"effective_info_html": Markup(_render_fans_info_cards(_build_fans_effective_info_lines(payload))),
|
||||
"topic_clusters_html": Markup(_render_topic_clusters(topic_clusters)),
|
||||
"hero_mentions_html": Markup(_render_hero_mentions(hero_mentions)),
|
||||
"hot_windows_html": Markup(_render_hot_window_cards(local_stats.get("peak_windows", []) or [])),
|
||||
"repeat_digest_html": Markup(_render_repeat_digest(payload)),
|
||||
"laugh_points_html": Markup(_render_list(laugh_points, item_class="funny-list")),
|
||||
"famous_scenes_html": Markup(_render_fans_scene_cards(famous_scenes)),
|
||||
"meme_rank_html": Markup(_render_rank_cards(meme_rank)),
|
||||
|
||||
Reference in New Issue
Block a user