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:
1 parent
0f2ac612f5
commit
39a6537f67
14 files changed
+158
-16
No files matched your search
@@ -12,6 +12,7 @@ from reaction_board.analytics import ( # noqa: E402
|
||||
METRICS,
|
||||
age_days,
|
||||
compute_growth,
|
||||
smoothed_growth_rate,
|
||||
content_hours,
|
||||
eb_rate,
|
||||
effective_shrink_minutes,
|
||||
@@ -161,6 +162,19 @@ def test_compute_growth():
|
||||
assert negative.per_day == -5.0
|
||||
|
||||
|
||||
def test_smoothed_growth_rate():
|
||||
# 80→512:真实增速 5.4;prior=500 时排序分 432/580
|
||||
assert abs(smoothed_growth_rate(432, 80, 500) - 432 / 580) < 1e-12
|
||||
assert abs(smoothed_growth_rate(432, 5000, 500) - 432 / 5500) < 1e-12
|
||||
assert abs(smoothed_growth_rate(432, 80, 1000) - 432 / 1080) < 1e-12
|
||||
# prior=0 时排序分退回真实相对增速(基线 ≥ 1)
|
||||
assert smoothed_growth_rate(432, 80, 0) == 432 / 80
|
||||
assert smoothed_growth_rate(None, 80, 500) is None
|
||||
assert smoothed_growth_rate(432, None, 500) is None
|
||||
assert smoothed_growth_rate(10, 0, 0) is None
|
||||
assert smoothed_growth_rate(10, 80, -1) is None
|
||||
|
||||
|
||||
def test_normalize_cover_and_hosts():
|
||||
assert normalize_cover(None) is None
|
||||
assert normalize_cover("") is None
|
||||
@@ -251,6 +265,7 @@ def main_test():
|
||||
test_score_percentiles()
|
||||
test_interaction_rate_thresholds()
|
||||
test_compute_growth()
|
||||
test_smoothed_growth_rate()
|
||||
test_normalize_cover_and_hosts()
|
||||
test_content_efficiency_helpers()
|
||||
test_age_and_maturity()
|
||||
|
||||
@@ -146,6 +146,7 @@ def test_analytics_defaults_and_validation():
|
||||
assert cfg.analytics.video_shrink_minutes == 60
|
||||
assert cfg.analytics.shrink_minutes is None
|
||||
assert cfg.analytics.home_growth_days == "auto"
|
||||
assert cfg.analytics.growth_rate_prior == 500
|
||||
assert cfg.schedule.recent_snapshot_minutes == 60
|
||||
assert cfg.schedule.recent_video_days == 2
|
||||
assert cfg.schedule.snapshot_at == "23:15"
|
||||
@@ -159,6 +160,7 @@ def test_analytics_defaults_and_validation():
|
||||
"video_shrink_minutes": 30,
|
||||
"shrink_minutes": 90,
|
||||
"home_growth_days": 1,
|
||||
"growth_rate_prior": 1000,
|
||||
}
|
||||
data["schedule"] = {
|
||||
"discover_minutes": 30,
|
||||
@@ -176,6 +178,7 @@ def test_analytics_defaults_and_validation():
|
||||
assert cfg.analytics.video_shrink_minutes == 30
|
||||
assert cfg.analytics.shrink_minutes == 90
|
||||
assert cfg.analytics.home_growth_days == 1
|
||||
assert cfg.analytics.growth_rate_prior == 1000
|
||||
assert cfg.schedule.recent_snapshot_minutes == 15
|
||||
assert cfg.schedule.recent_video_days == 3
|
||||
|
||||
@@ -194,6 +197,7 @@ def test_analytics_defaults_and_validation():
|
||||
({"home_growth_days": 3}, "home_growth_days"),
|
||||
({"home_growth_days": "7"}, "home_growth_days"),
|
||||
({"home_growth_days": 0}, "home_growth_days"),
|
||||
({"growth_rate_prior": -1}, "growth_rate_prior"),
|
||||
):
|
||||
data = base_config()
|
||||
data["analytics"] = bad
|
||||
|
||||
@@ -492,6 +492,7 @@ def test_about_payload():
|
||||
"video_shrink_minutes": 60,
|
||||
"shrink_minutes": None,
|
||||
"home_growth_days": "auto",
|
||||
"growth_rate_prior": 500,
|
||||
}
|
||||
assert about["heat_weights"]["view"] == 0.30
|
||||
assert about["followers"]["videos_total"] == 3
|
||||
@@ -748,12 +749,18 @@ def test_same_day_growth_baselines():
|
||||
assert 106 not in by_aid and 107 not in by_aid
|
||||
assert by_aid[103]["growth"]["baseline"] == "zero"
|
||||
assert by_aid[103]["growth"]["delta"] == 200
|
||||
assert by_aid[103]["growth"]["rate"] is None
|
||||
assert by_aid[103]["growth"]["gap_days"] == 0
|
||||
assert by_aid[105]["growth"]["baseline"] == "open"
|
||||
assert by_aid[105]["growth"]["delta"] == 300
|
||||
assert by_aid[101]["growth"]["baseline"] == "snapshot"
|
||||
assert by_aid[101]["growth"]["delta"] == 100
|
||||
|
||||
rate = q.list_videos(sort="growth_rate", growth_days=1, page_size=20)
|
||||
rate_aids = {item["aid"] for item in rate["items"]}
|
||||
assert 103 not in rate_aids
|
||||
assert 101 in rate_aids and 105 in rate_aids
|
||||
|
||||
seven = q.list_videos(sort="growth", growth_days=7)
|
||||
seven_aids = {item["aid"] for item in seven["items"]}
|
||||
assert 103 not in seven_aids
|
||||
@@ -774,6 +781,41 @@ def test_same_day_growth_baselines():
|
||||
db.close()
|
||||
|
||||
|
||||
def test_growth_rate_sort_uses_prior():
|
||||
"""小基数的真实增速可以更高,排序分用基线 + prior,不按 raw rate 排。"""
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
db, q = setup(tmp)
|
||||
try:
|
||||
repo = Repository(db)
|
||||
latest = DATES[-1]
|
||||
previous_day = DATES[-2]
|
||||
start = int(day_bounds(latest)[0])
|
||||
pub = int(day_bounds("2026-09-01")[0])
|
||||
for aid, views in ((301, (80, 512)), (302, (2000, 4000))):
|
||||
repo.upsert_from_fav(
|
||||
resource(aid, up_mid=1, up_name="UP A", pubdate=pub),
|
||||
FOLDER.media_id, was_added=True, now=start,
|
||||
)
|
||||
repo.upsert_snapshot(
|
||||
previous_day, aid, metrics=_view_metrics(views[0]),
|
||||
ok=True, err=None, captured_at=start - 86400,
|
||||
)
|
||||
repo.upsert_snapshot(
|
||||
latest, aid, metrics=_view_metrics(views[1]),
|
||||
ok=True, err=None, captured_at=start + 60,
|
||||
)
|
||||
DASHBOARD_CACHE.clear()
|
||||
ranked = q.list_videos(sort="growth_rate", growth_days=1, page_size=20)
|
||||
order = [item["aid"] for item in ranked["items"]]
|
||||
assert ranked["growth_rate_prior"] == 500
|
||||
assert order.index(302) < order.index(301)
|
||||
small = next(item for item in ranked["items"] if item["aid"] == 301)
|
||||
assert abs(small["growth"]["rate"] - 432 / 80) < 1e-12
|
||||
assert small["growth"]["delta"] == 432
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
def test_system_tasks_sorted_by_started_at_desc():
|
||||
"""最近任务按 started_at 倒序;缺开始时间的排末尾(含进行中无 finished)。"""
|
||||
import json
|
||||
@@ -835,6 +877,7 @@ def main_test():
|
||||
test_snapshot_recent_next_run_follows_completion()
|
||||
test_overview_adjacent_previous_with_seed_and_dual_fields()
|
||||
test_same_day_growth_baselines()
|
||||
test_growth_rate_sort_uses_prior()
|
||||
print("OK: Web 查询层总览/榜单/详情/UP/时长效率/粉丝/候选/系统/访问统计测试全部通过")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user