# 需求汇总反馈机制设计

## 1. 目标与范围

在现有“需求汇总”页面的三层记录上增加独立反馈入口：

1. 需求：`demand_grade` 的当日判断记录。
2. 视频：当前需求下的一条拓展命中视频或需求池源视频。
3. 命中内容：`demand_video_expansion` 中的一条目的点、关键点或灵感点。

本期反馈是新增证据，不直接修改需求等级、视频命中关系或命中内容。

## 2. 核心设计结论

### 2.1 使用一张统一反馈表

三类反馈共享提交人、审计时间和文字说明。统一表便于：

- 汇总一个需求下的全部反馈；
- 建立统一待处理队列；
- 后续将人工反馈作为下一业务日的输入证据；
- 保持追加式历史，避免修改原反馈导致审计信息丢失。

### 2.2 反馈绑定当日记录

需求反馈绑定 `demand_grade.id`，而不是只绑定 `demand_name`。同一需求在不同业务日的等级、原因、关联视频都可能变化，必须知道用户针对哪个版本反馈。

同时保存 `biz_dt` 和目标快照，方便跨日查询及源数据变化后的历史还原。

### 2.3 三类目标的稳定身份

| 反馈目标 | 稳定定位字段 | 说明 |
|---|---|---|
| 需求 | `demand_grade_id` | 对当日需求判断反馈 |
| 视频 | `demand_grade_id + video_id` | 同一视频对不同需求的相关性不同 |
| 命中内容 | `demand_video_expansion_id` | 精确定位单条目的点、关键点或灵感点 |

当前命中内容接口没有返回 `demand_video_expansion.id`，实现前需要为 `VideoDiscoveryPoint` 增加 `id`。

## 3. 反馈记录表

建议表名：`demand_feedback`

| 字段 | 类型 | 必填 | 说明 |
|---|---|---:|---|
| `id` | BIGINT | 是 | 自增主键 |
| `client_request_id` | VARCHAR(64) | 是 | 客户端提交幂等键，唯一 |
| `target_type` | VARCHAR(32) | 是 | `demand` / `video` / `hit_content` |
| `biz_dt` | VARCHAR(32) | 是 | 目标所属业务日 |
| `demand_grade_id` | BIGINT | 是 | 三类反馈都必须归属一个需求 |
| `video_id` | VARCHAR(64) | 否 | 视频、命中内容反馈必填 |
| `demand_video_expansion_id` | BIGINT | 否 | 命中内容反馈必填 |
| `feedback_action` | VARCHAR(32) | 是 | `support` / `oppose` / `correct` / `supplement` |
| `reason_code` | VARCHAR(64) | 否 | 按目标类型选择的问题原因 |
| `content` | TEXT | 否 | 用户补充说明；纠错、补充时必填 |
| `target_snapshot_json` | TEXT | 是 | 服务端生成的目标展示快照 |
| `feedback_user_id` | INT | 是 | 反馈人 `auth_user.id` |
| `feedback_user_name_snapshot` | VARCHAR(128) | 是 | 反馈人名称快照 |
| `feedback_username_snapshot` | VARCHAR(64) | 是 | 反馈人登录账号 `username` 快照 |
| `created_at` | DATETIME | 是 | 创建时间 |
| `updated_at` | DATETIME | 是 | 更新时间 |

推荐索引：

```sql
UNIQUE KEY uk_demand_feedback_request (client_request_id),
KEY idx_demand_feedback_demand (demand_grade_id, created_at),
KEY idx_demand_feedback_video (demand_grade_id, video_id, created_at),
KEY idx_demand_feedback_expansion (demand_video_expansion_id, created_at),
KEY idx_demand_feedback_user (feedback_user_id, created_at)
```

目标字段校验由服务层执行：

- `demand`：`video_id`、`demand_video_expansion_id` 必须为空；
- `video`：`video_id` 必填，`demand_video_expansion_id` 为空；
- `hit_content`：三个定位字段都必填；
- 命中内容必须确实属于传入的需求和视频；
- `biz_dt`、展示文本和其他快照全部由服务端根据目标生成，不信任客户端传值。

不建议对三个业务目标设置级联删除。反馈是审计证据，即使目标以后失效，也要依靠快照保留当时上下文。

### 3.1 目标快照示例

```json
{
  "demand_name": "老年人智能手机使用",
  "grade": "A",
  "video_title": "教父母使用手机的五个技巧",
  "video_source": "expansion",
  "point_type": "purpose",
  "expanded_text": "降低老年人使用智能设备的门槛",
  "point_desc": "面向子女和老人解释基础操作",
  "reason": "该内容直接覆盖当前需求"
}
```

快照仅保存目标提交时已有的业务字段，不保存密码、会话、IP 等认证信息。反馈人由服务端从当前登录会话取得，客户端不能指定或覆盖。

不增加 `has_feedback`、`is_feedback` 等“是否反馈”字段。某条业务记录是否存在反馈、反馈数量是多少，都直接由 `demand_feedback` 记录实时查询或聚合得出，避免业务记录和反馈表状态不一致。

## 4. 反馈选项

第一层统一动作保持简单：

| 动作 | 页面文案 | 使用场景 |
|---|---|---|
| `support` | 认可 | 判断、关联或内容准确 |
| `oppose` | 不认可 | 判断、关联或内容不成立 |
| `correct` | 纠错 | 数据、类型、文案或原因有明确错误 |
| `supplement` | 补充 | 增加需求、视频、依据或说明 |

第二层原因随目标变化：

| 目标 | 建议原因 |
|---|---|
| 需求 | 需求不成立、等级不合适、分类不准确、需求表述不清、判断原因不准确、缺少需求 |
| 视频 | 与需求不相关、相关性弱、视频失效、视频重复、标题或信息错误、缺少相关视频 |
| 命中内容 | 内容未命中、点位类型错误、表述不准确、命中原因不准确、内容重复、缺少命中内容 |

交互校验：

- “认可”可直接提交，也可补充说明；
- “不认可”必须选择原因；
- “纠错”和“补充”必须填写说明；
- 说明限制 1,000 字，前后端同时校验。

## 5. 前端页面设计

### 5.1 入口位置

沿用现有三栏工作台，不新开反馈页面：

```text
┌──────────────────┬────────────────────────┬────────────────────────┐
│ 01 选择需求       │ 02 需求拓展视频          │ 03 命中视频内容          │
│                  │                        │                        │
│ 需求卡片           │ 当前需求说明   [反馈]    │ 当前视频                │
│ 名称 / 等级 / 视频数│                        │                        │
│ [反馈 2]           │ 视频卡片 1       [反馈]  │ 目的点卡片 1      [反馈] │
│                  │ 视频卡片 2       [反馈]  │ 关键点卡片 2      [反馈] │
└──────────────────┴────────────────────────┴────────────────────────┘
```

- 需求反馈：放在第二栏“当前寻找需求”标题区域，确保反馈对象是当前选中的完整需求记录。
- 视频反馈：每条视频卡片右侧增加“反馈”，与选择视频动作分开。
- 命中内容反馈：每条点位卡片右侧增加“反馈”，保留现有复制按钮。
- 如需提示反馈情况，只显示由反馈记录聚合得到的 `反馈 n`，不显示或保存“已反馈”状态；徽标不使用颜色表达业务好坏。

当前需求卡和视频卡整块都是 `<button>`。实现视频行内反馈前，要重构为容器加两个并列按钮，避免交互元素嵌套。

### 5.2 统一反馈抽屉

点击任意入口后，从右侧打开统一抽屉：

```text
反馈 · 命中内容                                      ×
────────────────────────────────────────────────────
需求：老年人智能手机使用
视频：教父母使用手机的五个技巧
目的点：降低老年人使用智能设备的门槛

你的判断
[认可] [不认可] [纠错] [补充]

问题原因
[内容未命中] [点位类型错误] [原因不准确] [...]

补充说明
┌──────────────────────────────────────────────────┐
│ 请说明判断依据或建议的正确内容                    │
└──────────────────────────────────────────────────┘
0 / 1000

                                [取消] [提交反馈]
```

交互要求：

- 打开抽屉时固定保存目标上下文，切换左侧需求不能改变正在填写的反馈对象；
- 提交期间按钮显示加载态并禁止重复点击；
- 成功后关闭抽屉，重新获取对应记录的反馈数量并显示轻量成功提示；
- 失败时保留用户已填写内容；
- 使用 `client_request_id` 防止网络重试产生重复记录；
- Esc 可关闭，关闭未提交内容时二次确认；
- 移动端使用底部全屏面板。

### 5.3 反馈历史

MVP 在记录旁只展示该目标的反馈总数，不记录或展示“当前用户是否反馈过”。

点击数量徽标可查看该目标的反馈历史。每条历史记录展示反馈人、反馈动作、原因、说明和时间。

## 6. API 设计

### 6.1 扩展现有查询响应

避免页面对每条记录发起一次反馈查询。在现有两个接口中批量返回反馈摘要：

- `GET /api/video-discovery/demands`
- `GET /api/video-discovery/demands/{demand_grade_id}`

需求、视频和命中内容增加：

```json
{
  "feedback_summary": {
    "count": 2
  }
}
```

命中内容同时增加：

```json
{
  "id": 456,
  "point_type": "purpose",
  "expanded_text": "..."
}
```

### 6.2 提交反馈

`POST /api/video-discovery/feedback`

```json
{
  "client_request_id": "0198f85d-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "target_type": "hit_content",
  "demand_grade_id": 123,
  "video_id": "7360000000000000000",
  "demand_video_expansion_id": 456,
  "feedback_action": "oppose",
  "reason_code": "not_hit",
  "content": "该内容只提到设备设置，没有覆盖老人学习使用的需求。"
}
```

成功返回 `201`：

```json
{
  "item": {
    "id": 789,
    "created_at": "2026-07-30T15:20:00"
  },
  "feedback_summary": {
    "count": 3
  }
}
```

服务端响应：

- `400`：字段组合或反馈内容不合法；
- `404`：目标不存在；
- `409`：目标上下文不一致；
- 使用相同 `client_request_id` 重试时返回第一次创建的结果，不重复写入。

### 6.3 查看历史

`GET /api/video-discovery/feedback`

查询参数使用与提交相同的目标定位字段，并支持 `cursor`、`limit`。普通用户返回自己的记录，管理员返回全部记录。

## 7. 权限

| 能力 | 普通用户 | 管理员 |
|---|---:|---:|
| 提交反馈 | 是 | 是 |
| 查看反馈记录和反馈人 | 是 | 是 |
| 直接修改业务结果 | 否 | 否，需走独立受控流程 |

需要在 `AuthenticationMiddleware` 中放行普通用户的反馈 `GET` 和 `POST` 路由。

## 8. 实现拆分

### 第一阶段：可提交、可追溯

1. 新增 Alembic migration、SQLAlchemy model 和 repository。
2. 命中内容查询返回 `demand_video_expansion.id`。
3. 新增提交与历史查询 API。
4. 现有需求和详情接口批量附加反馈摘要。
5. 新增统一反馈抽屉，并在需求、视频、命中内容三层接入。
6. 增加字段组合校验、幂等提交和权限测试。

### 第二阶段：反馈闭环

1. 将反馈转换为下一业务日可消费的证据事件。
2. 展示反馈对后续需求判断的实际影响。

## 9. 验收标准

- 页面上的每条需求、每条视频和每条命中内容都有明确反馈入口；
- 提交后数据库能唯一还原提交人、业务日、需求、视频及命中内容；
- 同一视频在不同需求下的反馈不会串联；
- 源数据更新后，历史反馈仍能通过目标快照解释；
- 普通用户不能看到其他用户的反馈正文和身份；
- 重复点击或请求重试不会生成重复记录；
- 反馈不会直接覆盖 `demand_grade`、`demand_video_expansion` 或视频详情；
- 列表和详情加载反馈摘要时不产生逐条 N+1 查询。

## 10. 实施前需确认的产品决策

建议按以下默认值实施：

1. 所有已登录用户都可以提交反馈；
2. 反馈追加保存，不允许直接编辑；需要修改时再次提交，历史仍保留；
3. 第一阶段展示反馈记录和反馈人，不维护“是否反馈”状态；
4. 反馈是证据，不自动改变当天需求等级或命中结果。
