# User Profile Scoring(任务清单 · V1.2) > 依据:`spec_kit/User Profile Scoring/plan.md`(V1.2) > > 规则目标:在客户端实现“问卷答案(可跳过)→ 用户画像(含 profile_answered/confidence)+ 硬规则输出”,供推荐/Push/Widget 复用。 ## 任务状态说明 - `[ ]`:未开始 - `[~]`:进行中 - `[x]`:已完成 - `[-]`:已取消/不做 --- ## A. 代码实现(客户端 TypeScript 模块) ### A1. 建立目录与导出入口 - [x] 在 `client/src/features/` 下新增目录 `userProfileScoring/` - **验收**:目录存在,且后续文件均放入该目录 - [x] 新建 `client/src/features/userProfileScoring/index.ts` - **内容**:对外导出主函数与类型(避免上层直接深路径 import) - **验收**:上层可仅从 `.../userProfileScoring` 引入 ### A2. 定义类型与枚举(V1.2) - [x] 新建 `client/src/features/userProfileScoring/types.ts` - **定义输入类型**:`QuestionnaireAnswersV1_2` - 字段允许 `undefined`/`null`:`mom_stage/emotion/context/need` - **定义输出类型**:`UserProfileV1_2` - 固定 `profile_version: "v1.2"`、`profile_source: "questionnaire"` - `profile_answered`:`{ stage; emotion; context; need }` - `emotion_score: number | null` - `context/need: Record`(稀疏 one-hot;跳过为 `{}`) - **定义扩展输出类型**:`UserProfileV1_2_Extended` - `rule_hits: string[]` - `hard_rules: { forbidden_risk_flags: string[]; forbidden_content_predicates: ... }` - **验收**:类型字段与 `spec_kit/User Profile Scoring/spec.md`(V1.2)一致 ### A3. 实现答案归一化(非法值按跳过) - [x] 新建 `client/src/features/userProfileScoring/scoring.ts` - [x] 实现 `normalizeAnswers(raw): QuestionnaireAnswersV1_2` - **规则**: - 值不在枚举内 → 归一化为 `undefined` - `null` 保留为 `null`(表示显式跳过/无值) - **验收**:任意输入不抛错;输出只包含合法枚举/`undefined`/`null` ### A4. 计算 profile_answered(V1.2) - [x] 实现 `computeProfileAnswered(normalized)`: - `stage = (mom_stage !== undefined && mom_stage !== null)` - `emotion/context/need` 同理 - **验收**:与 `spec.md` 6.1.1 一致;可区分“缺失/跳过”与“明确作答” ### A5. 计算时间衰减 conf_time - [x] 实现 `computeTimeConfidence(generatedAt, now): number` - **规则**(与 `spec.md` 6.2 一致): - 0–7 天:1.0 - 7–30 天:线性衰减到 0.7 - 30 天以上:0.5 - **验收**:给定固定时间输入,输出符合分段规则(允许浮点误差) ### A6. 计算 profile_confidence(V1.2 完整度因子 + clamp) - [x] 实现 `computeProfileConfidence(conf_time, profile_answered): number` - **规则**(与 `设计说明文档/客戶端問卷打分規則.md` V1.2 一致): - `answered_count = Σ I[profile_answered.* = true]` - `completion = answered_count / 4` - `completion_factor = 0.5 + 0.5 * completion` - `conf = clamp(conf_time * completion_factor, 0.2, 1.0)` - **验收**: - 全部跳过且 `conf_time=1` → `conf=0.5` - 全部作答且 `conf_time=1` → `conf=1.0` - 极端情况下不会低于 0.2 / 高于 1.0 ### A7. 生成四维度画像字段(含跳过策略) - [x] 实现 `buildUserProfileFromQuestionnaire(answers, options)` - **元信息**: - `profile_version="v1.2"` - `profile_source="questionnaire"` - `profile_generated_at`:允许注入;默认 `new Date().toISOString()` - **stage**: - 若 `mom_stage` 缺失/为 `null`:按安全策略输出 `stage.unknown=1` - 若为枚举:对应 one-hot - **emotion_score**: - 若跳过:`null` - 否则按映射表输出数值 - **context/need**: - 若跳过:输出 `{}` - 否则输出稀疏 one-hot(例如 `{ work: 1 }`) - **验收**:与 `spec.md` 4.2/5.x/6.x 一致 ### A8. 硬规则与可观测输出(rule_hits / hard_rules) - [x] 在 `buildUserProfileFromQuestionnaire` 内生成 `rule_hits` 与 `hard_rules` - **risk_flags 映射规则**(与 `spec.md` 7 一致): - `stage.unknown=1` → `unsafe_for_stage_unknown` - `stage.parenting=1` → `unsafe_for_stage_parenting` - `emotion_score !== null && emotion_score<=0.2` → `unsafe_for_emotion_low` - **跨维度规则谓词**(与 `spec.md` 7、客户端规则 V1.2 一致): - 若 `stage.unknown=1`:追加谓词 - `id`: 建议 `unknown_block_parenting_pressure_personalized` - `when_user`: `{ stage_unknown: true }` - `forbid_content`: `{ need: "parenting_pressure", personalization_power: 1 }` - **验收**: - 输出结构稳定 - 不依赖内容侧字段(本模块只输出“可执行条件”) --- ## B. 单元测试(只测纯函数) > 若当前工程暂不引入测试框架,可先把测试用例写成“可执行脚本/最小断言”;但建议最终落地 `vitest` 或 `jest`。 ### B1. 建立测试框架(可选但建议) - [x] 若 `client/` 尚无测试:引入 `vitest`(或 `jest`)并添加 `test` 脚本 - **验收**:能在本机运行测试并输出结果 ### B2. 覆盖 spec.md 的关键用例 - [x] 用例:完整作答映射正确(对应 `spec.md` 用例 A/B) - [x] 用例:时间衰减(对应 `spec.md` 用例 C/D/E) - [x] 用例:全部跳过 `{}`(对应 `spec.md` 用例 A2) - 断言:`profile_answered` 全 false;`stage.unknown=1`;`emotion_score=null`;`context/need={}`;`profile_confidence=0.5`(当 conf_time=1) - [x] 用例:硬规则命中 - `emotion_score<=0.2` → 含 `unsafe_for_emotion_low` - `stage.parenting=1` → 含 `unsafe_for_stage_parenting` - `stage.unknown=1` → 含 `unsafe_for_stage_unknown` 且带跨维度谓词 --- ## C. 接入与文档回写(后续任务) - [ ] Onboarding 提交问卷处接入本模块(调用 `buildUserProfileFromQuestionnaire`) - **验收**:提交后能生成并持久化/上传画像对象(具体存储策略由上层决定) - [ ] 在推荐/Push/Widget 侧消费 `hard_rules` - **验收**:Hard Filter 在打分前执行;且能使用跨维度谓词表达 unknown+育儿压力强个性化禁推