# Rerank & Freqcap(重排 / 去重 / 频控)|Tasks > 对应计划:`spec_kit/Personalized Reco/modules/rerank-freqcap/plan.md` > > 本清单执行原则: > > - 本模块只做 **Dedup / Freqcap / Feed MMR 重排**,不做 Soft Scoring 与 Hard Filter。 > - V1 冷却窗口以“输入集合代表窗口内历史”为准(后续接入服务端历史再按天计算)。 --- ## 0. 任务标记规则 - 用勾选框标记执行状态: - `[ ]` 未开始 - `[x]` 已完成 - 每个任务都要求可独立验收(有明确产出/可运行的检查方式)。 --- ## 1. 文档对齐(先把口径写死,避免实现漂移) - [x] 1.1 校对 `modules/rerank-freqcap/spec.md` 与 `modules/rerank-freqcap/plan.md` 一致性 - **检查点**: - 输入:`scene/scored_candidates/history/k/config` 字段与命名一致 - 输出:`ranked_items` 与 `meta` 字段集合一致 - 去重键:`sentence_key=content_id`、`author_key`、`template_key` 口径一致 - Feed:MMR λ=0.7 与 Sim 规则一致 - **验收**:两份文档不存在冲突描述,且“V1 冷却窗口语义”写清楚(集合代表窗口内历史)。 - [x] 1.2 在 `plan.md` 中补充/固定“标签构造策略”与“候选截断策略”(若后续要改再更新) - **变更点**: - 明确 `tags` 的 V1 定义:`stage + need_argmax + context_argmax` - 明确 `top_n_for_mmr` 默认值与作用(性能兜底) - **验收**:实现时不会出现“标签到底取哪些 key”的二义性。 --- ## 2. 目录与骨架(与推荐子模块同级) - [x] 2.1 新建目录 `server/app/features/personalized_reco/rerank_freqcap/` - **包含**: - `__init__.py` - `types.py`(`ScoredCandidate`、`RerankConfig`、`RerankMeta`、`RerankResult`) - `defaults.py`(按 scene 的默认参数:λ、cooldown_*、top_n_for_mmr) - `utils.py`(ID 归一化、Jaccard、tag 构造等) - `rerank.py`(主入口 `rerank_and_freqcap`) - **验收**:可通过 `app.features.personalized_reco.rerank_freqcap.*` 正常 import。 --- ## 3. 类型与接口(稳定契约,便于 reco-engine 调用) - [x] 3.1 定义 `ScoredCandidate`(最小字段集合) - **必须字段**: - `content_id: int` - `final_score: float` - **建议字段**(用于多样性/频控): - `author_id: str | None` - `template_id: str | None` - `content_profile`(至少能取到 stage/need_suitability/context_suitability;缺失时需降级) - **验收**:能承载 MMR 相似度计算所需数据;缺失字段不会导致异常。 - [x] 3.2 定义 `RerankConfig`(可调参) - **字段**: - Feed:`mmr_lambda`(默认 0.7)、`top_n_for_mmr`(默认 200) - Push/Widget:`cooldown_sentence_days/cooldown_author_days/cooldown_template_days`(用于配置与可观测) - **验收**:能从 scene 推导默认 config(或由调用方传入覆盖)。 - [x] 3.3 定义 `RerankMeta` 与 `RerankResult` - **meta 必须字段**: - `candidate_pool_size_after_dedup` - `candidate_pool_size_after_freqcap` - `missing_history_fields` - **建议字段**: - `freqcap_filtered_counts`(sentence/author/template) - **验收**:字段集合固定;任何输入都能产出 meta(包括候选为空)。 --- ## 4. 核心算法实现(V1 最小集合) - [x] 4.1 实现历史 ID 归一化(避免 str/int 混用漏过滤) - **规则**: - `already_recommended_ids` / `touched_or_viewed_ids` 尽量转为 `int` 集合 - 转换失败的值忽略并记录 debug - **验收**:单测覆盖 `["1", 2, "bad"]` 等混合输入,过滤结果正确且稳定。 - [x] 4.2 实现 Dedup(必做) - **规则**:过滤 `content_id ∈ seen_ids` 的候选 - **产出**:`meta.candidate_pool_size_after_dedup` - **验收**:输出不包含历史出现过的 `content_id`。 - [x] 4.3 实现 Freqcap(Push/Widget 必做;Feed 可选) - **V1 策略**: - 句子维度(必做):同句硬过滤(使用 dedup 的 seen_ids 即可) - 作者/模板维度(增强项): - 若提供 `recent_author_ids`:命中则硬过滤;否则在 `missing_history_fields` 记录 `author` - 若提供 `recent_template_ids`:命中则硬过滤;否则在 `missing_history_fields` 记录 `template` - **产出**: - `candidate_pool_size_after_freqcap` - `freqcap_filtered_counts`(建议) - **验收**:在有/无 `recent_*_ids` 输入时行为一致且可解释。 - [x] 4.4 实现 Feed:tag 构造与相似度 `Sim` - **tag 规则(V1 写死)**: - `stage:` - `need:`(从 `need_suitability` 取最大值 key;为空则跳过) - `context:`(从 `context_suitability` 取最大值 key;为空则跳过) - **Jaccard**:`|A∩B|/|A∪B|`,空集合时返回 0 - **Sim 累加规则**: - 同 content_id → 1 - template_id 相同且非空 → +0.6 - author_id 相同且非空 → +0.3 - +0.1 * Jaccard(tags) - clamp 到 `[0,1]` - **验收**:单测覆盖缺失字段(无 author/template/tags)时仍能算出稳定 Sim。 - [x] 4.5 实现 Feed:MMR 选序列 - **规则**: - Top1:按 `final_score` 最大 - 后续:按 `MMR(c)=λ*Rel(c)-(1-λ)*maxSim` 选择 - `Rel=final_score` - **性能兜底**:先截断候选到 `top_n_for_mmr` 再做 MMR - **验收**: - Top1 恒等于最高分 - 候选足够时,序列不出现大量同作者/同模板紧邻重复(可用阈值断言) - [x] 4.6 实现 Push/Widget:最终 TopK - **规则**:dedup+freqcap 后按 `final_score` 降序取前 k 条 - **验收**:输出长度 ≤ k,且分数单调不增(允许相等)。 - [x] 4.7 实现主入口 `rerank_and_freqcap(...) -> RerankResult` - **规则**: - 三场景共用 dedup - Feed:MMR;Push/Widget:TopK - 必须输出 meta(即使 ranked_items 为空) - **验收**:任何输入(含空候选)不抛异常,并输出稳定结构。 --- ## 5. 单元测试(pytest,纯函数为主) - [x] 5.1 新建测试文件 `server/tests/test_rerank_freqcap.py` - **用例覆盖**: - dedup:历史集合过滤正确(含 str/int 混用) - freqcap:有/无 recent_author/template 的分支与 meta 缺失标记 - feed mmr:Top1=最高分;后续避免同作者/模板紧邻(构造数据断言) - push/widget:TopK 输出正确 - **验收**:`pytest -q tests/test_rerank_freqcap.py` 通过。 --- ## 6. 最终自检清单(合入前) - [x] 6.1 文档一致性检查 - **验收**:`spec.md` / `plan.md` / 实现接口签名三者一致(尤其 meta 字段与默认参数)。 - [x] 6.2 回归检查(不影响已实施模块) - **验收**:不修改 `content-repository` 与 `scoring` 的既有逻辑;仅新增 `rerank-freqcap` 模块与测试。 - [x] 6.3 全量测试通过 - **命令**(在 `server/`): - `pytest -q` - **验收**:所有用例通过。 - [x] 6.4 全部完成后更新大规范 `overview.md` - **变更点**: - 将 `modules/rerank-freqcap/` 标记为“已实施” - 增加一条变更记录(日期 + 交付物:plan/tasks/代码/测试) - **验收**:`spec_kit/Personalized Reco/overview.md` 中模块状态与交付记录准确。