# 子模块：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 任务调用（无框架耦合）。

