fix:更新算法
This commit is contained in:
102
spec_kit/Personalized Reco/modules/content-repository/spec.md
Normal file
102
spec_kit/Personalized Reco/modules/content-repository/spec.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# 子模块:Content Repository(候选查询与数据访问层)|Spec
|
||||
|
||||
## 1. 目标描述
|
||||
|
||||
提供推荐算法可注入的、与 ORM/SQL 解耦的数据访问接口:
|
||||
|
||||
- 按场景与用户画像拉取候选内容画像(Cᵢ)。
|
||||
- 按 `content_id` 批量获取内容画像(去重/重排/补字段)。
|
||||
- 对 risk_flags、suitability JSON 等“存储形态”做统一解析与兼容(对上提供稳定结构)。
|
||||
|
||||
> 规则口径:risk_flags 命名与语义严格按 `句子文案打分规则`;若历史数据存在旧 flag,需在读取层做一次映射(避免语义漂移)。
|
||||
>
|
||||
> 存储形态对齐 `modules/db-design/plan.md`:读取层需要将 `contents` + `content_profiles` + `content_risk_flags` 组装为上层稳定的 `ContentProfile` 结构。
|
||||
|
||||
---
|
||||
|
||||
## 2. 输入 / 输出定义
|
||||
|
||||
### 2.1 输入
|
||||
|
||||
- `scene`: `feed | push | widget`
|
||||
- `user_profile`: 客户端问卷画像(V1.2;字段允许缺失)
|
||||
- `fallback_level`: `0|1|2|3`
|
||||
- `limit`: 候选条数上限(由引擎配置)
|
||||
- (可选)排除集合:`exclude_content_ids`(用于 DB 层先排一部分,减少传输;类型为 `List[int]`,与 MySQL 自增 `content_id` 对齐)
|
||||
- 数据库会话/连接(实现层使用 `AsyncSession` 注入)
|
||||
|
||||
### 2.2 输出
|
||||
|
||||
- `List[ContentProfile]`(稳定字段契约):
|
||||
- `content_id`:`int`(MySQL 自增主键)
|
||||
- `text`:文案文本
|
||||
- `stage`:`general | expecting | parenting | unknown`
|
||||
- `emotion_score`:`float | None`(约定:`None` 表示 general)
|
||||
- `context_suitability`:`Dict[str, float]`(见 4.1 的 key 集合;值为 `0/0.5/1`)
|
||||
- `need_suitability`:`Dict[str, float]`(见 4.1 的 key 集合;值为 `0/0.5/1`)
|
||||
- `personalization_power`:`float`(对上稳定口径为 `0/0.5/1`;若 DB 存 `0/5/10`,读取层需映射)
|
||||
- `risk_flags`:`List[str]`(已做旧→新映射、去重;命名只允许 `unsafe_for_* / block_* / soft_*`)
|
||||
- 可选:`author_id`、`template_id`、`review_confidence`
|
||||
|
||||
### 2.3 数据存储形态(对齐 DB 设计)
|
||||
|
||||
> 对齐:`spec_kit/Personalized Reco/modules/db-design/plan.md`
|
||||
|
||||
- `contents`:提供 `content_id`、`text`、(可选)`author_id`、`template_id`
|
||||
- `content_profiles`:提供 `stage`、`emotion_score`、`context_suitability_json`、`need_suitability_json`、`personalization_power`(推荐存 `0/5/10`)、(可选)`review_confidence`
|
||||
- `content_risk_flags`:通过关联表提供风险标记集合(同一 content 下按 `uniq_content_flag(content_id, flag)` 去重)
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口(建议)
|
||||
|
||||
推荐模块对该子模块只依赖抽象接口(Python Protocol/ABC 均可):
|
||||
|
||||
- `fetch_candidates(scene, user_profile, fallback_level, limit, exclude_content_ids=None) -> List[ContentProfile]`
|
||||
- `fetch_contents_by_ids(content_ids: List[int]) -> List[ContentProfile]`
|
||||
|
||||
---
|
||||
|
||||
## 4. 关键规则与实现约束
|
||||
|
||||
### 4.1 字段缺失与默认值
|
||||
|
||||
- 若 `review_confidence` 缺失:输出时默认按 `0.7`(对齐 `句子文案打分规则` V1.2 约定)。
|
||||
- `context_suitability/need_suitability` 若缺失:**读取层必须补齐为“全 0.5 的通用可推”结构**(稳定输出,避免上层分支判断)。
|
||||
- `context_suitability` 必须包含 5 个 key:`family/work/relationship/friends/health`
|
||||
- `need_suitability` 必须包含 5 个 key:`emotional_support/parenting_pressure/self_worth/anxiety_relief/rest_balance`
|
||||
- 补齐时上述 key 的默认值均为 `0.5`
|
||||
- `personalization_power`:若 DB 采用 `0/5/10` 存储,读取层必须映射为 `0.0/0.5/1.0` 对上输出。
|
||||
|
||||
### 4.2 risk_flags 兼容映射(若存在历史旧数据)
|
||||
|
||||
对齐 `句子文案打分规则` 的旧→新映射:
|
||||
|
||||
- `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`(读取层默认采用更保守的硬拦截映射;除非未来引入可判定的细分字段再放宽为 `soft_health_sensitive`)
|
||||
|
||||
输出约束:
|
||||
|
||||
- 输出的 flags 必须已去重,且不得包含任何旧命名。
|
||||
|
||||
### 4.3 性能约束
|
||||
|
||||
- 不得产生 N+1 查询:候选与字段必须一次或少量批量查询获取。
|
||||
- `fetch_candidates` 必须支持 limit,并在 DB 层尽量过滤(减少应用层扫描)。
|
||||
- `fetch_contents_by_ids` / `fetch_candidates` 推荐查询形态:
|
||||
- `contents` JOIN `content_profiles`,再 LEFT JOIN `content_risk_flags`(或先批量取 profiles,再批量取 flags 并在应用层聚合),避免按 `content_id` 循环查 flags。
|
||||
|
||||
---
|
||||
|
||||
## 5. 验收标准(可验证)
|
||||
|
||||
- `fetch_contents_by_ids`:
|
||||
- 输入任意 `content_id` 列表,返回包含完整字段的 `ContentProfile` 列表(无重复、可缺省字段按约定兜底)。
|
||||
- `fetch_candidates`:
|
||||
- 在不同 `scene` 与 `fallback_level` 下能返回候选(即便画像缺失也不报错)。
|
||||
- risk_flags 映射正确:输出的 flag 名称集合只包含新命名(`unsafe_for_* / block_* / soft_*`)。
|
||||
- 性能:
|
||||
- 单次调用不出现按 content_id 循环查库的行为(可通过日志/测试断言查询次数)。
|
||||
|
||||
217
spec_kit/Personalized Reco/modules/db-design/plan.md
Normal file
217
spec_kit/Personalized Reco/modules/db-design/plan.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# DB Design & Migrations(数据库设计与迁移)|Plan
|
||||
|
||||
> 对应规范:`spec_kit/Personalized Reco/modules/db-design/spec.md`
|
||||
>
|
||||
> 前置确认(已对齐):
|
||||
>
|
||||
> - `content_id`:MySQL **自增主键**;文案微调时 **content_id 不变**(更新同一条记录)。
|
||||
> - `need_suitability/context_suitability`:**JSON** 存储。
|
||||
> - `risk_flags`:选择更强扩展性的方案(本计划采用 **关联表**,利于索引与过滤)。
|
||||
> - 安全池(L3):**方式 A**(`is_safe_pool`)。
|
||||
> - 迁移:使用 **Alembic**,目录放 `server/alembic/`;dev/pro 两套库均可运行同一套迁移。
|
||||
> - 字符集:统一 **utf8mb4**。
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标与交付物
|
||||
|
||||
### 1.1 目标
|
||||
|
||||
- 在“库为空”的前提下,落地推荐系统最小可用的数据模型。
|
||||
- 保证后续推荐查询可实现:按画像条件召回、按 ID 批量查、按风险标记过滤、支持安全池兜底。
|
||||
|
||||
### 1.2 交付物
|
||||
|
||||
- `server/alembic/`:Alembic 初始化目录、`alembic.ini`(或等价配置)、迁移脚本。
|
||||
- SQLAlchemy ORM 模型(建议放 `server/app/db/models/`)。
|
||||
- 初始迁移:创建 `contents`、`content_profiles`、`content_risk_flags`(以及必要索引)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 技术决策(V1)
|
||||
|
||||
### 2.1 表设计原则
|
||||
|
||||
- **分离主体与画像**:`contents` 存文本与来源字段;`content_profiles` 存画像字段(便于未来画像重算/回填)。
|
||||
- **JSON 存 suitability**:`context_suitability`、`need_suitability` 用 JSON,保持结构与规则文档一致。
|
||||
- **risk_flags 关联表**:用 `content_risk_flags(content_id, flag)`,便于:
|
||||
- 快速 Hard Filter(`block_health_medical` 等)
|
||||
- 索引与统计(按 flag 计数)
|
||||
- 兼容旧 flag 映射(在写入/读取层)
|
||||
- **emotion_score 的 general 表示**:用 `NULL` 表示 general(与规则文档“可为 general”语义等价)。
|
||||
- **personalization_power**:存为 `TINYINT`(0/5/10)或 `DECIMAL(2,1)`(0/0.5/1)。本计划推荐 `TINYINT`(更易索引/更省空间),应用层做映射:
|
||||
- 0 → 0.0
|
||||
- 5 → 0.5
|
||||
- 10 → 1.0
|
||||
|
||||
### 2.2 字符集与排序规则
|
||||
|
||||
- 数据库与表:`utf8mb4`
|
||||
- collation:建议 `utf8mb4_0900_ai_ci`(MySQL 8 默认更常见;若环境不同以实际为准,但必须 utf8mb4)
|
||||
|
||||
---
|
||||
|
||||
## 3. 表结构(V1 方案)
|
||||
|
||||
> 以下为“建议 schema”。实际字段名可调整,但语义必须严格对齐 `句子文案打分规则`。
|
||||
|
||||
### 3.1 `contents`(文案主体)
|
||||
|
||||
- `content_id` BIGINT UNSIGNED PK AUTO_INCREMENT
|
||||
- `text` TEXT NOT NULL
|
||||
- `author_id` VARCHAR(64) NULL
|
||||
- `template_id` VARCHAR(64) NULL
|
||||
- `created_at` DATETIME NOT NULL
|
||||
- `updated_at` DATETIME NOT NULL
|
||||
|
||||
索引建议:
|
||||
|
||||
- `idx_contents_author_id(author_id)`
|
||||
- `idx_contents_template_id(template_id)`
|
||||
|
||||
### 3.2 `content_profiles`(内容画像)
|
||||
|
||||
- `content_id` BIGINT UNSIGNED PK(FK → contents.content_id,ON DELETE CASCADE)
|
||||
- `stage` ENUM('general','expecting','parenting','unknown') NOT NULL DEFAULT 'general'
|
||||
- `emotion_score` DECIMAL(3,2) NULL
|
||||
- 约定:NULL 表示 general
|
||||
- `context_suitability_json` JSON NOT NULL
|
||||
- `need_suitability_json` JSON NOT NULL
|
||||
- `personalization_power` TINYINT UNSIGNED NOT NULL DEFAULT 0
|
||||
- 约定:只允许 0/5/10
|
||||
- `review_confidence` DECIMAL(3,2) NULL
|
||||
- 约定:NULL 由推荐模块按 0.7 兜底(对齐规则文档)
|
||||
- `is_safe_pool` BOOLEAN NOT NULL DEFAULT FALSE
|
||||
- `updated_at` DATETIME NOT NULL
|
||||
|
||||
索引建议:
|
||||
|
||||
- `idx_profiles_stage(stage)`
|
||||
- `idx_profiles_personalization_power(personalization_power)`
|
||||
- `idx_profiles_is_safe_pool(is_safe_pool)`
|
||||
|
||||
> 说明:suitability 放 JSON 后,V1 可以先不做 JSON 路径索引;当候选量上来后再加“生成列/函数索引”做加速(见 6.2)。
|
||||
|
||||
### 3.3 `content_risk_flags`(风险标记,关联表)
|
||||
|
||||
- `id` BIGINT UNSIGNED PK AUTO_INCREMENT
|
||||
- `content_id` BIGINT UNSIGNED NOT NULL(FK → contents.content_id,ON DELETE CASCADE)
|
||||
- `flag` VARCHAR(64) NOT NULL
|
||||
- `created_at` DATETIME NOT NULL
|
||||
|
||||
约束与索引:
|
||||
|
||||
- UNIQUE:`uniq_content_flag(content_id, flag)`(同一 content 不重复插同 flag)
|
||||
- 索引:`idx_flag(flag)`(用于 Hard Filter 与统计)
|
||||
- 索引:`idx_content_id(content_id)`(用于按内容批量取 flags)
|
||||
|
||||
命名约束(应用层强制,DB 可选):
|
||||
|
||||
- flag 必须以 `unsafe_for_` / `block_` / `soft_` 开头(严格对齐规则文档)
|
||||
|
||||
---
|
||||
|
||||
## 4. Alembic 迁移落地步骤
|
||||
|
||||
### 4.1 依赖与目录
|
||||
|
||||
- 后端依赖:`alembic`(加入 `server/requirements.txt`,版本随项目统一管理)
|
||||
- 目录:`server/alembic/`(包含 `env.py`、`versions/`)
|
||||
- 连接串:复用现有 `DATABASE_URL`(`mysql+aiomysql://...`)
|
||||
|
||||
### 4.2 初始化与生成迁移(一次性)
|
||||
|
||||
- `alembic init alembic`(在 `server/` 下)
|
||||
- 配置 `env.py`:
|
||||
- 从 `app/core/config.py` 读取 `DATABASE_URL`
|
||||
- 引入 ORM Base 与 models,启用 autogenerate
|
||||
- 创建初始迁移:
|
||||
- `alembic revision --autogenerate -m "init content tables"`
|
||||
- `alembic upgrade head`
|
||||
|
||||
### 4.3 dev/pro 一致性
|
||||
|
||||
- 迁移脚本保持同一套;通过不同环境的 `DATABASE_URL` 指向 `mindfulness_dev` 或 `mindfulness`。
|
||||
|
||||
---
|
||||
|
||||
## 5. 入库流程(写入契约)
|
||||
|
||||
### 5.1 一条文案最小入库数据
|
||||
|
||||
必须字段(V1 最小可用):
|
||||
|
||||
- `text`
|
||||
- `content_profiles.stage`
|
||||
- `content_profiles.emotion_score`(可为 NULL 表示 general)
|
||||
- `content_profiles.context_suitability_json`(必须包含 5 个 key:family/work/relationship/friends/health,值为 0/0.5/1)
|
||||
- `content_profiles.need_suitability_json`(必须包含 5 个 key:emotional_support/parenting_pressure/self_worth/anxiety_relief/rest_balance,值为 0/0.5/1)
|
||||
- `content_profiles.personalization_power`(0/5/10)
|
||||
- `content_risk_flags`(可为空集合,但若存在必须按命名规范)
|
||||
|
||||
强烈建议字段:
|
||||
|
||||
- `author_id`、`template_id`
|
||||
- `review_confidence`
|
||||
- `is_safe_pool`(若要参与 L3 安全池)
|
||||
|
||||
### 5.2 写入策略
|
||||
|
||||
- 创建文案时:
|
||||
- 先写 `contents` 得到 `content_id`(自增)
|
||||
- 再写 `content_profiles`(同 content_id)
|
||||
- 再批量写 `content_risk_flags`
|
||||
- 文案微调时(content_id 不变):
|
||||
- 更新 `contents.text` 与 `updated_at`
|
||||
- 同步更新 `content_profiles`(若画像变更)
|
||||
- risk_flags 做“全量覆盖”或“差量更新”(plan 实现阶段定)
|
||||
|
||||
---
|
||||
|
||||
## 6. 查询与性能规划
|
||||
|
||||
### 6.1 V1 查询策略(先可用)
|
||||
|
||||
- 候选召回:
|
||||
- 先按 `stage`、`personalization_power`、`is_safe_pool` 等可索引字段进行粗过滤
|
||||
- 再在应用层结合 suitability JSON 与 risk_flags 做精过滤/打分
|
||||
- Hard Filter:
|
||||
- 通过 `content_risk_flags` join 或子查询排除指定 flags(如 `block_health_medical`)
|
||||
- 批量查:
|
||||
- `content_id IN (...)` join `content_profiles` + left join `content_risk_flags`
|
||||
|
||||
### 6.2 V1.1 性能增强(候选量上来后再做)
|
||||
|
||||
当候选池变大、应用层过滤成本上升时,优先做两类增强:
|
||||
|
||||
- **生成列/函数索引**:为常用召回维度(例如 need/context 的某些 key)创建 generated columns(从 JSON_EXTRACT 取值并映射到 TINYINT),再加索引。
|
||||
- **风险 flag 位图/派生列**:对 `block_health_medical` 等强规则增加派生布尔列(或维护冗余表),降低 join 成本。
|
||||
|
||||
---
|
||||
|
||||
## 7. 测试与验收(DB 子模块)
|
||||
|
||||
### 7.1 迁移验收
|
||||
|
||||
- 在全新库执行 `alembic upgrade head` 成功。
|
||||
- 执行 `downgrade`(若实现)可回滚(至少在开发环境可用)。
|
||||
|
||||
### 7.2 数据契约验收
|
||||
|
||||
插入一条最小文案记录后,能够查询并组装出推荐模块所需的 `ContentProfile` 字段集合:
|
||||
|
||||
- `content_id/text/stage/emotion_score/context_suitability/need_suitability/personalization_power/risk_flags`
|
||||
|
||||
### 7.3 规则口径验收(写入侧)
|
||||
|
||||
- 写入 `risk_flags` 时,若出现旧 flag(如 `block_stage_unknown`):
|
||||
- 写入层需在入库前映射为新命名(或拒绝写入并提示)
|
||||
- 推荐侧读取层不得再出现旧 flag 名称
|
||||
|
||||
---
|
||||
|
||||
## 8. 与其他子模块的接口约定
|
||||
|
||||
- `Content Repository` 只依赖本模块提供的表与字段语义,不依赖具体迁移实现细节。
|
||||
- 推荐引擎/打分模块对 `review_confidence` 的缺省值假设(0.7)在 DB 缺失时依然成立。
|
||||
|
||||
91
spec_kit/Personalized Reco/modules/db-design/spec.md
Normal file
91
spec_kit/Personalized Reco/modules/db-design/spec.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 子模块:DB Design & Migrations(数据库设计与迁移)|Spec
|
||||
|
||||
## 1. 目标描述
|
||||
|
||||
在当前“数据库尚未设计且为空”的前提下,为个性化推荐提供最小可用的数据存储与索引能力:
|
||||
|
||||
- 存储文案及其内容画像(Content Profile,Cᵢ)。
|
||||
- 能按画像条件进行候选召回(need/context/stage/general、personalization_power、risk_flags 等)。
|
||||
- 能按 `content_id` 批量查询(补全候选、去重/重排时取字段)。
|
||||
- 为后续标注/审核/置信度补齐留出扩展空间。
|
||||
|
||||
> 规则口径:字段语义必须严格对齐 `设计说明文档/句子文案打分規則.md`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 输入 / 输出定义
|
||||
|
||||
### 2.1 输入
|
||||
|
||||
- 内容侧提供的文案与画像数据(可由运营导入、AI Reviewer 产出、人审修正等方式写入)。
|
||||
- 推荐模块对数据访问的需求(召回过滤字段、排序字段、频控字段)。
|
||||
|
||||
### 2.2 输出
|
||||
|
||||
- 一套 MySQL 表结构(或视图)满足 `ContentProfile` 字段契约:
|
||||
- `content_id`(稳定主键)
|
||||
- `text`
|
||||
- `stage`(`general/expecting/parenting/unknown`)
|
||||
- `emotion_score`(0~1 或 `general` 的等价表示)
|
||||
- `context_suitability`(每个 context 的 {0,0.5,1})
|
||||
- `need_suitability`(每个 need 的 {0,0.5,1})
|
||||
- `personalization_power`(0/0.5/1)
|
||||
- `risk_flags`(命名以 `unsafe_for_* / block_* / soft_*` 为准)
|
||||
- 可选:`author_id`、`template_id`、`review_confidence`
|
||||
- Alembic 迁移脚本:`alembic revision --autogenerate` / `alembic upgrade head` 可创建上述表。
|
||||
- 推荐查询需要的索引(至少支持按场景召回与按 ID 批量查)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 建议表结构(V1,允许后续调整)
|
||||
|
||||
> 说明:本子模块不强制具体表名;但需保证字段语义与索引可用。以下给出一个推荐落地方案,便于后续 plan 直接实现。
|
||||
|
||||
### 3.1 `contents`(文案主体)
|
||||
|
||||
- `content_id`(PK)
|
||||
- `text`
|
||||
- `author_id`(可空)
|
||||
- `template_id`(可空)
|
||||
- `created_at` / `updated_at`
|
||||
|
||||
### 3.2 `content_profiles`(内容画像)
|
||||
|
||||
- `content_id`(PK/FK → contents)
|
||||
- `stage`(枚举:general/expecting/parenting/unknown)
|
||||
- `emotion_score`(可为 NULL 表示 general,或用额外字段 `emotion_is_general` 表示)
|
||||
- `context_suitability_json`(JSON:每个 context -> 0/0.5/1)
|
||||
- `need_suitability_json`(JSON:每个 need -> 0/0.5/1)
|
||||
- `personalization_power`(DECIMAL(2,1) 或 TINYINT 映射到 0/0.5/1)
|
||||
- `risk_flags_json`(JSON 数组:字符串集合)
|
||||
- `review_confidence`(DECIMAL(3,2),缺省可按 0.7 处理,由推荐模块兜底)
|
||||
- `updated_at`
|
||||
|
||||
### 3.3 “通用安全池”支持(L3 兜底)
|
||||
|
||||
至少提供一种方式能拉到安全池内容:
|
||||
|
||||
- 方式 A:`content_profiles` 增加 `is_safe_pool`(boolean)
|
||||
- 方式 B:单独 `safe_pool_contents`(content_id 列表)
|
||||
|
||||
---
|
||||
|
||||
## 4. 索引与查询能力(V1 必需)
|
||||
|
||||
- **按 ID 批量查**:`content_id in (...)`
|
||||
- **按 stage / personalization_power 过滤**:支持候选召回与回退梯度
|
||||
- **按 risk_flags 过滤**:
|
||||
- 推荐做法:将 `risk_flags_json` 冗余为可索引的派生列/位图/多表行(plan 阶段定实现)
|
||||
- V1 最小可用:允许先在应用层过滤(但需控制候选量,避免全表扫)
|
||||
|
||||
---
|
||||
|
||||
## 5. 验收标准(可验证)
|
||||
|
||||
- **迁移可运行**:全新 MySQL 库上执行 `alembic upgrade head` 可成功创建表结构。
|
||||
- **字段契约可满足**:能从 DB 读出 `ContentProfile` 需要的字段(至少 content_id/text/stage/emotion_score/context_suitability/need_suitability/personalization_power/risk_flags)。
|
||||
- **基本查询可用**:
|
||||
- 能按 `content_id` 批量拉取内容画像
|
||||
- 能按 `stage/general` 与 `personalization_power` 条件召回候选(用于 L0~L3)
|
||||
- **安全池可用**:能稳定拉取 L3 兜底候选集(不依赖用户画像)。
|
||||
|
||||
99
spec_kit/Personalized Reco/modules/db-design/tasks.md
Normal file
99
spec_kit/Personalized Reco/modules/db-design/tasks.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# DB Design & Migrations(数据库设计与迁移)|Tasks
|
||||
|
||||
> 对应计划:`spec_kit/Personalized Reco/modules/db-design/plan.md`
|
||||
>
|
||||
> 目标:在“库为空”的前提下,落地推荐系统最小可用数据模型 + Alembic 迁移,并通过最小查询/契约验收。
|
||||
|
||||
---
|
||||
|
||||
## 0. 任务状态约定
|
||||
|
||||
- `[ ]`:未开始
|
||||
- `[~]`:进行中
|
||||
- `[x]`:已完成
|
||||
- `[-]`:已取消/不做(需写明原因)
|
||||
|
||||
---
|
||||
|
||||
## 1. 环境与依赖准备
|
||||
|
||||
- [ ] **确认 MySQL 版本与字符集支持**
|
||||
- 验收:MySQL 版本为 8.x;库/表可用 `utf8mb4`(建议 `utf8mb4_0900_ai_ci`)。
|
||||
- [x] **后端依赖补齐 Alembic**
|
||||
- 说明:`server/requirements.txt` 已包含 `alembic>=1.13`
|
||||
- 验收:在 `server/.venv` 中可成功 `import alembic`。
|
||||
|
||||
---
|
||||
|
||||
## 2. ORM 模型落地(推荐最小集合)
|
||||
|
||||
- [x] **创建 ORM 模型目录**
|
||||
- 目标路径:`server/app/db/models/`
|
||||
- 验收:目录存在,且可被 Python 正常 import。
|
||||
- [x] **实现 `contents` 模型**
|
||||
- 字段:`content_id(PK, 自增)`、`text_en?`、`text_tc?`、`author_id?`、`template_id?`、`created_at`、`updated_at`
|
||||
- 索引:`author_id`、`template_id`
|
||||
- 验收:Alembic autogenerate 能识别表结构。
|
||||
- [x] **实现 `content_profiles` 模型**
|
||||
- 字段:`content_id(PK/FK)`、`stage(enum)`、`emotion_score(NULL=general)`、`context_suitability_json(JSON)`、`need_suitability_json(JSON)`、`personalization_power(0/5/10)`、`review_confidence?`、`is_safe_pool`、`updated_at`
|
||||
- 索引:`stage`、`personalization_power`、`is_safe_pool`
|
||||
- 验收:Alembic autogenerate 能识别表结构;`content_id` 具备外键与级联删除。
|
||||
- [x] **实现 `content_risk_flags` 模型(关联表)**
|
||||
- 字段:`id(PK)`、`content_id(FK)`、`flag`、`created_at`
|
||||
- 约束:`UNIQUE(content_id, flag)`
|
||||
- 索引:`flag`、`content_id`
|
||||
- 验收:Alembic autogenerate 能识别唯一约束与索引。
|
||||
|
||||
---
|
||||
|
||||
## 3. Alembic 初始化与迁移生成
|
||||
|
||||
- [x] **初始化 Alembic 目录**
|
||||
- 目标位置:`server/alembic/`(含 `versions/`、`env.py`)
|
||||
- 验收:在 `server/` 下可运行 `alembic -h` 且能读取配置。
|
||||
- [x] **配置 Alembic 连接串来源**
|
||||
- 要求:复用现有 `DATABASE_URL`(对齐 `server/app/core/config.py`)
|
||||
- 验收:`alembic` 命令可加载 `env.py` 并读取 `DATABASE_URL`(未连 DB 验收留到第 4 章)。
|
||||
- [x] **配置 `env.py` 支持 autogenerate**
|
||||
- 要求:引入 ORM `Base` 与 models(确保 metadata 完整)
|
||||
- 验收:Alembic 能识别 `target_metadata`;且已提供初始迁移版本文件。
|
||||
- [ ] **生成并执行初始迁移**
|
||||
- 命令:`alembic upgrade head`
|
||||
- 验收:数据库中出现 `contents`、`content_profiles`、`content_risk_flags` 与 Alembic 版本表。
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据契约验收(最小入库与查询)
|
||||
|
||||
- [ ] **准备一条最小文案数据(人工插入或脚本)**
|
||||
- 必须字段(对齐 plan):`text`、`stage`、`emotion_score(可 NULL)`、`context_suitability_json(5 keys)`、`need_suitability_json(5 keys)`、`personalization_power(0/5/10)`、`risk_flags(可空)`
|
||||
- 验收:可插入成功,不违反约束。
|
||||
- [ ] **验证按 `content_id` 批量查询可用**
|
||||
- 目标:能 join 组装出 `ContentProfile` 所需字段集合(含 risk_flags 列表)
|
||||
- 验收:至少验证字段:`content_id/text/stage/emotion_score/context_suitability/need_suitability/personalization_power/risk_flags`。
|
||||
- [ ] **验证 Hard Filter 关键 flag 可过滤**
|
||||
- 插入:至少一条带 `block_health_medical` 的记录
|
||||
- 验收:通过 `content_risk_flags` 可在 SQL 层排除该内容(后续供推荐候选召回使用)。
|
||||
- [ ] **验证安全池(L3)可用**
|
||||
- 插入:至少一条 `is_safe_pool=true`
|
||||
- 验收:可单独查询出安全池候选集(不依赖画像条件)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 规则口径验收(risk_flags 严格对齐)
|
||||
|
||||
- [ ] **建立 risk_flags 白名单/校验策略(写入侧或读取侧)**
|
||||
- 要求:flag 命名必须以 `unsafe_for_` / `block_` / `soft_` 开头(对齐 `句子文案打分规则`)
|
||||
- 验收:插入非法前缀时被拒绝或被修正(选择其一,并写清策略)。
|
||||
- [ ] **旧 flag 兼容映射验收(若存在历史数据导入)**
|
||||
- 覆盖:`block_stage_unknown`→`unsafe_for_stage_unknown` 等(详见 plan)
|
||||
- 验收:系统对外(读出/下游)不再出现旧 flag 名称。
|
||||
|
||||
---
|
||||
|
||||
## 6. 文档与交接
|
||||
|
||||
- [ ] **补齐本子模块 README/说明(可选,但建议)**
|
||||
- 内容:如何初始化 DB、如何跑迁移、如何插入一条最小文案数据、如何验证查询。
|
||||
- 验收:新同学按文档能在 30 分钟内跑通迁移 + 插入 + 查询。
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# 子模块:Integration(FastAPI API + Celery Worker)|Spec
|
||||
|
||||
## 1. 目标描述
|
||||
|
||||
将推荐引擎对外暴露为两种调用方式,满足“API 与任务两者都需要”的集成要求:
|
||||
|
||||
- **FastAPI**:同步请求/响应式获取推荐结果(客户端直接请求)。
|
||||
- **Celery**:后台任务式生成推荐(用于 Push/Widget 的定时或批处理)。
|
||||
|
||||
两种方式都必须调用同一套 `Reco Engine`,确保行为一致、便于回归测试。
|
||||
|
||||
---
|
||||
|
||||
## 2. 输入 / 输出定义
|
||||
|
||||
### 2.1 API 输入(HTTP Request)
|
||||
|
||||
- `scene`: `feed | push | widget`
|
||||
- `k`(可选;默认按场景)
|
||||
- `user_profile`(客户端问卷画像,V1.2)
|
||||
- `already_recommended_ids`(数组)
|
||||
- `touched_or_viewed_ids`(数组)
|
||||
- (可选)`now`(用于可测试性;生产可不传,由服务端生成)
|
||||
|
||||
### 2.2 API 输出(HTTP Response)
|
||||
|
||||
- `items`: 推荐句子列表
|
||||
- `meta`: RecoMeta(候选规模、回退层级、empty_reason 等)
|
||||
|
||||
### 2.3 Celery 任务输入/输出
|
||||
|
||||
任务输入与 API 输入等价,但建议仅传必要字段并保持 payload 小(避免 Redis 膨胀):
|
||||
|
||||
- 允许只传 `user_profile` 与历史集合;若历史集合很大,建议传引用 ID(后续演进)。
|
||||
|
||||
任务输出:
|
||||
|
||||
- 可选择“不存结果”(默认忽略结果)并在任务内部将推荐结果写入下游(如消息队列/推送系统/DB)。
|
||||
- 或返回 `items/meta`(仅用于开发调试)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 关键约束
|
||||
|
||||
- API 与 Celery **不得复制推荐逻辑**:只能调用 `Reco Engine`。
|
||||
- 输入合法性:
|
||||
- 不因字段缺失而报错(画像允许缺失/跳过)。
|
||||
- `already_recommended_ids/touched_or_viewed_ids` 允许为空数组。
|
||||
- 可测试性:
|
||||
- 允许注入 `now`(或在 header/参数中提供),便于确定性测试。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验收标准(可验证)
|
||||
|
||||
- **API 可用**:给定合法请求体可返回推荐结果与 meta。
|
||||
- **任务可用**:Celery worker 能消费任务并成功调用推荐引擎(至少跑通 ping → reco 的调用链)。
|
||||
- **一致性**:相同输入下,API 与任务调用返回结果一致(忽略时间戳字段差异时)。
|
||||
|
||||
45
spec_kit/Personalized Reco/modules/observability/spec.md
Normal file
45
spec_kit/Personalized Reco/modules/observability/spec.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 子模块:Observability(可观测性与打点载荷)|Spec
|
||||
|
||||
## 1. 目标描述
|
||||
|
||||
定义推荐模块的统一可观测载荷(meta/event),用于:
|
||||
|
||||
- 线上监控覆盖率、回退率、候选规模分布、过滤原因分布。
|
||||
- 支撑调参(权重/回退阈值/频控窗口)与风险排查(Hard Filter 命中情况)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 输入 / 输出定义
|
||||
|
||||
### 2.1 输入
|
||||
|
||||
- 推荐引擎内部各阶段统计信息:
|
||||
- 候选生成数量、过滤后数量、去重后数量、频控后数量
|
||||
- 回退层级与触发原因
|
||||
- served_k
|
||||
- 缺失字段情况、conf_U
|
||||
|
||||
### 2.2 输出
|
||||
|
||||
统一的 `RecoMeta`(返回给调用方;调用方负责上报/落库/打点):
|
||||
|
||||
- `scene`
|
||||
- `candidate_pool_size_raw`
|
||||
- `candidate_pool_size_after_hard_filter`
|
||||
- `candidate_pool_size_after_dedup`
|
||||
- `candidate_pool_size_after_freqcap`
|
||||
- `fallback_level_final`
|
||||
- `served_k`
|
||||
- `empty_reason`(served_k=0 必填;枚举建议:`hard_filter_all | freqcap_all | pool_empty | unknown`)
|
||||
- `conf_U`
|
||||
- `missing_fields`:`{ need: boolean; context: boolean; emotion: boolean }`
|
||||
- (可选)`risk_filtered_count_by_flag`:`{ flag: count }`
|
||||
|
||||
---
|
||||
|
||||
## 3. 验收标准(可验证)
|
||||
|
||||
- 每次推荐调用都能产出 `RecoMeta`,并随响应/任务结果返回(或被上层打点系统消费)。
|
||||
- `served_k=0` 时 `empty_reason` 必不为空,且与实际清空阶段一致。
|
||||
- `fallback_level_final` 能真实反映最终回退层级(用于回退率报表)。
|
||||
|
||||
81
spec_kit/Personalized Reco/modules/reco-engine/spec.md
Normal file
81
spec_kit/Personalized Reco/modules/reco-engine/spec.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# 子模块:Reco Engine(推荐引擎编排)|Spec
|
||||
|
||||
## 1. 目标描述
|
||||
|
||||
实现推荐的主编排器(Orchestrator):
|
||||
|
||||
- 输入用户画像 + 历史集合 + 场景参数。
|
||||
- 调用 `Content Repository` 拉取候选。
|
||||
- 执行统一 Pipeline:Candidate → Hard Filter → Soft Scoring → Rerank/Freqcap → Fallback Ladder。
|
||||
- 输出推荐句子与 `meta`(可观测字段),供 API 或 Celery 上层直接下发。
|
||||
|
||||
---
|
||||
|
||||
## 2. 输入 / 输出定义
|
||||
|
||||
### 2.1 输入
|
||||
|
||||
- `scene`: `feed | push | widget`
|
||||
- `user_profile`: 客户端问卷画像(V1.2)
|
||||
- `already_recommended_ids`: `List[str|int]`
|
||||
- `touched_or_viewed_ids`: `List[str|int]`
|
||||
- `k`: int(feed 默认 30;push/widget 默认 1)
|
||||
- `now`: 时间戳
|
||||
- (可选)`constraints`:黑名单 author/template、最大候选量上限等
|
||||
|
||||
### 2.2 输出
|
||||
|
||||
- `items: List[RecommendedItem]`(长度 ≤ k)
|
||||
- `content_id`、`text`、`final_score`
|
||||
- `fallback_level_final`
|
||||
- `explanations`(可选,便于调参/排查)
|
||||
- `meta: RecoMeta`
|
||||
- `candidate_pool_size_raw`
|
||||
- `candidate_pool_size_after_hard_filter`
|
||||
- `candidate_pool_size_after_dedup`
|
||||
- `candidate_pool_size_after_freqcap`
|
||||
- `fallback_level_final`
|
||||
- `served_k`
|
||||
- `empty_reason`(served_k=0 必填)
|
||||
- `missing_fields`(need/context/emotion 的缺失情况)
|
||||
- `scene`、`conf_U`
|
||||
|
||||
---
|
||||
|
||||
## 3. 缺失字段判定(必须与客户端契约一致)
|
||||
|
||||
- `U.need` 为空对象 `{}` 或不存在 → 视为缺失
|
||||
- `U.context` 为空对象 `{}` 或不存在 → 视为缺失
|
||||
- `U.emotion_score` 为 `null`/不存在 → 视为缺失
|
||||
|
||||
当缺失明显或 `conf_U` 偏低时:
|
||||
|
||||
- Push:必须启用不确定性惩罚 `P_uncertainty`,并自动降个性化(限制 `personalization_power` 上限)。
|
||||
- 任意场景:候选生成至少按 **L1** 处理(降个性化,增加通用安全占比)。
|
||||
|
||||
---
|
||||
|
||||
## 4. Fallback Ladder(回退梯度,必须实现)
|
||||
|
||||
- L0:正常召回配比
|
||||
- L1:放宽匹配 + `personalization_power ≤ 0.5`
|
||||
- L2:回退通用池 + `personalization_power = 0`
|
||||
- L3:仅安全池(白名单/安全池)
|
||||
|
||||
触发条件(任一满足即可回退):
|
||||
|
||||
- 候选池为空 / Hard Filter 清空 / 去重清空 / 频控清空
|
||||
- served_k < k(feed 可允许部分不足,但需记录并可继续回退补齐,具体由实现配置)
|
||||
|
||||
每次回退必须更新 meta 中的候选规模与触发原因。
|
||||
|
||||
---
|
||||
|
||||
## 5. 验收标准(可验证)
|
||||
|
||||
- **稳定性**:任意输入(包括画像字段缺失、历史集合为空/很大)不报错,返回结构稳定。
|
||||
- **回退可观测**:当候选不足时能逐级回退,且 `fallback_level_final` 与 `empty_reason` 正确。
|
||||
- **去重生效**:输出不包含 `already_recommended_ids` 与 `touched_or_viewed_ids` 中的 content_id。
|
||||
- **风险优先**:Hard Filter 始终优先执行(尤其 `block_health_medical` 必挡)。
|
||||
- **跨调用复用**:同一引擎既可被 FastAPI API 调用,也可被 Celery 任务调用(无框架耦合)。
|
||||
|
||||
87
spec_kit/Personalized Reco/modules/rerank-freqcap/spec.md
Normal file
87
spec_kit/Personalized Reco/modules/rerank-freqcap/spec.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 子模块:Rerank & Freqcap(重排 / 去重 / 频控)|Spec
|
||||
|
||||
## 1. 目标描述
|
||||
|
||||
对 Soft Scoring 后的候选集做最终的“可下发排序”,解决:
|
||||
|
||||
- **去重**:避免同一句在短期内反复出现。
|
||||
- **多样性**:Feed 场景需要序列多样性(作者/模板/标签)。
|
||||
- **频控与冷却**:Push/Widget 必做,按句子/作者/模板维度做窗口期不重复。
|
||||
|
||||
> 当前确认:历史集合由客户端在请求时传入(已推荐 ID、已触达/浏览 ID)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 输入 / 输出定义
|
||||
|
||||
### 2.1 输入
|
||||
|
||||
- `scene`: `feed | push | widget`
|
||||
- `scored_candidates`: `List[{ content_id, score, content_profile... }]`
|
||||
- `already_recommended_ids`: `List[str|int]`
|
||||
- `touched_or_viewed_ids`: `List[str|int]`
|
||||
- (可选)`recent_author_ids` / `recent_template_ids`(若客户端暂不传,可由后续服务端侧补齐)
|
||||
- `k`: 目标条数
|
||||
- `config`:
|
||||
- Feed:`mmr_lambda`(默认 0.7)
|
||||
- 冷却窗口:`cooldown_sentence_days / cooldown_author_days / cooldown_template_days`
|
||||
|
||||
### 2.2 输出
|
||||
|
||||
- `ranked_items`: 排好序的候选(长度 ≤ k)
|
||||
- `meta`:
|
||||
- `candidate_pool_size_after_dedup`
|
||||
- `candidate_pool_size_after_freqcap`
|
||||
- `freqcap_filtered_counts`(可选,按维度统计)
|
||||
|
||||
---
|
||||
|
||||
## 3. 规则与算法(V1 最小集合)
|
||||
|
||||
### 3.1 去重(必做)
|
||||
|
||||
- 过滤 `content_id` ∈ `already_recommended_ids ∪ touched_or_viewed_ids` 的候选。
|
||||
- 去重键定义(对齐算法规则的工程约定):
|
||||
- `sentence_key = content_id`
|
||||
- `author_key = author_id`(可空)
|
||||
- `template_key = template_id`(可空)
|
||||
|
||||
### 3.2 Feed:MMR 序列重排(建议)
|
||||
|
||||
- Top1:最高分,确保第一眼命中。
|
||||
- 后续:使用 MMR 选择序列,最大化与已选内容的差异(作者/模板/need/context/stage)。
|
||||
|
||||
MMR 定义:
|
||||
|
||||
\[
|
||||
MMR(c)=\lambda\cdot Rel(c) - (1-\lambda)\cdot \max_{s\in S} Sim(c,s)
|
||||
\]
|
||||
|
||||
其中:
|
||||
|
||||
- `Rel(c)`:可直接使用 `final_score`
|
||||
- `Sim(c,s)`:离散特征版(V1 推荐):
|
||||
- content_id 相同:1
|
||||
- template_id 相同:+0.6
|
||||
- author_id 相同:+0.3
|
||||
- 标签重合(Jaccard):+0.1 * Jaccard(tags_c, tags_s)
|
||||
- clamp 到 [0,1]
|
||||
|
||||
### 3.3 Push/Widget:频控与冷却(必做)
|
||||
|
||||
最小要求:
|
||||
|
||||
- 同一句在窗口 X 天内不重复(句子冷却)。
|
||||
- 同作者/同模板在窗口期内尽量不重复(可作为硬频控或强降权,plan 阶段定)。
|
||||
|
||||
> 当前 V1 输入侧只确认有 content_id 历史;若 author/template 历史暂不具备,可先对 content_id 强硬去重,并将 author/template 频控作为“可选增强”(meta 仍需统计缺失原因)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验收标准(可验证)
|
||||
|
||||
- **去重正确**:输出不包含 `already_recommended_ids` 与 `touched_or_viewed_ids`。
|
||||
- **Feed 序列多样**:在候选足够时,序列中不会出现大量同作者/同模板连续重复(可用统计阈值验收)。
|
||||
- **Push/Widget 频控生效**:在冷却窗口内同一句不会再次被推荐(基于传入历史集合验证)。
|
||||
- **可观测**:输出 meta 中包含 `after_dedup/after_freqcap` 的候选规模,便于排查 served_k 不足原因。
|
||||
|
||||
97
spec_kit/Personalized Reco/modules/scoring/spec.md
Normal file
97
spec_kit/Personalized Reco/modules/scoring/spec.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 子模块:Scoring(软打分与惩罚项)|Spec
|
||||
|
||||
## 1. 目标描述
|
||||
|
||||
实现推荐排序的打分函数与分解项,严格对齐 `設計說明文檔/個性化推薦算法規則.md` 的公式与 V1.2 缺失字段约定,并与内容画像(Cᵢ)结构对齐 `句子文案打分规则`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 输入 / 输出定义
|
||||
|
||||
### 2.1 输入
|
||||
|
||||
- `scene`: `feed | push | widget`
|
||||
- `user_profile`(U)
|
||||
- `content_profile`(Cᵢ)
|
||||
- `now`(用于 freshness/时间衰减,若 V1 暂不实现可先占位)
|
||||
- `config`(权重与开关)
|
||||
- `w_need/w_emotion/w_stage/w_context`
|
||||
- `alpha`(个性化加成系数)
|
||||
- `beta`(不确定性惩罚系数)
|
||||
- `enable_uncertainty_penalty`(push 默认 true)
|
||||
- `widget_emotion_soft_range`(0.4~0.8,软降权)
|
||||
|
||||
### 2.2 输出
|
||||
|
||||
- `final_score: float`
|
||||
- `breakdown`(用于调参/可观测,V1 可选但建议保留):
|
||||
- `S_need/S_context/S_stage/S_emotion`
|
||||
- `S_core/S_personal/S_fresh`
|
||||
- `P_fatigue/P_repeat/P_risk/P_uncertainty`
|
||||
- `missing_fields`(need/context/emotion)
|
||||
|
||||
---
|
||||
|
||||
## 3. 计算规则(必须实现的最小集合)
|
||||
|
||||
### 3.1 核心结构(线性加权 + 惩罚)
|
||||
|
||||
\[
|
||||
final\_score(U,C_i)=\mathbb{I}[pass]\times\Big(S_{core}+S_{personal}+S_{fresh}-P_{fatigue}-P_{repeat}-P_{risk}-P_{uncertainty}\Big)
|
||||
\]
|
||||
|
||||
\[
|
||||
S_{core}=w_{need}S_{need}+w_{emotion}S_{emotion}+w_{stage}S_{stage}+w_{context}S_{context}
|
||||
\]
|
||||
|
||||
### 3.2 V1.2 缺失字段的保守计算(必须对齐)
|
||||
|
||||
- 若 `U.need` 缺失 → `S_need = 0.5`
|
||||
- 若 `U.context` 缺失 → `S_context = 0.5`
|
||||
- 若 `U.emotion_score` 缺失 → `S_emotion = 0.8`
|
||||
- 文案为 general 时仍按 0.8(对齐算法规则)
|
||||
|
||||
### 3.3 个性化加成(受回退梯度/降个性化约束)
|
||||
|
||||
\[
|
||||
S_{personal}=\alpha \cdot C_i.personalization\_power \cdot \max(S_{need}, S_{context})
|
||||
\]
|
||||
|
||||
当触发降个性化(缺失明显 / 低置信度 / fallback_level≥1)时:
|
||||
|
||||
- L1:限制 `personalization_power ≤ 0.5`
|
||||
- L2/L3:限制 `personalization_power = 0`
|
||||
|
||||
### 3.4 不确定性惩罚(Push 默认启用)
|
||||
|
||||
\[
|
||||
P_{uncertainty}=\beta \cdot (1-conf_U)\cdot(1-conf_{C_i})\cdot C_i.personalization\_power
|
||||
\]
|
||||
|
||||
其中:
|
||||
|
||||
- `conf_U` 来自 `user_profile.profile_confidence`
|
||||
- `conf_{C_i}` 来自 `content_profile.review_confidence`(缺省 0.7,对齐 `句子文案打分规则`)
|
||||
|
||||
### 3.5 Widget 情绪区间(软降权)
|
||||
|
||||
- 若 `scene=widget` 且 `C_i.emotion_score` 可计算(非 general):
|
||||
- 当 `emotion_score` 不在 0.4~0.8:不硬过滤,但需要产生一个降权项(可并入 `P_risk` 或单独 `P_widget_emotion_out_of_range`)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 场景默认权重(V1 建议,来源算法规则)
|
||||
|
||||
- Feed:`w_need=0.35, w_emotion=0.20, w_stage=0.15, w_context=0.30`
|
||||
- Push:`w_need=0.45, w_emotion=0.35, w_stage=0.15, w_context=0.05`
|
||||
- Widget:`w_need=0.25, w_emotion=0.25, w_stage=0.30, w_context=0.20`
|
||||
|
||||
---
|
||||
|
||||
## 5. 验收标准(可验证)
|
||||
|
||||
- **缺失字段一致性**:当 U 的 need/context/emotion 缺失时,S_need/S_context/S_emotion 按 0.5/0.5/0.8 计算,且不会报错。
|
||||
- **不确定性惩罚生效**:Push 默认启用,低 `conf_U` 或低 `conf_C` 时,高 `personalization_power` 内容分数降低。
|
||||
- **Widget 软降权生效**:0.4~0.8 区间外不被硬挡,但分数能明显被压低(可通过断言对比分数验证)。
|
||||
- **可调参**:权重与系数可配置,调整不会破坏输出结构。
|
||||
|
||||
107
spec_kit/Personalized Reco/overview.md
Normal file
107
spec_kit/Personalized Reco/overview.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# Personalized Reco|Overview(大规范总览)
|
||||
|
||||
> 目的:保留大规范的背景/总览,并作为各子模块规范的索引入口。
|
||||
>
|
||||
> 依据:
|
||||
>
|
||||
> - `设计说明文档/個性化推薦算法規則.md`
|
||||
> - `设计说明文档/句子文案打分規則.md`(risk_flags 命名与语义的唯一准绳,必须严格对齐)
|
||||
> - `spec_kit/User Profile Scoring/spec.md`(客户端问卷画像输出契约,V1.2)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景
|
||||
|
||||
需要在后端实现一套可复用、可调参、可观测、可回退的推荐流程,覆盖 Feed / Push / Widget 三种分发场景。推荐模块必须与 API/任务解耦,避免逻辑散落;并在 Push 等高风险场景自动“降个性化/降风险”。
|
||||
|
||||
当前约束与确认点(来自需求澄清):
|
||||
|
||||
- **数据库现状**:数据库尚未设计,当前为空(需要新增 DB 设计与迁移子模块)。
|
||||
- **规则口径**:risk_flags 等内容画像语义必须严格对齐 `句子文案打分规则`。
|
||||
- **调用方式**:推荐请求时由客户端传入 `user_profile` 与历史集合(已推荐 ID、已触达/浏览 ID)。
|
||||
- **Widget 情绪区间**:0.4~0.8 采用**软降权**(非硬过滤)。
|
||||
- **频控**:Push/Widget 需要冷却与频控(确认要做)。
|
||||
- **集成形态**:既需要 FastAPI API 接口,也需要 Celery 任务调用(两者都要)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 大需求输入/输出(对外契约摘要)
|
||||
|
||||
- **输入**:
|
||||
- `scene`: `feed | push | widget`
|
||||
- `user_profile`: 客户端问卷画像(V1.2,支持字段缺失/跳过)
|
||||
- `already_recommended_ids`: 已推荐过的文案 ID(本次请求需去重)
|
||||
- `touched_or_viewed_ids`: 已触达/浏览的文案 ID(本次请求需去重/疲劳)
|
||||
- `k`:返回条数(feed 默认 30;push/widget 默认 1)
|
||||
- **输出**:
|
||||
- 推荐句子列表(`content_id` + `text` + `final_score` 等)
|
||||
- `meta`:候选规模、过滤/频控后规模、回退层级、empty_reason 等(用于打点)
|
||||
|
||||
---
|
||||
|
||||
## 3. 子模块拆分(modules/)
|
||||
|
||||
> 原则:每个子模块都可独立实现、测试、验收;子模块之间通过明确接口关联。
|
||||
|
||||
目录结构:
|
||||
|
||||
```text
|
||||
spec_kit/Personalized Reco/
|
||||
├ overview.md
|
||||
├ spec.md
|
||||
└ modules/
|
||||
├ db-design/
|
||||
│ └ spec.md
|
||||
├ content-repository/
|
||||
│ └ spec.md
|
||||
├ reco-engine/
|
||||
│ └ spec.md
|
||||
├ scoring/
|
||||
│ └ spec.md
|
||||
├ rerank-freqcap/
|
||||
│ └ spec.md
|
||||
├ observability/
|
||||
│ └ spec.md
|
||||
└ integration-api-worker/
|
||||
└ spec.md
|
||||
```
|
||||
|
||||
模块关系(逻辑依赖):
|
||||
|
||||
- `integration-api-worker` → 调用 `reco-engine`
|
||||
- `reco-engine` → 依赖 `content-repository` 拉取候选
|
||||
- `reco-engine` → 依赖 `hard filter / scoring / rerank-freqcap`(此处拆为独立模块:`scoring`、`rerank-freqcap`)
|
||||
- `content-repository` → 依赖 `db-design` 提供表结构与字段契约
|
||||
- `observability` → 被 `reco-engine` 与 `integration-api-worker` 共同使用
|
||||
|
||||
---
|
||||
|
||||
## 4. 主规范与子规范的职责划分
|
||||
|
||||
- `spec.md`:大需求的高层契约与总体约束(面向集成与验收)。
|
||||
- `modules/*/spec.md`:每块可落地实现的子模块规范(面向工程实现与单测)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 模块实现顺序(建议)
|
||||
|
||||
> 原则:先把“数据可查”打通,再实现“可排序”,最后做“可对外提供(API/任务)”与“可观测”。
|
||||
|
||||
1. **`modules/db-design/`(数据库设计与迁移)**
|
||||
- 交付:表结构 + Alembic 迁移可跑通;能插入/读取最小 `ContentProfile` 字段。
|
||||
2. **`modules/content-repository/`(数据访问层)**
|
||||
- 交付:按场景/回退层级拉候选、按 ID(`content_id` 自增 `int`)批量查;risk_flags 旧→新映射与默认值兜底。
|
||||
- 口径:suitability 读取层统一输出稳定结构;缺失时补齐“全 0.5”,key 集合固定为:
|
||||
- context:`family/work/relationship/friends/health`
|
||||
- need:`emotional_support/parenting_pressure/self_worth/anxiety_relief/rest_balance`
|
||||
3. **`modules/scoring/`(打分)**
|
||||
- 交付:`final_score` 与缺失字段保守策略(V1.2)实现;Push 的 `P_uncertainty`;Widget 情绪区间软降权。
|
||||
4. **`modules/rerank-freqcap/`(去重/重排/频控)**
|
||||
- 交付:基于传入历史集合的去重;Feed 的 MMR;Push/Widget 冷却窗口规则(先保证“同句不重复”)。
|
||||
5. **`modules/observability/`(可观测)**
|
||||
- 交付:统一 `meta` 结构与字段;能准确定位候选在何阶段被清空/回退。
|
||||
6. **`modules/reco-engine/`(引擎编排)**
|
||||
- 交付:串起候选→过滤→打分→重排→回退;输出 items+meta;在画像缺失/候选不足时仍稳定返回。
|
||||
7. **`modules/integration-api-worker/`(API + Celery 集成)**
|
||||
- 交付:FastAPI 路由 + Celery 任务都能调用同一引擎;相同输入下结果一致(忽略时间戳差异)。
|
||||
|
||||
287
spec_kit/Personalized Reco/spec.md
Normal file
287
spec_kit/Personalized Reco/spec.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# Personalized Reco|后端个性化推荐算法模块(解耦版)|Spec(索引版)
|
||||
|
||||
> 阶段:高层规范(spec)
|
||||
>
|
||||
> 目标:把「个性化推荐算法」做成后端**独立模块**,输入用户画像与曝光历史,模块内部从数据库拉取文案候选并排序,输出推荐句子(支持 Feed / Push / Widget 三种场景的可调参策略)。
|
||||
>
|
||||
> 依据:
|
||||
>
|
||||
> - `设计说明文档/個性化推薦算法規則.md`
|
||||
> - `设计说明文档/句子文案打分規則.md`(risk_flags 命名与语义必须严格对齐)
|
||||
> - `spec_kit/User Profile Scoring/spec.md`(用户画像输入契约,V1.2)
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与动机(摘要)
|
||||
|
||||
需要在后端实现一套跨场景一致、可调参、可观测、可回退的推荐流程,并将其做成独立模块以便复用(API / Celery 均可调用),降低 Push 等高风险场景的翻车概率。
|
||||
|
||||
已确认约束:
|
||||
|
||||
- 数据库当前未设计且为空:本需求内新增 DB 设计与迁移子模块。
|
||||
- risk_flags 等内容画像语义:严格按 `句子文案打分规则` 执行。
|
||||
- 推荐请求:由客户端在请求时传入 `user_profile` 与历史集合。
|
||||
- Widget 情绪区间(0.4~0.8):采用软降权。
|
||||
- Push/Widget:需要频控与冷却;且 API 与 Celery 两种调用方式都需要。
|
||||
|
||||
---
|
||||
|
||||
## 2. 目标(Goals)
|
||||
|
||||
- **模块解耦**:推荐逻辑以独立包/子模块方式存在,暴露稳定接口,不与 FastAPI 路由/ Celery 任务强绑定。
|
||||
- **统一 Pipeline**:Hard Filter → Soft Scoring → Rerank(多样性/新鲜度/疲劳),并支持候选不足的回退梯度(L0~L3)。
|
||||
- **输入明确**:输入用户画像(允许字段缺失)、已推荐的文案 ID、已触达/浏览的文案 ID(用于去重/疲劳/频控)。
|
||||
- **从 DB 取候选**:模块内部负责根据画像与场景召回候选,并从数据库读取文案画像/风险标记/元信息。
|
||||
- **输出可直接下发**:输出推荐句子(含 content_id、文本、排序分与必要的解释字段用于打点/调参)。
|
||||
- **可观测**:对候选规模、过滤原因、回退层级、 served_k 等关键指标提供统一事件结构,便于落点与报表。
|
||||
|
||||
---
|
||||
|
||||
## 3. 非目标(Non-goals)
|
||||
|
||||
- **不在本阶段定义**:内容生产(生成句子)、人工审核工作流、embedding/向量检索(V2 扩展)。
|
||||
- **不绑定具体表结构**:仅规定“必须能读到的字段契约”,具体表名/索引/迁移由后续 plan 细化。
|
||||
- **不负责推送/Widget 排程**:模块只负责“给定场景与约束,返回推荐结果”,调度由上层系统决定。
|
||||
|
||||
---
|
||||
|
||||
## 4. 术语与数据对象(Definitions,摘要)
|
||||
|
||||
### 4.1 场景(Scene)
|
||||
|
||||
- **feed**:探索优先(Exploration-first),返回序列 TopK(例如 30 条)。
|
||||
- **push**:精确与安全优先(Precision-first),通常返回 Top1(或 TopN 供上层挑选)。
|
||||
- **widget**:稳定与品牌一致性优先(Consistency-first),返回 Top1(每日一句)。
|
||||
|
||||
### 4.2 用户画像 U(UserProfile)
|
||||
|
||||
用户画像结构以客户端问卷输出为准(V1.2),详见:
|
||||
|
||||
- `spec_kit/User Profile Scoring/spec.md`(字段与缺失约定)
|
||||
- `modules/reco-engine/spec.md`(本模块如何消费画像与缺失判定)
|
||||
|
||||
### 4.3 文案画像 C(ContentProfile)
|
||||
|
||||
模块需能从 DB 读到(或通过视图/派生字段得到):
|
||||
|
||||
- **content_id**(稳定主键)
|
||||
- **text**(句子文本)
|
||||
- **stage**(或可推导的 stage 标签)
|
||||
- **emotion_score**(0~1 或标记为 general)
|
||||
- **need_suitability / context_suitability**(离散适配表/JSON)
|
||||
- **personalization_power**(0 / 0.5 / 1)
|
||||
- **risk_flags**(集合/数组/位图均可,语义一致即可)
|
||||
- (可选)**author_id / template_id / review_confidence**
|
||||
|
||||
---
|
||||
|
||||
## 5. 模块边界与接口(API Contract)
|
||||
|
||||
### 5.1 入口函数(推荐引擎接口)
|
||||
|
||||
推荐模块对外暴露一个主入口,建议形式(不限定语言结构,但需表达同等信息):
|
||||
|
||||
- **输入**:
|
||||
- `scene`: `"feed" | "push" | "widget"`
|
||||
- `user_profile`: `UserProfile`
|
||||
- `already_recommended_ids`: `list[int|str]`
|
||||
- `touched_or_viewed_ids`: `list[int|str]`(触达/浏览/点击等均归一为“已曝光”集合)
|
||||
- `k`: int(feed 默认 30,push/widget 默认 1)
|
||||
- `now`: 时间戳(用于时间衰减/冷却窗口)
|
||||
- `constraints`(可选):如黑名单 author/template、最大风险等级、频控窗口天数等
|
||||
- **输出**:
|
||||
- `items`: 推荐结果数组(长度 ≤ k)
|
||||
- `meta`: 本次推荐的统计与解释字段(用于打点/调参)
|
||||
|
||||
### 5.2 输出结果项(RecommendedItem)
|
||||
|
||||
每条推荐至少包含:
|
||||
|
||||
- `content_id`
|
||||
- `text`
|
||||
- `final_score`
|
||||
- `fallback_level_final`
|
||||
- `explanations`(轻量可选):如命中 need/context/stage、是否 general、被降个性化的原因(缺失/低置信度/回退)
|
||||
|
||||
### 5.3 数据访问抽象(Repository / Gateway)
|
||||
|
||||
为实现解耦,推荐模块**不得**直接依赖 FastAPI 的 `Depends`;应通过注入的 `ContentRepository` 读取数据:
|
||||
|
||||
- `fetch_candidates(scene, user_profile, limit, fallback_level) -> list[ContentProfile]`
|
||||
- `fetch_contents_by_ids(ids) -> list[ContentProfile]`(用于补全曝光历史的画像信息时可用)
|
||||
|
||||
实现层可使用 `AsyncSession`(SQLAlchemy)完成具体查询,但算法层不感知 ORM/SQL。
|
||||
|
||||
---
|
||||
|
||||
## 6. 推荐流程(统一 Pipeline)
|
||||
|
||||
本节是工程必须遵守的“骨架”,参数可按场景配置。
|
||||
|
||||
### 6.1 Candidate Generation(候选集生成)
|
||||
|
||||
核心要求:
|
||||
|
||||
- 依据 `U` 召回一个候选池:匹配 need/context/stage 的内容 + 一部分通用内容(general)。
|
||||
- 若问卷允许跳过导致字段缺失(need/context/emotion_score 缺失),候选池需自动提高通用安全内容占比,并视为至少进入 **L1**(限制 `personalization_power ≤ 0.5`)。
|
||||
|
||||
### 6.2 Hard Filter(硬性过滤)
|
||||
|
||||
基于 `risk_flags` 与产品规则做剔除(说明文档 3.x):
|
||||
|
||||
- `block_health_medical`:全场景硬过滤。
|
||||
- `U.stage.unknown=1`:过滤 `unsafe_for_stage_unknown`。
|
||||
- `U.stage.parenting=1`:过滤 `unsafe_for_stage_parenting`。
|
||||
- `U.emotion_score≤0.2`:过滤 `unsafe_for_emotion_low`。
|
||||
- 跨维度规则:`U.stage.unknown=1` 且 `need_suitability[parenting_pressure]=1` 且 `personalization_power=1` → 禁推。
|
||||
|
||||
Hard Filter 后需输出 `candidate_pool_size_after_hard_filter` 以及若清空的 `empty_reason`。
|
||||
|
||||
### 6.3 Soft Scoring(软性打分)
|
||||
|
||||
采用说明文档的线性加权结构:
|
||||
|
||||
最终分:
|
||||
|
||||
\[
|
||||
final\_score(U,C_i)=\mathbb{I}[pass]\times\Big(S_{core}+S_{personal}+S_{fresh}-P_{fatigue}-P_{repeat}-P_{risk}-P_{uncertainty}\Big)
|
||||
\]
|
||||
|
||||
其中
|
||||
|
||||
\[
|
||||
S_{core}=w_{need}S_{need}+w_{emotion}S_{emotion}+w_{stage}S_{stage}+w_{context}S_{context}
|
||||
\]
|
||||
|
||||
缺失字段的保守计算(说明文档 V1.2,必须实现):
|
||||
|
||||
- `U.need` 缺失 → `S_need = 0.5`
|
||||
- `U.context` 缺失 → `S_context = 0.5`
|
||||
- `U.emotion_score` 缺失 → `S_emotion = 0.8`(general 仍为 0.8)
|
||||
|
||||
个性化加成(必须受回退梯度约束):
|
||||
|
||||
\[
|
||||
S_{personal}=\alpha \cdot personalization\_power \cdot \max(S_{need}, S_{context})
|
||||
\]
|
||||
|
||||
不确定性惩罚(Push 建议默认开启,模块需支持开关):
|
||||
|
||||
\[
|
||||
P_{uncertainty}=\beta \cdot (1-conf_U)\cdot(1-conf_{C_i})\cdot personalization\_power
|
||||
\]
|
||||
|
||||
### 6.4 Rerank(重排:多样性/新鲜度/疲劳)
|
||||
|
||||
最小要求:
|
||||
|
||||
- **去重**:排除 `already_recommended_ids` 与 `touched_or_viewed_ids`(同一句不重复)。
|
||||
- **多样性**:作者/模板/标签多样性;Feed 场景建议使用 MMR(说明文档 4.5)。
|
||||
- **频控与冷却**(Push/Widget 必做):同句/同作者/同模板在窗口 X 天内不重复(具体 X 由 plan 定参数)。
|
||||
|
||||
---
|
||||
|
||||
## 7. 回退策略(Fallback Ladder)
|
||||
|
||||
候选不足或过滤/频控导致 served_k 过少时,必须按梯度回退,并输出最终 `fallback_level_final`:
|
||||
|
||||
- **L0(正常)**:按场景默认召回配比。
|
||||
- **L1(放宽匹配)**:need/context 命中阈值放宽;`personalization_power ≤ 0.5`。
|
||||
- **L2(回退通用池)**:增加 general 低风险句;`personalization_power = 0`。
|
||||
- **L3(兜底安全池)**:仅从通用安全句库/白名单池抽取。
|
||||
|
||||
规则:
|
||||
|
||||
- 回退时必须“降个性化与降风险”,尤其 Push。
|
||||
- 每次回退都要重新统计候选规模,并记录触发原因(如 `hard_filter_all` / `freqcap_all` / `pool_empty`)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 场景默认参数(V1 建议)
|
||||
|
||||
### 8.1 Feed(Exploration-first)
|
||||
|
||||
- 候选配比:匹配 60% + 通用 30% + 探索 10%
|
||||
- 权重建议:`w_need=0.35, w_emotion=0.20, w_stage=0.15, w_context=0.30`
|
||||
- 序列:Top1 最高分,后续使用 MMR(λ=0.7)
|
||||
|
||||
### 8.2 Push(Precision & Safety-first)
|
||||
|
||||
- 候选配比:need 强命中 70% + emotion 命中 20% + 通用安全 10%
|
||||
- 权重建议:`w_need=0.45, w_emotion=0.35, w_stage=0.15, w_context=0.05`
|
||||
- 默认启用 `P_uncertainty`;当 `fallback_level_final>0` 或画像缺失/低置信度时自动降个性化
|
||||
|
||||
### 8.3 Widget(Consistency & Brand-first)
|
||||
|
||||
- 候选池:以 general + `personalization_power≤0.5` 为主
|
||||
- 权重建议:`w_need=0.25, w_emotion=0.25, w_stage=0.30, w_context=0.20`
|
||||
- 情绪区间约束:建议 `emotion_score` 限制在 0.4~0.8(过低/过高降权或过滤,具体由 plan 定)
|
||||
|
||||
---
|
||||
|
||||
## 9. 数据库与存储契约(DB Contract)
|
||||
|
||||
模块需要 DB 提供以下能力(不限定表名实现方式):
|
||||
|
||||
- **按标签召回**:能按 need/context/stage/general、personalization_power、risk_flags 等条件筛选候选。
|
||||
- **按 ID 批量拉取**:用于补全曝光历史或在 rerank 时补字段。
|
||||
- **安全池支持**:L3 兜底必须能拉到一批“通用安全句”(白名单/低风险集合)。
|
||||
|
||||
性能要求(V1):
|
||||
|
||||
- 单次推荐(k<=30)应避免 N+1 查询;候选批量拉取 + 内存打分。
|
||||
- 查询需可加索引:`stage`、`general`、`personalization_power`、(以及用于召回的标签字段)。
|
||||
|
||||
---
|
||||
|
||||
## 10. 可观测性(事件与指标)
|
||||
|
||||
每次推荐(Feed session / 每次 Push / 每日 Widget)必须能产出以下字段(由上层统一打点即可):
|
||||
|
||||
- `candidate_pool_size_raw`
|
||||
- `candidate_pool_size_after_hard_filter`
|
||||
- `candidate_pool_size_after_dedup`
|
||||
- `candidate_pool_size_after_freqcap`
|
||||
- `fallback_level_final`
|
||||
- `served_k`
|
||||
- `empty_reason`(served_k=0 时必填)
|
||||
|
||||
建议额外输出(用于调参/排查):
|
||||
|
||||
- `scene`
|
||||
- `conf_U`
|
||||
- `missing_fields`(need/context/emotion 的缺失情况)
|
||||
- `risk_filtered_count_by_flag`(可选聚合)
|
||||
|
||||
---
|
||||
|
||||
## 11. 安全与合规
|
||||
|
||||
- 严禁返回 Hard Filter 禁推内容。
|
||||
- 对 Push 场景,默认倾向“低风险 + 低个性化强度”;当画像不确定或字段缺失时必须进一步降个性化。
|
||||
- 不在仓库写入真实数据库账号密码;连接串统一从 `DATABASE_URL` 环境变量读取(已由 `server/app/core/config.py` 支持)。
|
||||
|
||||
---
|
||||
|
||||
## 12. 验收标准(Acceptance Criteria)
|
||||
|
||||
- 能以统一接口在三种场景运行:输入画像 + 曝光集合 → 输出 TopK 句子。
|
||||
- 画像字段缺失时仍能产出排序,并触发降个性化策略(至少 L1),不会报错。
|
||||
- Hard Filter 规则生效:命中 `block_health_medical` 等风险标记的内容绝不出现在输出中。
|
||||
- 候选不足时会逐级回退并输出 `fallback_level_final`,不会出现无输出且无原因字段的情况。
|
||||
- 输出包含用于上层打点的 meta 统计字段,便于观测候选规模与回退率。
|
||||
|
||||
---
|
||||
|
||||
## 13. 子模块规范索引(modules/)
|
||||
|
||||
> 子模块规范用于实现拆分;本文件保留大需求高层约束与对外契约摘要。
|
||||
|
||||
- `modules/db-design/spec.md`:数据库设计与迁移(当前库为空,本需求内补齐)
|
||||
- `modules/content-repository/spec.md`:数据访问层(按画像与场景拉取候选、按 ID 批量查)
|
||||
- `modules/reco-engine/spec.md`:推荐引擎编排(候选→过滤→打分→重排→回退)
|
||||
- `modules/scoring/spec.md`:打分与惩罚项(严格对齐规则文档;含缺失字段保守策略)
|
||||
- `modules/rerank-freqcap/spec.md`:重排/去重/频控(Feed 的 MMR;Push/Widget 冷却)
|
||||
- `modules/observability/spec.md`:可观测事件结构与指标(candidate_pool_size_* 等)
|
||||
- `modules/integration-api-worker/spec.md`:API 与 Celery 集成(请求入参、响应、任务封装)
|
||||
|
||||
Reference in New Issue
Block a user