资讯动态

48 小时窗口、24 小时半衰期:AIHOT 热度算法为什么比人工榜单更讲武德

发布时间:2026/10/16 13:13:09 来源:尧图企业网站定制
48 小时窗口、24 小时半衰期AIHOT 热度算法为什么比人工榜单更讲武德【免费下载链接】AIHOT一个自己找热点、自己写日报的网站框架。把信源和精选标准换成你的它就是你的行业热点站。项目地址: https://gitcode.com/gh_mirrors/ai/AIHOT热点榜是内容产品里最容易翻车的地方。人工编辑排榜难免带口味与立场用点击量排榜又被标题党和水军牵着走。AIHOT 的做法是把热度计算交给一套完全确定性的数学规则不靠模型打分不靠编辑拍脑袋只用过去 48 小时、24 小时半衰期、独立来源去重这三个参数就筛出了真正有很多人在说的事。这套逻辑没有一处魔法全部落在仓库的 packages/backend/src/events/hot.ts 一个文件里规则版本号甚至写死在代码中heat-v1-48h-halflife24h。一、热度公式拆解三个参数撑起一套排序先看核心常量它们就定义在 hot.ts 的第 57–60 行export const HOT_RULE_VERSION heat-v1-48h-halflife24h; const WINDOW_HOURS 48; const HALF_LIFE_HOURS 24; const MIN_PARTICIPANTS 2;规则命名里自带参数48 小时时间窗口、24 小时半衰期、最少 2 个参与者。理解这三条整个榜单的脾气就摸清了。第一热度按事件算不按文章算。这是整套设计的地基。AIHOT 先把不同来源说同一件事的报道聚成一个事件聚簇逻辑见 README.md 的聚簇与热点一节然后热度只在这个事件维度上累计。官网发一篇、媒体转十篇、X 上吵一天读者看到的是同一个事件条目热度也是这个事件的热度。第二48 小时窗口决定谁有资格说话。事件证据的观测时间必须落在now - 48h到now之间才会进入统计。第 140–141 行用 SQL 生成两个窗口起点——当前时刻的窗口起点以及 6 小时前的窗口起点nowFrom at::timestamptz - make_interval(hours 48) prevFrom prev::timestamptz - make_interval(hours 48) -- prev at - 6h48 小时意味着榜单只反映最近两天大家在讨论什么一周前再轰动的新闻过了窗口就自动下榜不需要编辑手动换血。第三24 小时半衰期决定发言的权重怎么衰减。这是整个公式最漂亮的部分。一个参与者对热度的贡献不是固定的 1 分而是按他最后一次发言距离现在的时间做指数衰减decayNow power(0.5, extract(epoch FROM (at - last_at)) / 3600.0 / 24)翻译成白话每过 24 小时这条发言的贡献减半发言越新鲜贡献越大。公式里用的是power(0.5, ...)这是 SQL 标准函数没有浮点累积误差也没有任何近似。同样的衰减逻辑在 heatSeries事件页的逐小时热度曲线里也用 JS 复刻了一遍heat 0.5 ** ((hour - last) / 3600_000 / HALF_LIFE_HOURS);半衰期和窗口的配合很妙。一个 48 小时窗口内、恰好卡在窗口边缘发言的参与者其贡献经 24 小时半衰期衰减后只剩 1/4而在 6 小时前刚发言的参与者贡献几乎打满。于是榜单天然偏向刚刚还在发生的讨论而不是48 小时前热闹过一下的旧闻。最近 6 小时新增参与者的数量还单独被统计为recent6h为下面的飙升判断提供弹药。第四最小参与者数把关。事件入围榜单至少需要 2 个独立参与者、且其中至少 1 个是报道型editorial来源见第 185 行const rows (await heatRows(at, behind)).filter( (r) Number(r.participants) MIN_PARTICIPANTS Number(r.editorial_participants) 1 );一条孤零零的自媒体爆料、一个没有旁证的传闻连榜都上不了——这从规则层面就堵死了一家之言刷屏。热度计算完后展示层还要做一个10 倍缩放heatIndex把内部热度四舍五入后 ×10 展示Math.round(heat * 100) / 10榜单顶部限 10 条if (entries.length 10) break;。所以你在页面上看到的热度值 ×10是展示层约定底层权重逻辑完全不受影响。这一整套参数最终和条目一起以 JSON 形式落库hot_rankings表evidence字段里还存着windowHours、halfLifeHours、minParticipants供审计——每次榜单是怎么算出来的都可追溯。二、入围规则与趋势标签新 / 上升 / 飙升的判定全是数学榜单不只是一堆热度数字每个条目还挂着趋势标签。这些标签的判定同样不含任何模型推理全部是确定的阈值规则集中在 packages/backend/src/events/hot.ts 的第 217–238 行。先看和谁比的问题。趋势是跟 6 小时前比prev at - 6h。但直接拿当前热度减 6 小时前热度是不公平的——因为两次统计的 48 小时窗口有 6 小时的重叠差异而且有些信源的采集可能落后导致没看到最近的新证据。代码的应对是引入可比组comparable只有那些在 6 小时前窗口开始前就已存在、且采集没有落后的参与者才进入前后对比heat_obs/heat_prev_obs。第 220–222 行const [cur, prev] Number(r.uncomparable) 0 ? [heatIndex(Number(r.heat_obs)), heatIndex(Number(r.heat_prev_obs))] : [heat, prevAll]; const pct prev 0 ? (cur - prev) / prev : null;翻译过来如果存在采集落后或窗口开始后才加入的参与者就把它们从变化率计算里剔除只用两边都完整观测到的同一批人算百分比。这样涨了 30%说的是同一批信源口径下的真实变化而不是被新接入的信源数量虚抬上去的。然后是三档标签的判定逻辑第 223–229 行const isNew at.getTime() - firstAt.getTime() 6 * 3600 * 1000; const surge Number(r.recent6h) 3 Number(r.recent6h) / Number(r.participants) 0.5; const badges: HotEntry[badges] []; if (surge) badges.push(surge); if (isNew) badges.push(new); if (!surge pct ! null pct 0.15) badges.push(rising);新事件首次出现距现在不足 6 小时isNew。注意它用的是first_report_at而非当前时间窗口一个 5 小时前刚冒头的事件会立刻带上新标。飙升/爆最近 6 小时新加入的参与者 ≥ 3 个且占当前总参与者的比例 ≥ 50%surge。这个条件极其苛刻——既要绝对数量又要相对密度防止老事件被慢慢拱上去也误标成爆。发酵中/上升剔除爆之后变化率 pct 15% 才标上升rising。0.15 这个阈值意味着必须有实质性的讨论增长微涨不算。趋势方向trend另有 ±10% 的阈值判定第 237 行pct 0.1是上升、pct -0.1是下降、之间是持平无法比较时标unknown前端会诚实显示暂不比较而不是硬编一个数。前端把这些标签渲染成爆 / 新 / 发酵中三个徽章和 ↑/↓ 百分比定义在 apps/web/app/routes/hot.tsx 里const BADGES { surge: { label: 爆, tone: hot, hint: 讨论快速增加 }, new: { label: 新, tone: accent, hint: 首报 6 小时内 }, rising:{ label: 发酵中, tone: amber, hint: 讨论仍在增加 }, };每个徽章都带 tooltip 说明判定依据——读者能看懂审核能复核这是讲武德的另一个侧面规则全公开没有任何黑箱。三、对刷量与同源重复报道的天然防御力人工榜单最怕水军而 AIHOT 这套算法从数据结构上就对两种最常见的作弊手段免疫。防御一重复抓取、重复报道不加分——一个参与者每个窗口只算一次。热度统计的粒度是participant_key参与者键而不是文章条数。在 currentSignals 这个 SQL 视图里参与者身份的判定非常讲究CASE WHEN s.id ANY(COMMUNITY_FEEDS.dev) THEN dev:account: || lower(substring(a.url ...)) WHEN s.id ANY(COMMUNITY_FEEDS.hn) THEN hn:account: || substring(a.author ...) WHEN s.signal_group_id IS NOT NULL THEN group: || s.signal_group_id WHEN s.owner_entity_id IS NOT NULL THEN owner: || s.owner_entity_id ELSE source: || s.id END AS participant_key层层去重的含义是同一家媒体的官网、公众号、X 账号如果挂了同一个owner_entity_id它们算一个参与者——一家公司发十篇也只算一次一个信号组signal_group_id整体算一个参与者而在 dev.to、Hacker News 这类社区信源上每个发帖账号算一个独立参与者dev:account:*/hn:account:*——几十个不同的人发帖才真正叠加热度更狠的是聚合层面用GROUP BY story_id, participant_key把同一参与者在窗口内的最新一条作为代表max(observed_at)前面重复抓取的那几篇自动被吸收不会重复计权。这一设计在 tests/hot.test.ts 里有专门测试one companys channels are one participant; every community author is one——同一实体的多渠道测试、社区账号独立测试都直接写进了回归用例。还有一个反向用例值得注意归属修复后同一参与者的多条证据会合并成一条。测试里一个聚合器来源的文章被修正归属到官方 publisher 后参与者数从 2 降回 1因为同一个 publisher 只算一个参与者。防御二48 小时窗口天然冷却刷量。刷量要持续 48 小时不断有新账号发言成本陡增而 24 小时半衰期让昨天买的量今天只剩一半权重。更重要的是事件级聚合把同一件事的十篇水文压成一条刷子即便发一百篇也只算一个参与者的一次发言。防御三撤回和改归属实时生效。热度证据是按当前状态实时计算的不是快照缓存。被撤回的报道visibility withdrawn立即从统计中剔除信源角色从 editorial 改成 signal、或者被标记为 isolated不参与热度下一轮计算就自动消失。甚至管理员修正一篇文章的归属热度会立刻反映新归属。测试 withdrawn reports and changed roles are counted as they stand now 验证的正是这一点。这意味着舆论反转、假新闻撤回后热度会自己消下去不需要人工去删榜。防御四采集落后的信源不会造成假冷或假热。榜单计算时会检查每个信源的上次成功抓取时间sourceClocks落后超过三个抓取间隔的信源被标记为behind。这些信源的新证据可能缺失所以它们会被排除出变化率对比但不能排除出绝对热度。同理逐小时热度快照story_heat_hourly表里采集未完整的小时会被标记complete false前端图表宁可留缺口也不把缺失画成零——见 Sparkline.tsx 的注释Hours without a comparable snapshot break the curve rather than being drawn as zero。四、这套规则最终长什么样把上面的机制拼起来AIHOT 热点榜的完整链路是采集层持续抓取信源判重去重后入库归组层把同一件事的报道聚成一个事件向量 模型判断复核可换模型热度层用纯 SQL 计算每个事件在 48 小时窗口内的独立参与者热度按 24 小时半衰期衰减排序取前 10配上新 / 上升 / 飙升 / 持平 / 暂不比较的标签和 24 小时逐小时热度曲线HeatChart.tsx 展示事件页趋势榜单整体以hot_rankings快照落库公开读取层publication/hot.ts在展示前还会复核代表报道是否仍是有效证据防止归组变动后出现死链。值得一提的是这套热度数据不止喂给网页。公开 API v1 会输出每个条目的sourceCount、participantCount、sourceNames见 publication/stories.ts 的v1HotTopicsMCP 工具和 RSS 也走同一份公开读取层——机器看到的榜单和人类看到的完全一致且热度数值只对网站展示机器出口只给排名和来源数避免数值被滥用。从社区反响看AIHOT 凭借自动采集热点、编排行业日报的能力在开源社区快速积累热度GitHub 已获约 5400 Star并多次登上 GitHub 日榜。但真正让它在同类项目里立住的恰恰不是大模型的部分——大模型负责判断这件事值不值得看而有多少人在说这件事AIHOT 交给了 48 小时窗口、24 小时半衰期和独立参与者计数这套确定性算法。模型会幻觉编辑会疲惫但power(0.5, ...)不会。把情绪排除在热度之外也许正是它敢把全部规则开源的原因。【免费下载链接】AIHOT一个自己找热点、自己写日报的网站框架。把信源和精选标准换成你的它就是你的行业热点站。项目地址: https://gitcode.com/gh_mirrors/ai/AIHOT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑