fix:更新算法

This commit is contained in:
吕新雨
2026-02-02 11:22:35 +08:00
parent 814b96edb6
commit 58d17fc39f
27 changed files with 3286 additions and 62 deletions

View 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 PKFK → contents.content_idON 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 NULLFK → contents.content_idON 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 个 keyfamily/work/relationship/friends/health值为 0/0.5/1
- `content_profiles.need_suitability_json`(必须包含 5 个 keyemotional_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 缺失时依然成立。

View File

@@ -0,0 +1,91 @@
# 子模块DB Design & Migrations数据库设计与迁移Spec
## 1. 目标描述
在当前“数据库尚未设计且为空”的前提下,为个性化推荐提供最小可用的数据存储与索引能力:
- 存储文案及其内容画像Content ProfileCᵢ
- 能按画像条件进行候选召回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 兜底候选集(不依赖用户画像)。

View 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 分钟内跑通迁移 + 插入 + 查询。