fix(growth): rank the rate board by a smoothed score

Show the real percentage on the card, but sort with delta / (baseline + prior) so a small baseline cannot dominate. Same-day publishes stay off the rate board.
This commit is contained in:
kairee committed 2026-11-02 22:06:41 +08:00
1 parent 0f2ac612f5
commit 39a6537f67
14 files changed
+158 -16

No files matched your search

+1
View File
@@ -50,6 +50,7 @@ export async function renderAbout(root) {
<li><strong>播放 / 投币 / 收藏 / 评论</strong>:直接取当日快照;弹幕与转发不单列,已并入“互动”。</li>
<li><strong>互动</strong>:点赞 + 投币 + 收藏。综合热度也使用全部七项指标,按配置权重加权。</li>
<li><strong>互动率</strong>:(点赞 + 投币 + 收藏) / 播放;播放不足 100 不计算。</li>
<li><strong>播放增速</strong>:卡片上的百分比是增量 / 基线播放。排序用增量 / (基线 + ${analytics.growth_rate_prior}),避免小基数霸榜。当日发布没有基线,不进增速榜。</li>
<li><strong>综合热度</strong>:每项先 <code>log1p</code>,再按中位数与 IQR 做稳健标准化,最后加权求和。分数只用于同一批数据内排序,不能跨日期比较。</li>
<li><strong>内容效率</strong>:播放 / 内容小时。内容小时 = 视频总时长(多 P 为各 P 之和)÷ 3600。榜单里的单条视频用收缩后的估计:<code>(播放 + κ·μ₀) / (小时 + κ)</code>,κ 默认 ${analytics.video_shrink_minutes} 分钟、μ₀ 为全库播放/小时;详情页保留原始播放/小时。</li>
<li><strong>互动效率</strong>:互动 / 内容小时,互动口径与收缩方式同上。</li>
+14 -4
View File
@@ -166,6 +166,7 @@ function listHtml(data, scope, sort, startRank) {
return videoCard(item, {
rank: startRank + index, primary: primary.primary,
primaryLabel: primary.label, primaryText: primary.text,
growthField: sort === "growth_rate" ? "rate" : "delta",
});
}).join("")}</div>`;
}
@@ -179,9 +180,18 @@ function durationNote(data) {
return `<div class="section-note">内容效率与互动效率:时长 ≥ ${filter.min_duration_seconds} 秒、发布满 ${filter.maturity_days} 天、播放 ≥ ${filter.min_view}${shrink};时长缺失不参与。</div>`;
}
function growthNote(sort, growthDays) {
if (sort !== "growth" && sort !== "growth_rate") return "";
if (Number(growthDays) !== 1) return "";
function growthNote(sort, growthDays, prior) {
if (sort === "growth_rate") {
const strength = Number(prior);
const smooth = Number.isFinite(strength)
? `百分比是真实相对增量;排序按平滑增速(基线 + ${strength})。`
: "百分比是真实相对增量;排序按平滑增速。";
const cold = Number(growthDays) === 1
? "当日发布不进增速榜。更早发布的新入库按入库时播放量起算,需要当天至少两次采样。"
: "";
return `<div class="section-note">${smooth}${cold}</div>`;
}
if (sort !== "growth" || Number(growthDays) !== 1) return "";
return `<div class="section-note">近 1 日:当日发布按 0 起算;更早发布的新入库按入库时播放量起算,需要当天至少两次采样。</div>`;
}
@@ -251,7 +261,7 @@ async function renderScope(root, { query }, scope) {
</header>
${controls({ scope, sort, within, year, growthDays, q, years })}
${durationNote(data)}
${growthNote(sort, growthDays)}
${growthNote(sort, growthDays, data.growth_rate_prior)}
${data.available === false ? `<div class="notice-row warning">
<strong>${sort.startsWith("growth") ? "增长数据尚不可用" : "榜单筛选后为空"}</strong>
<span>${escapeHtml(data.reason || "")}</span>