# User Profile Scoring（高层规范）

## 1. 背景与目标

本模块用于将 Onboarding 问卷答案转换为标准化、可计算、可版本化的**用户画像（User Profile）**，供其他模块（个性化推荐 / Push 推送排序 / 内容过滤）统一调用。

### 1.1 问卷信息（V1.2，供外部 UI 实现；支持可跳过）

本模块**不接入 UI**，仅定义“问卷答案 → 用户画像”的规则。外部 UI（或服务端）只需在用户完成 Onboarding 问卷后，按以下题目将答案编码为枚举值并调用本模块。

V1.2 题目集合（每题**可跳过**；作答时为单选；**中英对齐**）：

#### Q1 母职阶段（`mom_stage`）

- 中文：你处于哪个母职阶段？
- English: Which stage of motherhood are you in?

| UI 展示（中文） | UI 展示（English） | 枚举值（提交给本模块） |
| --- | --- | --- |
| 备孕 / 怀孕 / 准备迎接宝宝 | Pregnant / Preparing | `expecting` |
| 已经在育儿阶段 | Parenting | `parenting` |
| 不想说 / 不希望被母职身份定义 | Prefer not to say | `unknown` |

#### Q2 当下情绪（`emotion`）

- 中文：你现在感觉如何？
- English: How are you feeling right now?

| UI 展示（中文） | UI 展示（English） | 枚举值（提交给本模块） |
| --- | --- | --- |
| 低落 | Low | `low` |
| 过载 / 不堪重负 | Overwhelmed | `overwhelmed` |
| 疲惫 | Tired | `tired` |
| 还好 / 一般 | Okay | `neutral` |
| 平静 | Calm | `calm` |
| 愉悦 | Joyful | `joyful` |

#### Q3 主要触发场景（`context`）

- 中文：这些感受主要受到什么影响？
- English: What’s been influencing how you feel?

| UI 展示（中文） | UI 展示（English） | 枚举值（提交给本模块） |
| --- | --- | --- |
| 家庭 | Family | `family` |
| 工作或学习 | Work or study | `work` |
| 亲密关系 | Relationship | `relationship` |
| 朋友 | Friends | `friends` |
| 健康 | Health | `health` |

#### Q4 最需要的支持（`need`）

- 中文：你现在最需要哪一种支持？
- English: What kind of support do you need most right now?

| UI 展示（中文） | UI 展示（English） | 枚举值（提交给本模块） |
| --- | --- | --- |
| 情绪支持 | Emotional support | `emotional_support` |
| 育儿压力 | Parenting pressure | `parenting_pressure` |
| 自我价值 | Self-worth | `self_worth` |
| 缓解焦虑 | Anxiety relief | `anxiety_relief` |
| 休息与平衡 | Rest & balance | `rest_balance` |

说明：

- 外部 UI 可自由决定视觉样式，但建议文案**中英成对出现**并与上表一致；无论文案如何变化，**答案编码必须与上述枚举一致**，以保证跨端一致性与可回归测试。
- 每一题允许用户**跳过不答**；当题目被跳过时，外部调用可选择“不传该字段”或传 `null`（以具体接口约定为准），本模块将按第 6.1.1 节输出 `profile_answered` 并生成保守画像。
- 后续若扩展为多选（例如 `context/need` 多选），应遵循第 8 节兼容策略，保持 V1 输出结构可向后兼容。

该模块的设计原则：

- **不做心理诊断**：只描述「此刻允许被如何对待」
- 区分 **身份（离散）** 与 **状态（连续）**
- 允许「通用」与「高个性化」内容并存，并通过置信度控制“个性化力度”

来源规则文档：`设计说明文档/客戶端問卷打分規則.md`（V1 / V1.1 / V1.2）

## 2. 模块定位与边界

### 2.1 负责（In Scope）

- 定义**统一画像结构**（字段、类型、取值范围）
- 定义从问卷答案到画像的**映射规则**（V1）
- 生成画像的**元信息**（V1.1：版本、来源、生成时间、置信度）
- 提供最小可复用的“硬规则”输出（用于下游模块做禁推/降风险）

### 2.2 不负责（Out of Scope）

- 不负责具体推荐算法实现（相似度计算、排序、探索/利用策略等）
- 不负责内容标签的生成与管理（内容画像 Cᵢ 的维护属于内容/推荐模块）
- 不负责长期行为信号融合（行为修正可在后续版本扩展）

## 3. 关键概念

- **用户画像 U**：由问卷生成，描述用户阶段、状态与当下需求
- **内容画像 Cᵢ**：内容侧标签/强度（由内容模块维护）
- **置信度 conf_U**：画像可靠程度（用于推送等高风险场景的降个性化/降风险）

## 4. 输入与输出（对外接口）

### 4.1 输入（问卷答案）

V1.2：每题**可跳过**；作答时为**单选**：

- `mom_stage?`：`expecting | parenting | unknown`
- `emotion?`：`low | overwhelmed | tired | neutral | calm | joyful`
- `context?`：`family | work | relationship | friends | health`
- `need?`：`emotional_support | parenting_pressure | self_worth | anxiety_relief | rest_balance`

> 说明：
>
> - 带 `?` 表示该字段允许缺失（题目跳过）。
> - 多选 `context/need` 可在后续版本扩展，但 V1 输出结构需保持可兼容升级。

### 4.2 输出（用户画像）

输出为一个 JSON 对象（或等价的 TypeScript 类型），包含：

- **元信息（V1.2）**
  - `profile_version`: string（如 `"v1.2"`）
  - `profile_source`: `"questionnaire"`（后续可扩展 `"behavior"` / `"mixed"`）
  - `profile_generated_at`: string（ISO8601 时间戳）
  - `profile_confidence`: number（0–1）
  - `profile_answered`: object（标记各题是否作答）
- **四个维度（V1，V1.2 允许缺失/跳过）**
  - `stage`: one-hot（若题目跳过，按安全策略输出 `unknown=1`）
  - `emotion_score`: number（0–1）或 `null`（题目跳过）
  - `context`: one-hot 或空对象 `{}`（题目跳过）
  - `need`: one-hot 或空对象 `{}`（题目跳过）

推荐的最小输出示例（结构示意）：

```json
{
  "profile_version": "v1.2",
  "profile_source": "questionnaire",
  "profile_generated_at": "2026-01-30T00:00:00Z",
  "profile_confidence": 1.0,
  "profile_answered": { "stage": true, "emotion": true, "context": true, "need": true },
  "stage": { "expecting": 0, "parenting": 1, "unknown": 0 },
  "emotion_score": 0.4,
  "context": { "work": 1 },
  "need": { "rest_balance": 1 }
}
```

题目跳过时的最小输出示例（V1.2）：

```json
{
  "profile_version": "v1.2",
  "profile_source": "questionnaire",
  "profile_generated_at": "2026-01-30T00:00:00Z",
  "profile_confidence": 0.5,
  "profile_answered": { "stage": false, "emotion": false, "context": false, "need": false },
  "stage": { "unknown": 1 },
  "emotion_score": null,
  "context": {},
  "need": {}
}
```

## 5. 画像字段定义（V1）

### 5.1 mom_stage（母职阶段，离散）

映射（one-hot）：

- Pregnant / Preparing → `stage.expecting = 1`
- Parenting → `stage.parenting = 1`
- Prefer not to say → `stage.unknown = 1`

语义约束：

- `unknown` 的语义是：**不希望被母职身份定义**（不是“没有孩子”）
  - 若题目被跳过：按安全策略输出 `stage.unknown = 1`，同时 `profile_answered.stage=false`

### 5.2 emotion（当下情绪，连续 0–1）

该分数不是“快乐程度”，而是**可承受刺激与吸收内容的能力**。分数越低越需要减压、陪伴、允许停下；分数越高才适合庆祝、提醒珍惜等高能量调性内容。

映射（固定离散值）：

| 选项 | tag | emotion_score |
| --- | --- | --- |
| Low | emotion: low | 0.0 |
| Overwhelmed | emotion: overwhelmed | 0.2 |
| Tired | emotion: tired | 0.4 |
| Okay | emotion: neutral | 0.6 |
| Calm | emotion: calm | 0.8 |
| Joyful | emotion: joyful | 1.0 |

输出：`emotion_score ∈ [0,1]`；若题目被跳过则输出 `null`（或不输出该字段），并通过 `profile_answered.emotion=false` 表示“未作答”

### 5.3 context（影响来源，离散）

映射（one-hot）：

- Family → `context.family = 1`
- Work or study → `context.work = 1`
- Relationship → `context.relationship = 1`
- Friends → `context.friends = 1`
- Health → `context.health = 1`

若题目被跳过：

- 输出 `context = {}`
- `profile_answered.context = false`

语义说明：

- `context` 不是情绪，而是情绪的“触发场景”
- 用于提升内容共感（语境命中），而非硬性过滤

### 5.4 need（最需要的支持，离散，推荐权重最高）

映射（one-hot）：

- Emotional support → `need.emotional_support = 1`
- Parenting pressure → `need.parenting_pressure = 1`
- Self-worth → `need.self_worth = 1`
- Anxiety relief → `need.anxiety_relief = 1`
- Rest & balance → `need.rest_balance = 1`

若题目被跳过：

- 输出 `need = {}`
- `profile_answered.need = false`

语义说明：

- `need` 是**推荐权重最高的维度**
- 表示“她现在最缺的是哪一种心理资源”

## 6. 元信息与置信度（V1.2）

### 6.1 profile_version / source / generated_at

- `profile_version`：用于规则升级与兼容（建议从 `"v1.2"` 起步；本规范与规则文档已对齐 V1.2）
- `profile_source`：固定为 `"questionnaire"`（后续可扩展）
- `profile_generated_at`：画像生成时间（用于推送降风险）

### 6.1.1 profile_answered（支持题目可跳过，V1.2）

> 目的：区分“明确选择 unknown”与“题目被跳过导致 unknown/缺失”，并让下游在不确定时自动降个性化/降风险。

- `profile_answered`：记录每一题是否作答
  - `stage/emotion/context/need`：`true/false`

输出约定（与设计说明文档保持一致，V1.2）：

- `mom_stage` 被跳过：按安全策略输出 `stage.unknown = 1`，同时 `profile_answered.stage=false`
- `emotion` 被跳过：`emotion_score` 输出 `null`（或不输出该字段），同时 `profile_answered.emotion=false`
- `context` 被跳过：`context` 输出为空对象 `{}`，同时 `profile_answered.context=false`
- `need` 被跳过：`need` 输出为空对象 `{}`，同时 `profile_answered.need=false`

### 6.2 profile_confidence（conf_U）

默认规则（可配置参数）：

- 问卷刚完成：`conf_U = 1.0`
- 随时间衰减（避免用过期状态强个性化推送）：
  - 0–7 天：`conf_U = 1.0`
  - 7–30 天：线性衰减到 `0.7`
  - 30 天以上：`conf_U = 0.5`（除非用户重新做问卷或有行为信号更新）

V1.2 补充：叠加“作答完整度”（题目可跳过场景）：

- 设 `answered_count = Σ I[profile_answered.* = true]`，总题数 `total_questions = 4`
- `completion = answered_count / total_questions`
- `completion_factor = 0.5 + 0.5 * completion`
- `conf_U = clamp(conf_time * completion_factor, 0.2, 1.0)`（其中 `conf_time` 按时间衰减得到）

下游使用建议：

- Push 推送等高风险场景：当 `conf_U` 低时，应启用不确定性惩罚（例如 `P_uncertainty`）并限制个性化力度上限。

## 7. 跨维度硬规则（Hard Rules）输出

本模块应向下游暴露“可直接执行”的硬规则结果（例如 `forbidden_tags` 或 `content_tone_blacklist`），以保证在任何排序之前先做安全过滤。

V1.2 核心禁推规则：

1. **`stage.unknown = 1`**
   - 禁推：强指向育儿压力且高个性化的内容（用于避免“把她当妈妈/正在育儿”的对话）
     - 条件（由下游在内容侧判定）：`need: parenting_pressure` 且 `personalization_power = 1`
2. **`stage.parenting = 1`**
   - 禁推：明确怀孕 / 孕期 / 胎动等内容
3. **`emotion_score ≤ 0.2`（low / overwhelmed）**
   - 禁推：高能量庆祝型（joyful 调性）内容

原则说明：

- 这些不是“答案不准”，而是“会造成反感或退出”的高风险匹配，应始终优先执行。

## 8. 兼容性与演进策略

- **向后兼容**：新版本字段可新增，但不得破坏 V1 的四维度结构与含义
- **多选扩展**：后续允许 `context/need` 多选时，输出仍可保持 one-hot，但可允许多个 key=1（并提供归一化规则）
- **行为融合**：后续可引入 `profile_source=mixed`，将行为信号用于修正 `emotion_score` 或补全缺失维度，但必须保留 `questionnaire` 原始画像以便可观测与回溯

## 9. 验收标准（模块级）

- Onboarding 填写完成后，能够生成符合本规范的用户画像对象
- 输出包含元信息（版本、来源、时间、置信度）与四维度字段
- 硬规则输出可被推荐/推送模块直接用于过滤（先过滤、后打分）

## 10. 测试（输入题目答案 → 输出用户画像）

> 本模块作为**独立模块**暂不接入 UI；外部 UI 只需按本规范提供枚举答案，本模块输出标准化用户画像对象。以下用例用于外部 UI / 服务端编写自动化测试，验证“输入答案 → 输出画像”稳定且可版本化。

### 10.1 测试约定（为可重复性而设）

- `profile_version`：固定为 `"v1.2"`
- `profile_source`：固定为 `"questionnaire"`
- `profile_generated_at`：测试用例中使用固定时间戳（避免随系统时间波动）
- `profile_confidence`：
  - 若“刚完成问卷”，测试时可令 `now == profile_generated_at`，期望 `profile_confidence = 1.0`
  - 若测试时间衰减，则固定 `profile_generated_at` 与 `now`，按第 6.2 节规则计算期望值

### 10.2 字段映射用例（基础正确性）

#### 用例 A：完整映射（parenting + tired + work + rest_balance）

输入（题目答案）：

```json
{
  "mom_stage": "parenting",
  "emotion": "tired",
  "context": "work",
  "need": "rest_balance"
}
```

期望输出（用户画像；示例中 `profile_generated_at` 由测试固定注入）：

```json
{
  "profile_version": "v1.2",
  "profile_source": "questionnaire",
  "profile_generated_at": "2026-01-30T00:00:00Z",
  "profile_confidence": 1.0,
  "profile_answered": { "stage": true, "emotion": true, "context": true, "need": true },
  "stage": { "expecting": 0, "parenting": 1, "unknown": 0 },
  "emotion_score": 0.4,
  "context": { "work": 1 },
  "need": { "rest_balance": 1 }
}
```

#### 用例 B：unknown 语义（不希望被母职身份定义）

输入：

```json
{
  "mom_stage": "unknown",
  "emotion": "neutral",
  "context": "relationship",
  "need": "emotional_support"
}
```

期望输出（关键点：`stage.unknown = 1`）：

```json
{
  "profile_version": "v1.2",
  "profile_source": "questionnaire",
  "profile_generated_at": "2026-01-30T00:00:00Z",
  "profile_confidence": 1.0,
  "profile_answered": { "stage": true, "emotion": true, "context": true, "need": true },
  "stage": { "expecting": 0, "parenting": 0, "unknown": 1 },
  "emotion_score": 0.6,
  "context": { "relationship": 1 },
  "need": { "emotional_support": 1 }
}
```

#### 用例 A2：全部跳过（V1.2 最小可计算画像）

输入（所有题都跳过；用“不传字段”的方式表示）：

```json
{}
```

期望输出（关键点：`profile_answered` 全 false；`stage.unknown=1`；`emotion_score=null`；`context/need={}`）：

```json
{
  "profile_version": "v1.2",
  "profile_source": "questionnaire",
  "profile_generated_at": "2026-01-30T00:00:00Z",
  "profile_confidence": 0.5,
  "profile_answered": { "stage": false, "emotion": false, "context": false, "need": false },
  "stage": { "unknown": 1 },
  "emotion_score": null,
  "context": {},
  "need": {}
}
```

### 10.3 置信度衰减用例（时间维度）

> 本组用例只验证 `profile_confidence` 计算是否符合第 6.2 节；其余字段可沿用任意合法输入。

#### 用例 C：0–7 天（不衰减）

- `profile_generated_at = 2026-01-01T00:00:00Z`
- `now = 2026-01-05T00:00:00Z`

期望：`profile_confidence = 1.0`

#### 用例 D：7–30 天（线性衰减到 0.7）

- `profile_generated_at = 2026-01-01T00:00:00Z`
- `now = 2026-01-11T00:00:00Z`（第 10 天）

期望：`profile_confidence ≈ 1.0 - 0.3 * (10 - 7) / (30 - 7) ≈ 0.9609`

> 测试建议：用浮点容差断言（例如 `abs(actual - expected) <= 1e-3`）。

#### 用例 E：30 天以上（固定为 0.5）

- `profile_generated_at = 2026-01-01T00:00:00Z`
- `now = 2026-02-15T00:00:00Z`

期望：`profile_confidence = 0.5`

### 10.4 硬规则触发用例（可选，但建议覆盖）

> 本节只验证“规则是否被触发”。由于第 7 节目前只规定了规则语义，未强约束硬规则输出字段结构，测试可用两种方式之一：
>
> - 方式 1：若实现提供 `hard_rules`（或同义字段），则断言其包含对应禁推项
> - 方式 2：实现不提供结构化 `hard_rules` 时，至少应提供可观测的 `rule_hits`（字符串数组）用于下游与回归测试

#### 用例 F：emotion_score ≤ 0.2 禁推 joyful 调性

输入：

```json
{
  "mom_stage": "expecting",
  "emotion": "overwhelmed",
  "context": "health",
  "need": "anxiety_relief"
}
```

期望：

- `emotion_score = 0.2`
- 规则命中：`emotion_score ≤ 0.2` → 禁推高能量庆祝型（joyful 调性）内容

#### 用例 G：stage.unknown 禁推“育儿压力 + 高个性化”内容

输入：

```json
{
  "mom_stage": "unknown",
  "emotion": "calm",
  "context": "family",
  "need": "parenting_pressure"
}
```

期望：

- `stage.unknown = 1`
- 规则命中：`stage.unknown = 1` → 下游禁推满足以下条件的内容：
  - `need: parenting_pressure`
  - `personalization_power = 1`

