新功能:个性化推荐算法
This commit is contained in:
275
spec_kit/Personalized Reco/modules/content-repository/plan.md
Normal file
275
spec_kit/Personalized Reco/modules/content-repository/plan.md
Normal file
@@ -0,0 +1,275 @@
|
||||
# Content Repository(候选查询与数据访问层)|Plan
|
||||
|
||||
> 对应规范:`spec_kit/Personalized Reco/modules/content-repository/spec.md`
|
||||
>
|
||||
> 依赖对齐:
|
||||
>
|
||||
> - DB 设计:`spec_kit/Personalized Reco/modules/db-design/plan.md`
|
||||
> - 回退梯度与场景口径:`spec_kit/Personalized Reco/spec.md`(Fallback Ladder + 场景默认参数)
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标与交付物
|
||||
|
||||
### 1.1 目标
|
||||
|
||||
- 为推荐引擎提供**与 ORM/SQL 解耦**的数据访问接口:候选召回与按 ID 批量获取。
|
||||
- 将 DB 内部存储形态(JSON、关联表、NULL 语义等)统一“规范化”为上层稳定的 `ContentProfile` 结构。
|
||||
- 在候选不足时支持按 `fallback_level (L0~L3)` 进行**可控降级**(降个性化/回退通用池/安全池),并且不产生 N+1 查询。
|
||||
|
||||
### 1.2 交付物
|
||||
|
||||
- `modules/content-repository/plan.md`:本技术计划(本文件)。
|
||||
- 代码实现(后续 tasks 阶段落地)建议位置:
|
||||
- `server/app/features/personalized_reco/content_repository/`(或等价目录)
|
||||
- 需要包含:
|
||||
- 抽象接口 `ContentRepository`(Protocol 或 ABC)
|
||||
- SQLAlchemy 实现 `SqlAlchemyContentRepository`
|
||||
- `ContentProfile`(DTO/数据结构)与规范化工具函数
|
||||
- 单元测试与最小集成测试(后续 tasks 阶段落地):
|
||||
- risk_flags 映射与去重
|
||||
- suitability 缺失兜底
|
||||
- personalization_power 映射
|
||||
- 查询不出现按 `content_id` 循环查 flags(避免 N+1)
|
||||
|
||||
---
|
||||
|
||||
## 2. 关键技术决策(V1)
|
||||
|
||||
### 2.1 存储形态(来自 DB Design 的已对齐结论)
|
||||
|
||||
- `content_id`:MySQL 自增主键(`int`)。
|
||||
- `context_suitability_json / need_suitability_json`:JSON 存储。
|
||||
- `risk_flags`:关联表 `content_risk_flags(content_id, flag)`(同一 content 下 `uniq_content_flag` 去重)。
|
||||
- `emotion_score`:`NULL` 表示 general。
|
||||
- `review_confidence`:DB 可为 `NULL`;读取层输出时按 `0.7` 兜底(对齐规则口径)。
|
||||
- `personalization_power`:DB 推荐存 `0/5/10`;读取层输出稳定为 `0.0/0.5/1.0`。
|
||||
|
||||
### 2.2 职责边界(避免耦合)
|
||||
|
||||
- `Content Repository` **不负责** Hard Filter/Soft Scoring/Rerank/Freqcap(这些由引擎编排与打分子模块完成)。
|
||||
- `Content Repository` **负责**:
|
||||
- 按 `fallback_level` 对候选池做“降级约束”(例如限制 `personalization_power`、回退通用池/安全池)
|
||||
- 输出稳定结构(JSON 解析、默认值兜底、旧 flag 映射)
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口与数据结构(V1)
|
||||
|
||||
### 3.1 接口定义(与 spec 对齐)
|
||||
|
||||
- `fetch_candidates(scene, user_profile, fallback_level, limit, locale, exclude_content_ids=None) -> List[ContentProfile]`
|
||||
- `fetch_contents_by_ids(content_ids: List[int], locale) -> List[ContentProfile]`
|
||||
|
||||
### 3.2 `ContentProfile`(输出契约的推荐形态)
|
||||
|
||||
稳定字段(必须输出):
|
||||
|
||||
- `content_id: int`
|
||||
- `text: str`
|
||||
- `stage: "general" | "expecting" | "parenting" | "unknown"`
|
||||
- `emotion_score: float | None`(`None` 表示 general)
|
||||
- `context_suitability: Dict[str, float]`
|
||||
- `need_suitability: Dict[str, float]`
|
||||
- `personalization_power: float`(`0/0.5/1`)
|
||||
- `risk_flags: List[str]`
|
||||
|
||||
可选字段(尽量输出):
|
||||
|
||||
- `author_id: str | None`
|
||||
- `template_id: str | None`
|
||||
- `review_confidence: float`(缺失/NULL 按 `0.7` 输出)
|
||||
|
||||
---
|
||||
|
||||
## 4. 读取层规范化(Normalization)
|
||||
|
||||
### 4.1 text 选文案与多语言策略(不允许回退)
|
||||
|
||||
数据来源(当前 DB/ORM 约定):
|
||||
|
||||
- `contents.text_en`:英文
|
||||
- `contents.text_tc`:繁体中文
|
||||
|
||||
规则(**不允许语言回退**):
|
||||
|
||||
- `locale=en*`:仅允许返回存在 `text_en` 的内容;输出 `text = text_en`
|
||||
- `locale=tc/zh-TW/zh-HK`:仅允许返回存在 `text_tc` 的内容;输出 `text = text_tc`
|
||||
|
||||
若内容缺少目标语言文本(例如 `locale=en*` 但 `text_en` 为空):该内容视为不可用,必须在候选/按 ID 获取时过滤掉。
|
||||
|
||||
### 4.1 suitability JSON 解析与缺失兜底
|
||||
|
||||
固定 key 集合(对齐 DB Plan 的最小入库契约):
|
||||
|
||||
- `context_suitability`:`family/work/relationship/friends/health`
|
||||
- `need_suitability`:`emotional_support/parenting_pressure/self_worth/anxiety_relief/rest_balance`
|
||||
|
||||
规则:
|
||||
|
||||
- 若 DB 字段缺失/为 `NULL`/解析失败:**补齐为全 0.5**(以上所有 key 均为 `0.5`)。
|
||||
- 若 DB JSON 存在但缺少部分 key:对缺少 key 补 `0.5`,其余按原值。
|
||||
- 值域约束:期望值为 `0/0.5/1`;若出现其他值(例如字符串、越界浮点),按 `0.5` 兜底并记录告警日志(V1 可先打 debug,后续接入可观测模块)。
|
||||
|
||||
### 4.2 `review_confidence` 兜底
|
||||
|
||||
- DB `review_confidence` 为 `NULL` 或缺失:输出 `0.7`。
|
||||
|
||||
### 4.3 `personalization_power` 映射
|
||||
|
||||
若 DB 存 `0/5/10`:
|
||||
|
||||
- `0 -> 0.0`
|
||||
- `5 -> 0.5`
|
||||
- `10 -> 1.0`
|
||||
|
||||
若读到其他值:按 `0.0` 兜底并记录告警日志。
|
||||
|
||||
### 4.4 risk_flags 旧→新映射与输出约束
|
||||
|
||||
映射表(对齐 spec):
|
||||
|
||||
- `block_stage_unknown` → `unsafe_for_stage_unknown`
|
||||
- `block_stage_parenting` → `unsafe_for_stage_parenting`
|
||||
- `block_emotion_low` → `unsafe_for_emotion_low`
|
||||
- `block_health_sensitive` → `block_health_medical`(V1 保守硬拦截)
|
||||
|
||||
输出约束:
|
||||
|
||||
- 输出 `risk_flags` 必须去重。
|
||||
- 输出不得包含旧命名。
|
||||
- 输出建议稳定排序(便于测试与可观测):按字典序排序或按严重等级排序(V1 可先字典序)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 查询策略(V1)
|
||||
|
||||
> 原则:DB 层先做“粗过滤”,应用层再做“精过滤/打分”。避免在 V1 过早依赖 JSON 路径查询索引。
|
||||
|
||||
### 5.1 `fetch_contents_by_ids`(按 ID 批量获取)
|
||||
|
||||
目标:
|
||||
|
||||
- 输入任意 `content_id` 列表,返回无重复的 `ContentProfile` 列表。
|
||||
- 避免 N+1:不得按 `content_id` 循环查 `content_risk_flags`。
|
||||
- 返回顺序:必须与输入 `content_ids` 一致(对“缺记录/缺语言文本”的 id 采取跳过策略,见下)。
|
||||
|
||||
缺记录/缺语言文本的处理(V1 约定):
|
||||
|
||||
- 若某个 `content_id` 在 DB 中不存在,或按 `locale` 规则无法产出 `text`:该 id 在返回列表中**跳过**(不返回占位对象)。
|
||||
|
||||
推荐实现形态(两段式,避免 JOIN 导致重复行):
|
||||
|
||||
1. **批量拉主体与画像**(`contents` JOIN `content_profiles`),限制 `content_id IN (...)`。
|
||||
2. **批量拉 risk_flags**:`SELECT content_id, flag FROM content_risk_flags WHERE content_id IN (...)`,在应用层按 `content_id` 聚合为集合,再做旧→新映射与去重。
|
||||
|
||||
备注:
|
||||
|
||||
- 由于 `content_risk_flags` 是 1:N,直接三表 JOIN 容易导致行膨胀;两段式更便于组装与去重。
|
||||
|
||||
### 5.2 `fetch_candidates`(候选召回,支持 L0~L3)
|
||||
|
||||
输入:
|
||||
|
||||
- `scene: feed | push | widget`
|
||||
- `user_profile`(允许字段缺失)
|
||||
- `fallback_level: 0|1|2|3`
|
||||
- `limit`
|
||||
- `exclude_content_ids`(可选)
|
||||
|
||||
#### 5.2.1 fallback_level 约束(对齐大规范 Fallback Ladder)
|
||||
|
||||
从 `spec_kit/Personalized Reco/spec.md` 对齐:
|
||||
|
||||
- **L0**:正常召回配比(不在读取层实现复杂配比,读取层只保证候选池足够大且不过度放宽)
|
||||
- **L1**:放宽匹配 + 降个性化:限制 `personalization_power ≤ 0.5`
|
||||
- **L2**:回退通用池 + 进一步降个性化:限制 `personalization_power = 0`,且优先 `stage=general`
|
||||
- **L3**:兜底安全池:限制 `is_safe_pool = true`(安全池字段来自 DB Design)
|
||||
|
||||
缺失字段的最小处理(对齐大规范 Candidate Generation 口径):
|
||||
|
||||
- 若 `user_profile` 缺失明显(need/context/emotion 任一缺失):读取层按**至少 L1** 的约束执行(即使入参 fallback_level=0)。
|
||||
|
||||
#### 5.2.2 stage 粗过滤策略(V1)
|
||||
|
||||
读取层可做的最小粗过滤(不引入复杂业务判断):
|
||||
|
||||
- **L2/L3**:只取 `stage=general`(L3 额外 `is_safe_pool=true`)。
|
||||
- **L0/L1**:
|
||||
- 优先取 `stage=用户匹配阶段` + `stage=general`
|
||||
- 若无法从 `user_profile` 明确阶段,则仅取 `stage=general`(避免误推)
|
||||
|
||||
> 说明:更细粒度的阶段/跨维度规则(例如 unknown+parenting_pressure 的禁推)由 Hard Filter 子模块实现;读取层仅做粗过滤以减少扫描与传输。
|
||||
|
||||
#### 5.2.3 查询形态(避免 JOIN 行膨胀 + 保证 limit)
|
||||
|
||||
推荐采用“两段式候选召回”:
|
||||
|
||||
1. **先只查候选 ID 列表**(`contents` JOIN `content_profiles`),应用粗过滤(stage / personalization_power / is_safe_pool / exclude_content_ids),并增加 **locale 文本存在性过滤**(不允许语言回退),再用 `LIMIT limit * multiplier` 拉一批候选 ID(`multiplier` 例如 3~5,避免后续去重/过滤后不足)。
|
||||
2. **再用 `fetch_contents_by_ids` 批量补全字段**(主体+画像+risk_flags),最终在应用层去重并截断到 `limit`。
|
||||
|
||||
排序(V1):
|
||||
|
||||
- 若没有更明确的排序字段:使用 `updated_at DESC` 或随机抽样(需谨慎,MySQL `ORDER BY RAND()` 在大表会慢)。
|
||||
- 推荐:V1 先用 `content_profiles.updated_at DESC` 或 `contents.created_at DESC`,后续由打分模块决定最终排序。
|
||||
|
||||
---
|
||||
|
||||
## 6. 性能与可观测(V1)
|
||||
|
||||
### 6.1 性能约束
|
||||
|
||||
- 单次调用不得出现按 `content_id` 循环查库(避免 N+1)。
|
||||
- `fetch_candidates` 必须在 DB 层支持 `limit`,并尽量通过粗过滤减少扫描。
|
||||
|
||||
### 6.2 建议打点/日志(为 observability 子模块预留)
|
||||
|
||||
在 Repository 层建议输出 debug 级日志(或埋点字段,供上层汇总):
|
||||
|
||||
- `scene`
|
||||
- `fallback_level`(入参)与 `effective_fallback_level`(考虑缺失字段自动至少 L1 后的实际约束级别)
|
||||
- `limit`、`exclude_content_ids_count`
|
||||
- `candidate_ids_size_raw`(第 1 段查到的候选 ID 数)
|
||||
- `candidate_size_returned`(最终返回数量)
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试计划(V1)
|
||||
|
||||
### 7.1 单元测试(纯函数)
|
||||
|
||||
- risk_flags 映射:
|
||||
- 输入包含旧 flag,输出只包含新命名
|
||||
- 去重与稳定排序
|
||||
- suitability 兜底:
|
||||
- DB 字段缺失/NULL/解析失败 → 全 0.5
|
||||
- 部分 key 缺失 → 补齐 0.5
|
||||
- personalization_power 映射:
|
||||
- 0/5/10 → 0.0/0.5/1.0
|
||||
- 异常值 → 0.0 兜底
|
||||
- review_confidence:
|
||||
- NULL/缺失 → 0.7
|
||||
|
||||
### 7.2 最小集成测试(含数据库)
|
||||
|
||||
- `fetch_contents_by_ids`:
|
||||
- 输入多个 id 返回无重复
|
||||
- flags 聚合正确(同一 content 多条 flag 行能聚合成 list)
|
||||
- 查询次数断言(避免 N+1):
|
||||
- `fetch_contents_by_ids`:固定 2 次查询(主体+画像一次,flags 一次)
|
||||
- `fetch_candidates`:固定 3 次查询(候选 id 一次 + `fetch_contents_by_ids` 两次),或实现允许的常数级次数
|
||||
|
||||
---
|
||||
|
||||
## 8. 风险与后续演进
|
||||
|
||||
### 8.1 已知风险
|
||||
|
||||
- V1 不做 JSON 路径索引:候选量变大后,粗过滤不足可能导致候选池拉取过多、应用层过滤成本上升。
|
||||
- `ORDER BY RAND()` 的性能风险:候选大表下不可用,需要替代策略(时间窗口抽样/预生成候选池)。
|
||||
|
||||
### 8.2 V1.1 优化方向(与 DB Plan 对齐)
|
||||
|
||||
- 为常用 need/context key 增加生成列/函数索引(从 JSON_EXTRACT 提取到 TINYINT)以加速召回。
|
||||
- 为强规则风险(如 `block_health_medical`)增加派生布尔列或缓存表,减少 JOIN 成本。
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
- `scene`: `feed | push | widget`
|
||||
- `user_profile`: 客户端问卷画像(V1.2;字段允许缺失)
|
||||
- `locale`:客户端语言(由请求携带并透传至推荐模块;**当前仅支持 EN/TC**,例如 `en` / `en-US` / `tc` / `zh-TW` / `zh-HK`)
|
||||
- `fallback_level`: `0|1|2|3`
|
||||
- `limit`: 候选条数上限(由引擎配置)
|
||||
- (可选)排除集合:`exclude_content_ids`(用于 DB 层先排一部分,减少传输;类型为 `List[int]`,与 MySQL 自增 `content_id` 对齐)
|
||||
@@ -29,7 +30,9 @@
|
||||
|
||||
- `List[ContentProfile]`(稳定字段契约):
|
||||
- `content_id`:`int`(MySQL 自增主键)
|
||||
- `text`:文案文本
|
||||
- `text`:按 `locale` 输出的文案文本(**不允许语言回退**)
|
||||
- `locale=en*`:必须从 `contents.text_en` 产出;若该 content 无 `text_en`,则该 content 不可返回(在候选/按 ID 获取时过滤)
|
||||
- `locale=tc/zh-TW/zh-HK`:必须从 `contents.text_tc` 产出;若无 `text_tc`,则该 content 不可返回
|
||||
- `stage`:`general | expecting | parenting | unknown`
|
||||
- `emotion_score`:`float | None`(约定:`None` 表示 general)
|
||||
- `context_suitability`:`Dict[str, float]`(见 4.1 的 key 集合;值为 `0/0.5/1`)
|
||||
|
||||
198
spec_kit/Personalized Reco/modules/content-repository/tasks.md
Normal file
198
spec_kit/Personalized Reco/modules/content-repository/tasks.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Content Repository(候选查询与数据访问层)|Tasks
|
||||
|
||||
> 对应计划:`spec_kit/Personalized Reco/modules/content-repository/plan.md`
|
||||
>
|
||||
> 本清单已对齐确认点:
|
||||
>
|
||||
> - `text` 按客户端 **locale** 输出(请求携带)
|
||||
> - **不允许语言回退**(缺少目标语言文本的内容直接过滤,不返回)
|
||||
> - 需要做 **MySQL(dev)DB 集成测试**
|
||||
> - 代码与其他推荐子模块放同一目录:`server/app/features/personalized_reco/`
|
||||
> - `fetch_contents_by_ids` 返回顺序必须与输入 `content_ids` **一致**
|
||||
>
|
||||
> 执行说明:
|
||||
>
|
||||
> - 本次已在 dev MySQL 环境跑通 `pytest`,且测试不做破坏性操作:
|
||||
> - 不清表(不执行 DELETE/TRUNCATE)
|
||||
> - 每个用例使用事务并在结束时 rollback
|
||||
> - 默认不执行 Alembic 迁移(如需自动迁移需显式设置 `ALLOW_SCHEMA_MIGRATION=1`)
|
||||
|
||||
---
|
||||
|
||||
## 0. 任务标记规则
|
||||
|
||||
- 用勾选框标记执行状态:
|
||||
- `[ ]` 未开始
|
||||
- `[x]` 已完成
|
||||
- 每个任务都要求可独立验收(有明确产出/可运行的检查方式)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 文档对齐(先把口径写死,避免实现漂移)
|
||||
|
||||
- [x] 1.1 更新 `modules/content-repository/spec.md`,加入 locale 相关契约
|
||||
- **变更点**:
|
||||
- 在输入中新增 `locale`(例如 `en`/`en-US`/`tc`/`zh-TW`/`zh-HK`),声明由客户端请求携带并透传至 repository
|
||||
- 在输出字段 `text` 补充语言选择规则:
|
||||
- `locale=en*`:必须从 `text_en` 产出;若缺失则该内容不可返回(过滤)
|
||||
- `locale=zh-TW|zh-HK`:必须从 `text_tc` 产出;若缺失则该内容不可返回(过滤)
|
||||
- 说明:当前仅支持 EN/TC(不做繁转简);若未来新增 `zh-CN` 再单独设计转换策略
|
||||
- 明确:`fetch_contents_by_ids` **返回顺序与入参一致**
|
||||
- **验收**:`spec.md` 中输入/输出与接口签名不再缺少 locale,且 `text` 的来源与“不允许语言回退”规则清晰。
|
||||
|
||||
- [x] 1.2 更新 `modules/content-repository/plan.md`,补齐 locale 选文与“繁转简”技术方案
|
||||
- **变更点**:
|
||||
- 在“读取层规范化(Normalization)”新增 `text` 规范化章节:语言选择 + 简中转换
|
||||
- 说明:当前仅支持 EN/TC(不做繁转简);若未来新增 `zh-CN` 再单独设计转换策略
|
||||
- 明确策略:**不允许语言回退**;缺少目标语言文本的内容视为不可用,必须在候选/按 ID 获取时过滤掉
|
||||
- **验收**:`plan.md` 有明确依赖与落地策略(含依赖包/转换时机/兜底策略),不留二义性。
|
||||
|
||||
---
|
||||
|
||||
## 2. 目录与骨架(与推荐子模块同级)
|
||||
|
||||
- [x] 2.1 新建目录 `server/app/features/personalized_reco/content_repository/`
|
||||
- **包含**:
|
||||
- `__init__.py`
|
||||
- `types.py`(DTO:`ContentProfile`、locale 类型等)
|
||||
- `interface.py`(`ContentRepository` Protocol/ABC)
|
||||
- `normalization.py`(解析与兜底:suitability、risk_flags、power、text)
|
||||
- `sqlalchemy_repo.py`(SQLAlchemy 实现)
|
||||
- **验收**:可被 `app.features.personalized_reco.content_repository.*` 正常 import。
|
||||
|
||||
- [ ] 2.2 依赖补齐(如采用 OpenCC)
|
||||
- **说明**:当前仅支持 EN/TC,此任务可跳过;若未来新增 `zh-CN` 并需要繁转简,再引入 OpenCC。
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据结构与接口(面向引擎注入)
|
||||
|
||||
- [x] 3.1 定义 `ContentProfile` DTO(稳定字段契约)
|
||||
- **字段**:对齐 `modules/content-repository/spec.md`,并补齐 `review_confidence` 输出兜底为 `0.7`
|
||||
- **注意**:`text` 为最终对外输出文本(已按 locale 选择/转换)
|
||||
- **验收**:DTO 字段齐全;类型清晰;不暴露 ORM 模型。
|
||||
|
||||
- [x] 3.2 定义 `ContentRepository` 接口(含 locale)
|
||||
- **建议签名**(示例,最终以 spec 为准):
|
||||
- `fetch_candidates(scene, user_profile, fallback_level, limit, locale, exclude_content_ids=None) -> List[ContentProfile]`
|
||||
- `fetch_contents_by_ids(content_ids, locale) -> List[ContentProfile]`
|
||||
- **验收**:推荐引擎可以仅依赖该接口,不依赖 SQLAlchemy/FastAPI Depends。
|
||||
|
||||
---
|
||||
|
||||
## 4. 规范化工具函数(可单测)
|
||||
|
||||
- [x] 4.1 suitability 解析与兜底
|
||||
- **规则**:
|
||||
- 缺失/NULL/解析失败 → 全 0.5(固定 key 集合)
|
||||
- 部分 key 缺失 → 对缺失 key 补 0.5
|
||||
- 非法值 → 兜底 0.5
|
||||
- **验收**:单元测试覆盖缺失/部分缺失/非法值。
|
||||
|
||||
- [x] 4.2 risk_flags 映射、去重与排序
|
||||
- **规则**:旧→新映射对齐 spec;去重;稳定排序(例如字典序)
|
||||
- **验收**:单元测试断言输出不含旧命名且顺序稳定。
|
||||
|
||||
- [x] 4.3 personalization_power 映射
|
||||
- **规则**:`0/5/10 -> 0.0/0.5/1.0`;非法值 -> 0.0
|
||||
- **验收**:单元测试覆盖正常/异常值。
|
||||
|
||||
- [x] 4.4 text 选择与简中转换
|
||||
- **输入**:`text_en`、`text_tc`、`locale`
|
||||
- **规则**:按 1.1/1.2 写死的策略执行(当前仅支持 EN/TC,不做繁转简)
|
||||
- **验收**:单元测试覆盖:
|
||||
- `en` 取英文
|
||||
- `zh-TW` 取繁中
|
||||
- 缺失目标语言文本时的行为:返回“不可用”(例如返回空字符串 + 上层过滤,或直接返回 `None` 由调用方过滤;实现中必须一致)
|
||||
|
||||
---
|
||||
|
||||
## 5. SQLAlchemy 实现(无 N+1、顺序可控)
|
||||
|
||||
> 说明:当前 DB 模型为:
|
||||
>
|
||||
> - `contents`:`text_en` / `text_tc` / `author_id` / `template_id`
|
||||
> - `content_profiles`:JSON、power、stage、is_safe_pool、review_confidence
|
||||
> - `content_risk_flags`:关联表(1:N)
|
||||
|
||||
- [x] 5.1 实现 `fetch_contents_by_ids(content_ids, locale)`
|
||||
- **实现要点**:
|
||||
- 输入去重,但输出必须按原始输入顺序重排(并忽略不存在的 id 或明确行为:不存在则跳过)
|
||||
- 两段式查询避免行膨胀:
|
||||
1) `contents` JOIN `content_profiles` 批量取主体与画像
|
||||
2) `content_risk_flags` 批量取 flags,再按 `content_id` 聚合
|
||||
- 组装 DTO 时执行 normalization(含 text locale 规则)
|
||||
- **验收**:
|
||||
- 返回顺序与输入一致
|
||||
- 缺失目标语言文本的 content_id 不返回(跳过,不做语言回退)
|
||||
- 不产生按 id 循环查 flags 的查询(查询次数为常数级)
|
||||
|
||||
- [x] 5.2 实现 `fetch_candidates(scene, user_profile, fallback_level, limit, locale, exclude_content_ids)`
|
||||
- **实现要点**:
|
||||
- 计算 `effective_fallback_level`:
|
||||
- 若画像 need/context/emotion 任一缺失,则 `effective_fallback_level = max(fallback_level, 1)`
|
||||
- DB 粗过滤对齐 plan:
|
||||
- L1:`personalization_power <= 5`
|
||||
- L2:`personalization_power = 0` 且 `stage = general`
|
||||
- L3:`is_safe_pool = true` 且 `stage = general` 且 `personalization_power = 0`(如需更严格可在此明确)
|
||||
- 排序(V1):按 `content_profiles.updated_at DESC` 或 `contents.updated_at DESC`(择一写死并记录)
|
||||
- 两段式候选:
|
||||
1) 先查候选 id(`LIMIT limit * multiplier`)
|
||||
2) 调用 `fetch_contents_by_ids` 补全字段
|
||||
- locale 文本存在性过滤:
|
||||
- `locale=en*`:`contents.text_en IS NOT NULL`
|
||||
- `locale=zh-*`:`contents.text_tc IS NOT NULL`
|
||||
- 输出顺序:
|
||||
- 返回顺序按候选 id 列表顺序(用于后续引擎打分/重排);最终截断至 `limit`
|
||||
- **验收**:
|
||||
- 在不同 `effective_fallback_level` 下能返回候选
|
||||
- 不返回缺少目标语言文本的内容(不做语言回退)
|
||||
- 查询次数为常数级(不随 `limit` 线性增长)
|
||||
|
||||
---
|
||||
|
||||
## 6. DB 集成测试(dev MySQL)
|
||||
|
||||
- [x] 6.1 建立测试目录与 pytest 配置
|
||||
- **目标**:在 `server/` 内新增 `tests/`(或 `app/**/__tests__/`,但建议统一为 `server/tests/`)
|
||||
- **内容**:
|
||||
- `server/tests/conftest.py`:提供 AsyncEngine/AsyncSession、清库策略、query count 统计工具
|
||||
- 测试运行约定:通过 `DATABASE_URL` 指向 dev 测试库(建议单独库名,例如 `mindfulness_dev_test`)
|
||||
- **验收**:`pytest` 可在 `server/` 下运行并发现测试。
|
||||
|
||||
- [x] 6.2 测试库 schema 初始化(用 Alembic)
|
||||
- **策略**(二选一写死):
|
||||
- A:测试启动时 `alembic upgrade head`(确保 schema 最新)
|
||||
- B:在 CI/本地提前准备库,仅在测试中清表
|
||||
- **验收**:测试运行前 schema 可用,且不会污染开发主库数据(推荐使用独立 test 库)。
|
||||
|
||||
- [x] 6.3 集成测试用例:`fetch_contents_by_ids`
|
||||
- **准备数据**:插入最小内容 2~3 条(覆盖 text_en/text_tc 缺失组合)、profiles、flags(含旧 flag)
|
||||
- **断言**:
|
||||
- 返回顺序与输入一致
|
||||
- `en` 不返回 `text_en` 缺失的内容(不回退 `text_tc`)
|
||||
- risk_flags 映射后不含旧命名
|
||||
- `review_confidence` NULL → 0.7
|
||||
- **验收**:测试稳定通过。
|
||||
|
||||
- [x] 6.4 集成测试用例:`fetch_candidates`
|
||||
- **准备数据**:覆盖 `personalization_power` 0/5/10、`is_safe_pool` true/false、不同 stage
|
||||
- **断言**:
|
||||
- L1/L2/L3 粗过滤生效
|
||||
- `exclude_content_ids` 生效
|
||||
- 查询次数为常数级(用 before_cursor_execute 计数)
|
||||
- **验收**:测试稳定通过。
|
||||
|
||||
---
|
||||
|
||||
## 7. 最终自检清单(合入前)
|
||||
|
||||
- [x] 7.1 文档一致性检查
|
||||
- `spec.md` / `plan.md` / 实现接口签名三者一致(尤其是 `locale` 与 `text` 输出规则)
|
||||
|
||||
- [x] 7.2 性能检查(最小)
|
||||
- `fetch_contents_by_ids` / `fetch_candidates` 查询次数断言通过(无 N+1)
|
||||
|
||||
- [x] 7.3 回归检查
|
||||
- 不影响现有 `user_profile_scoring` 模块与迁移脚本(仅新增模块与测试)
|
||||
|
||||
Reference in New Issue
Block a user