185 lines
9.1 KiB
Markdown
185 lines
9.1 KiB
Markdown
# User Profile Scoring(技术计划 · V1.2)
|
||
|
||
## 1. 计划目标
|
||
|
||
在**客户端(TypeScript)**实现一个可复用的“用户画像计算”模块:
|
||
|
||
- **输入**:Onboarding 问卷答案(每题**可跳过**;字段可缺失/为 `null`)
|
||
- **输出**:严格符合 `spec_kit/User Profile Scoring/spec.md`(V1.2)的用户画像对象(含 `profile_answered`、四维度字段、`profile_confidence`)
|
||
- **硬规则**:输出一份“下游可直接执行/可观测”的硬规则结果,用于推荐/Push/Widget 统一复用(对齐 V1.2 规则语义)
|
||
- **约束**:不抛异常、不报错;非法值按**跳过**处理
|
||
|
||
## 2. 默认技术决策(本计划采用)
|
||
|
||
- **实现位置**:`client/src/features/userProfileScoring/`
|
||
- **语言**:TypeScript(纯函数为主,便于测试与跨端复用)
|
||
- **时间与可测性**:允许调用方注入 `generatedAt/now`,默认使用 `new Date()`,保证可回归测试
|
||
- **规则对齐来源**:
|
||
- 画像与置信度:`设计说明文档/客戶端問卷打分規則.md`(V1.2)
|
||
- 下游过滤语义:`设计说明文档/個性化推薦算法規則.md`(Hard Filter / `risk_flags` 语义)
|
||
|
||
## 3. 输入与输出契约(V1.2,含跳过)
|
||
|
||
### 3.1 输入:问卷答案(允许部分缺失/跳过)
|
||
|
||
定义输入类型 `QuestionnaireAnswersV1_2`(所有字段可选;也允许显式 `null`):
|
||
|
||
- `mom_stage?: "expecting" | "parenting" | "unknown" | null`
|
||
- `emotion?: "low" | "overwhelmed" | "tired" | "neutral" | "calm" | "joyful" | null`
|
||
- `context?: "family" | "work" | "relationship" | "friends" | "health" | null`
|
||
- `need?: "emotional_support" | "parenting_pressure" | "self_worth" | "anxiety_relief" | "rest_balance" | null`
|
||
|
||
非法值处理策略:
|
||
|
||
- 字段存在但值不在枚举内:按**跳过**处理(归一化为 `undefined`),不抛错
|
||
|
||
### 3.2 输出:用户画像(严格对齐 spec.md V1.2)
|
||
|
||
输出 `UserProfileV1_2`,满足 `spec.md` 的字段与取值:
|
||
|
||
- `profile_version: "v1.2"`
|
||
- `profile_source: "questionnaire"`
|
||
- `profile_generated_at: string`(ISO8601)
|
||
- `profile_confidence: number`(0–1)
|
||
- `profile_answered: { stage: boolean; emotion: boolean; context: boolean; need: boolean }`
|
||
- `stage: { expecting?: 0|1; parenting?: 0|1; unknown: 0|1 }`
|
||
- 注:题目跳过时按安全策略输出 `unknown=1`
|
||
- `emotion_score: number | null`
|
||
- `context: Record<string, 1>`(稀疏 one-hot;题目跳过时为 `{}`)
|
||
- `need: Record<string, 1>`(稀疏 one-hot;题目跳过时为 `{}`)
|
||
|
||
### 3.3 额外输出:硬规则(供下游直接执行/打点)
|
||
|
||
`spec.md` 要求“向下游暴露可直接执行的硬规则结果”,但不强制字段结构。本计划采用以下扩展字段(不破坏 `spec.md` 核心结构):
|
||
|
||
- `rule_hits: string[]`
|
||
- 仅用于可观测/回归测试(例如 `["unsafe_for_emotion_low"]`)
|
||
- `hard_rules: { ... }`
|
||
- `forbidden_risk_flags: string[]`(对齐内容侧/推荐侧 `risk_flags` 名称)
|
||
- `forbidden_content_predicates: Array<{ id: string; when_user: object; forbid_content: object }>`
|
||
- 用于表达“需要同时看用户与内容字段才能执行”的规则(例如 unknown + 育儿压力 + 强个性化)
|
||
|
||
## 4. 跳过题目的处理策略(V1.2:安全优先 + 可观测)
|
||
|
||
> 目标:题目跳过时不报错;画像仍可计算;并通过 `conf_U` 与下游 `P_uncertainty` 思路自动降个性化/降风险。
|
||
|
||
- **mom_stage 跳过**:
|
||
- 输出:`stage.unknown = 1`
|
||
- `profile_answered.stage = false`
|
||
- **emotion 跳过**:
|
||
- 输出:`emotion_score = null`(不强行填 0.6,避免制造“伪精确画像”)
|
||
- `profile_answered.emotion = false`
|
||
- **context 跳过**:
|
||
- 输出:`context = {}`
|
||
- `profile_answered.context = false`
|
||
- **need 跳过**:
|
||
- 输出:`need = {}`
|
||
- `profile_answered.need = false`
|
||
|
||
## 5. 画像计算规则(映射 + 置信度 + 硬规则)
|
||
|
||
### 5.1 映射规则(对齐 `客戶端問卷打分規則.md` V1.2)
|
||
|
||
- `mom_stage` → `stage.*`(one-hot;若跳过则 `unknown=1`)
|
||
- `emotion` → `emotion_score`:
|
||
- `low=0.0`,`overwhelmed=0.2`,`tired=0.4`,`neutral=0.6`,`calm=0.8`,`joyful=1.0`
|
||
- 若跳过:`emotion_score = null`
|
||
- `context` → 稀疏 one-hot:例如 `work` → `{ work: 1 }`;若跳过:`{}`
|
||
- `need` → 稀疏 one-hot:例如 `rest_balance` → `{ rest_balance: 1 }`;若跳过:`{}`
|
||
|
||
### 5.2 置信度(`profile_confidence / conf_U`)计算(V1.2)
|
||
|
||
时间衰减(与 `spec.md` 一致):
|
||
|
||
- 0–7 天:`conf_time = 1.0`
|
||
- 7–30 天:线性衰减到 `0.7`
|
||
- 30 天以上:`conf_time = 0.5`
|
||
|
||
作答完整度因子(对齐 `客戶端問卷打分規則.md` V1.2):
|
||
|
||
- `answered_count = Σ I[profile_answered.* = true]`,`total_questions = 4`
|
||
- `completion = answered_count / total_questions`
|
||
- `completion_factor = 0.5 + 0.5 * completion`
|
||
- `profile_confidence = clamp(conf_time * completion_factor, 0.2, 1.0)`
|
||
|
||
### 5.3 硬规则输出(对齐 V1.2)
|
||
|
||
本模块输出 `rule_hits` 与 `hard_rules`,用于下游先过滤、后打分:
|
||
|
||
1) **按用户状态映射到内容 `risk_flags` 的禁推**
|
||
|
||
- 若 `stage.unknown = 1`:
|
||
- `rule_hits += ["unsafe_for_stage_unknown"]`
|
||
- `hard_rules.forbidden_risk_flags += ["unsafe_for_stage_unknown"]`
|
||
- 若 `stage.parenting = 1`:
|
||
- `rule_hits += ["unsafe_for_stage_parenting"]`
|
||
- `hard_rules.forbidden_risk_flags += ["unsafe_for_stage_parenting"]`
|
||
- 若 `emotion_score !== null` 且 `emotion_score <= 0.2`:
|
||
- `rule_hits += ["unsafe_for_emotion_low"]`
|
||
- `hard_rules.forbidden_risk_flags += ["unsafe_for_emotion_low"]`
|
||
|
||
2) **跨维度产品规则(需要同时看用户与内容字段)**
|
||
|
||
- 若 `stage.unknown = 1`:
|
||
- 增加一个可执行谓词,表达“禁推 育儿压力且高个性化内容”:
|
||
- `when_user`: `{ stage_unknown: true }`
|
||
- `forbid_content`: `{ need: "parenting_pressure", personalization_power: 1 }`
|
||
- 该规则由下游在内容侧字段存在时执行(对齐 `spec.md` 7 与 `客戶端問卷打分規則.md` 6 的 V1.2 口径)
|
||
|
||
> 注:本模块不判断内容的 `personalization_power`(属于内容侧),只输出“可执行条件”。
|
||
|
||
## 6. 文件与目录规划(客户端)
|
||
|
||
在 `client/src/` 新增/调整:
|
||
|
||
- `client/src/features/userProfileScoring/types.ts`
|
||
- `QuestionnaireAnswersV1_2` / `UserProfileV1_2` / 枚举联合类型
|
||
- `client/src/features/userProfileScoring/scoring.ts`
|
||
- `buildUserProfileFromQuestionnaire(answers, options): UserProfileV1_2 & { rule_hits; hard_rules }`
|
||
- `computeTimeConfidence(generatedAt, now): number`
|
||
- `normalizeAnswers(raw): QuestionnaireAnswersV1_2`(非法值 → 跳过)
|
||
- `computeProfileAnswered(normalized): profile_answered`
|
||
- `computeProfileConfidence(conf_time, profile_answered): number`
|
||
- `client/src/features/userProfileScoring/index.ts`
|
||
- 对外导出(供 Onboarding/推荐/Push 调用)
|
||
|
||
## 7. 测试计划(客户端可回归)
|
||
|
||
若工程未提供 `test` 脚本,建议补齐最小单元测试能力(只测纯函数):
|
||
|
||
- 新增 `vitest`(或 `jest`)为 devDependencies,并在 `client/package.json` 增加 `test` 脚本
|
||
- 为 `buildUserProfileFromQuestionnaire` 增加用例(与 `spec.md` 第 10 章一致):
|
||
- 映射正确性(完整作答)
|
||
- 置信度时间衰减(0–7/7–30/30+)
|
||
- 硬规则触发(`emotion_score<=0.2`、`stage.unknown`、`stage.parenting`)
|
||
- 跳过场景(V1.2):
|
||
- 全部跳过(输入 `{}`):`profile_answered` 全 false;`stage.unknown=1`;`emotion_score=null`;`context/need={}`;`profile_confidence=0.5`(当 conf_time=1 时)
|
||
- 仅回答 1 题:验证 `completion_factor` 与 clamp(最小 0.2)逻辑正确
|
||
|
||
## 8. 实施步骤
|
||
|
||
1. 在 `client/src/features/userProfileScoring/` 增加类型定义与常量映射表(V1.2)
|
||
2. 实现答案归一化(非法值按跳过;支持 `null/undefined`)
|
||
3. 实现画像生成主函数(含 `profile_answered`、元信息生成、置信度计算)
|
||
4. 实现硬规则命中与结构化输出(`forbidden_risk_flags` + `forbidden_content_predicates`)
|
||
5. 补齐最小测试框架与单测(仅纯函数)
|
||
6. Onboarding 流程完成处接入(后续任务):问卷提交后生成画像对象;持久化/上传由上层决定
|
||
|
||
## 9. 验收标准
|
||
|
||
- 任意问卷答案输入(含任意题跳过/非法值),都能生成符合 `spec.md`(V1.2)结构的用户画像对象(不抛错)
|
||
- 输出包含:
|
||
- V1.2 元信息(`profile_version/source/generated_at/confidence/profile_answered`)
|
||
- 四维度字段(`stage/emotion_score/context/need`;允许跳过后的 `null/{}`)
|
||
- `rule_hits` 与 `hard_rules`(供下游执行 Hard Filter 与打点)
|
||
- 置信度:
|
||
- 时间衰减符合 `spec.md` 6.2
|
||
- 跳过越多 `profile_confidence` 越低(按 V1.2 完整度因子),并被 clamp 到 `[0.2, 1.0]`
|
||
|
||
## 10. 后续演进(不阻塞 V1.2)
|
||
|
||
- 支持 `context/need` 多选(输出仍为 one-hot,可多个 key=1)
|
||
- 引入 `profile_source="mixed"`:行为信号修正 `emotion_score` 或补全缺失维度(保留 questionnaire 原始画像用于可观测)
|
||
- 将硬规则输出进一步与推荐系统规则引擎 schema 对齐(使 `forbidden_content_predicates` 可直接落入规则引擎)
|
||
|