All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 9m14s
- Update device_interaction views - Update admin README and CLAUDE.md - Add affinity system design doc - Add device chat record subtitle storage scheme doc Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
314 lines
13 KiB
Markdown
314 lines
13 KiB
Markdown
# 好感度系统功能与规则设计
|
||
|
||
> 文档范围:本文档描述**洛天依管理系统**中「好感度系统」的全部功能模块与规则设计,用于产品对齐、联调对接与后续服务端落地。
|
||
>
|
||
> 当前状态:管理后台 UI 已完成(见 [qy-lty-admin/app/affinity/page.tsx](../qy-lty-admin/app/affinity/page.tsx)),后端模型骨架已就绪(见 [qy_lty/userapp/models.py](../qy_lty/userapp/models.py) 第 79–115 行),但前端尚未实际调用真实接口,设备端/手机端事件尚未接入好感度逻辑。
|
||
|
||
---
|
||
|
||
## 一、系统定位
|
||
|
||
好感度系统用于刻画**用户与洛天依**之间的亲密度关系。其核心价值:
|
||
|
||
- 让用户的互动行为产生**持续、可感知的数值反馈**
|
||
- 通过**等级 + 解锁内容**形成长期陪伴动机
|
||
- 对不活跃用户通过**衰减机制**保留流失预警与召回空间
|
||
|
||
好感度是一个 `[0, max_affinity]` 区间的整数(默认上限 100),每个用户独立维护,记录在 `ParadiseUser.favorability` 字段。
|
||
|
||
---
|
||
|
||
## 二、功能模块总览
|
||
|
||
管理后台分 4 个标签页,对应 5 块功能:
|
||
|
||
| 模块 | 页签 | 功能说明 |
|
||
|---|---|---|
|
||
| 系统概览 | 系统概览 | 关键指标卡片 + 全局基础参数设置 |
|
||
| 互动规则 | 互动规则 | 管理各类互动行为的好感度变化规则 |
|
||
| 衰减规则 | 互动规则页下半 | 配置不活跃用户的好感度衰减策略 |
|
||
| 等级奖励 | 等级奖励 | 管理好感度等级划分与奖励发放 |
|
||
| 数据统计 | 数据统计 | 好感度分布、互动分析、趋势监控 |
|
||
|
||
---
|
||
|
||
## 三、模块 1 — 系统概览与全局参数
|
||
|
||
### 3.1 关键指标卡片
|
||
|
||
| 指标 | 含义 | 数据源 |
|
||
|---|---|---|
|
||
| 平均好感度 | 所有用户 `favorability` 平均值 | 聚合 `ParadiseUser` |
|
||
| 最高好感度 | 系统中达到上限的用户数 | `favorability = max_affinity` 计数 |
|
||
| 互动次数/日 | 当天触发的所有规则次数总和 | 聚合 `AffinityLog`(待建) |
|
||
| 活跃用户比例 | 近 N 天有互动的用户占比 | 聚合用户活跃状态 |
|
||
|
||
### 3.2 全局参数(AffinitySetting)
|
||
|
||
| 字段 | 默认值 | 说明 |
|
||
|---|---|---|
|
||
| `initial_affinity` | 10 | 新用户创建时的初始好感度 |
|
||
| `max_affinity` | 100 | 好感度上限(所有规则累计不会超过此值) |
|
||
| `daily_cap` | 20 | 单用户每日好感度**净增长**上限(跨规则汇总) |
|
||
| `decay_rate` | 2 点/天 | 全局默认衰减速率(衰减规则可覆盖) |
|
||
| `decay_threshold` | 3 天 | 不互动多少天后开始衰减 |
|
||
| `enable_notify` | true | 好感度变化是否推送通知 |
|
||
| `enable_rewards` | true | 是否启用等级奖励发放 |
|
||
|
||
**规则**:
|
||
- `initial_affinity` 只影响**新用户**,不回溯修改已有用户
|
||
- `max_affinity` 调低后,**已超过的用户保留原值**,但不再增加
|
||
- `daily_cap` 是**跨所有正向规则**的顶层限制,触达后当日所有增益无效(衰减不受此限)
|
||
|
||
---
|
||
|
||
## 四、模块 2 — 互动规则
|
||
|
||
### 4.1 规则字段定义
|
||
|
||
| 字段 | 类型 | 示例 | 说明 |
|
||
|---|---|---|---|
|
||
| `id` | string | `rule-2` | 规则唯一 ID |
|
||
| `name` | string | `对话` | 规则展示名 |
|
||
| `type` / `rule_key` | enum | `chat` | **代码标识**,服务端事件通过它匹配规则,不可重复 |
|
||
| `description` | string | `与洛天依进行对话` | 展示描述 |
|
||
| `minChange` | int | 1 | 单次好感度变化最小值 |
|
||
| `maxChange` | int | 5 | 单次好感度变化最大值(最终取 `[min, max]` 的随机整数) |
|
||
| `singleCap` | int | 5 | 单次变化绝对值上限(保护性钳位,防止数据异常) |
|
||
| `dailyCap` | int | 15 | **本规则**每日累计变化上限(绝对值) |
|
||
| `isNegative` | bool | false | 是否负向规则。正向用正数,负向用负数 |
|
||
| `isEnabled` | bool | true | 是否启用,禁用后事件不触发 |
|
||
|
||
### 4.2 默认规则集(8 条)
|
||
|
||
| rule_key | 名称 | 范围 | 单次/日 | 正负 | 触发来源 |
|
||
|---|---|---|---|---|---|
|
||
| `card` | 使用卡片 | +1 ~ +3 | 3 / 10 | 正 | 手机端(使用卡片 API) |
|
||
| `chat` | 对话 | +1 ~ +5 | 5 / 15 | 正 | 设备端 + 手机端(聊天消息) |
|
||
| `feed` | 喂食 | +2 ~ +8 | 8 / 16 | 正 | 手机端(喂食动作) |
|
||
| `touch` | 抚摸 | +1 ~ +3 | 3 / 9 | 正 | 手机端 / 设备端(抚摸信号) |
|
||
| `dress` | 换装 | +2 ~ +6 | 6 / 12 | 正 | 手机端(换装 API) |
|
||
| `prop` | 使用道具 | +1 ~ +4 | 4 / 12 | 正 | 手机端(道具使用) |
|
||
| `gift` | 送礼物 | +5 ~ +15 | 15 / 20 | 正 | 手机端(赠礼 API) |
|
||
| `decay` | 无互动衰减 | −1 ~ −3 | 3 / 5 | 负 | 定时任务 |
|
||
|
||
### 4.3 触发计算流程
|
||
|
||
```
|
||
收到事件 (user_id, rule_key, 来源上下文)
|
||
│
|
||
▼
|
||
① 取规则 → 若 is_enabled=false,丢弃
|
||
│
|
||
▼
|
||
② 冷却检查(Redis:affinity:cd:{user}:{rule_key})
|
||
│ 未到冷却 → 丢弃
|
||
▼
|
||
③ 本规则日上限检查(affinity:daily:{user}:{rule_key}:{date})
|
||
│ 已满 → 丢弃
|
||
▼
|
||
④ 全局日上限检查(正向事件才检查)
|
||
│ 已满 → 丢弃
|
||
▼
|
||
⑤ 计算变化值 = random(min, max)
|
||
↓ 按 single_cap 钳位
|
||
│
|
||
▼
|
||
⑥ 原子更新 ParadiseUser.favorability
|
||
↓ 钳位到 [0, max_affinity]
|
||
│
|
||
▼
|
||
⑦ 写 AffinityLog、更新计数器
|
||
│
|
||
▼
|
||
⑧ 判断是否跨越等级边界
|
||
│ 是 → 触发等级变更事件(发奖励 + 推通知)
|
||
▼
|
||
⑨ 通过 WebSocket 向用户的所有在线端推送 affinity_update
|
||
```
|
||
|
||
### 4.4 规则设计约定
|
||
|
||
- **单一写入入口**:所有好感度变化必须经由服务端统一入口,客户端不能直接增减。
|
||
- **rule_key 即契约**:客户端事件不携带分值,只报「我触发了 `gift` 规则」,具体加多少由服务端按规则算。规则可被管理员随时调整,客户端无需改动。
|
||
- **幂等防护**:同一 `rule_key` + 同一设备事件 ID 在冷却窗口内只生效一次,防抖防重复。
|
||
- **禁用规则的兜底**:管理员禁用某规则后,客户端若继续上报该事件,服务端静默丢弃(不报错、不扣冷却)。
|
||
|
||
---
|
||
|
||
## 五、模块 3 — 衰减规则
|
||
|
||
衰减是**本质为 `rule_key=decay` 的负向规则**,但由于业务语义特殊,在后台独立配置。
|
||
|
||
### 5.1 衰减字段
|
||
|
||
| 字段 | 默认值 | 说明 |
|
||
|---|---|---|
|
||
| `decay_start_days` | 3 | 不互动多少天后开始衰减 |
|
||
| `decay_rate_per_day` | 2 点/天 | 平均每日衰减点数 |
|
||
| `min_decay` | 1 | 单日衰减最小值 |
|
||
| `max_decay` | 3 | 单日衰减最大值 |
|
||
| `decay_cap` | 5 点/天 | 单日衰减上限(保护性) |
|
||
| `min_floor` | 0 | **衰减下限**,好感度不会低于此值 |
|
||
| `notify_decay` | true | 是否通知用户「好感度下降了」 |
|
||
|
||
### 5.2 衰减执行
|
||
|
||
- **频次**:每日 00:30 由定时任务统一跑一次
|
||
- **命中对象**:`last_active_at < now - decay_start_days` 的用户
|
||
- **落库**:衰减也写 `AffinityLog`,`source='system_decay'`
|
||
- **下限保护**:若用户当前好感度 ≤ `min_floor` 则跳过
|
||
- **与互动的关系**:用户当天有互动即**重置不活跃计数**,次日不衰减
|
||
|
||
### 5.3 设计权衡
|
||
|
||
- 衰减**不占用** `daily_cap` 全局日上限(因为它是扣减,不是增益)
|
||
- 衰减日志会产生大量记录,可考虑按天合并写一条,减少 `AffinityLog` 膨胀
|
||
- 若「当日互动」和「当日衰减」同时命中,先执行衰减再执行互动(让用户感受到「我回来了 → 好感度止跌回升」)
|
||
|
||
---
|
||
|
||
## 六、模块 4 — 等级奖励
|
||
|
||
### 6.1 等级字段
|
||
|
||
| 字段 | 示例 | 说明 |
|
||
|---|---|---|
|
||
| `level` | 3 | 等级序号,唯一 |
|
||
| `name` | 熟悉 | 等级名 |
|
||
| `minAffinity` / `maxAffinity` | 41 / 60 | 等级好感度区间(闭区间) |
|
||
| `unlockContent` | `更多服装、特殊对话` | 文案描述,前端展示 |
|
||
| `rewardType` | `unlock` / `item` / `currency` / `mixed` | 奖励类型 |
|
||
| `rewardCurrency` | 100 | 虚拟货币数量(rewardType 含 currency 时生效) |
|
||
| `rewardItems` | `[{item_id, qty}]` | 道具列表(rewardType 含 item 时生效) |
|
||
| `isEnabled` | true | 是否启用该等级 |
|
||
|
||
### 6.2 默认等级(5 档)
|
||
|
||
| 等级 | 名称 | 区间 | 解锁内容 |
|
||
|---|---|---|---|
|
||
| 1 | 初识 | 0 ~ 20 | 基础对话功能 |
|
||
| 2 | 相识 | 21 ~ 40 | 基础服装、道具使用 |
|
||
| 3 | 熟悉 | 41 ~ 60 | 更多服装、特殊对话 |
|
||
| 4 | 亲密 | 61 ~ 80 | 限定服装、特殊互动 |
|
||
| 5 | 挚友 | 81 ~ 100 | 专属内容、特殊剧情 |
|
||
|
||
### 6.3 等级变化规则
|
||
|
||
- 等级由好感度区间**自动映射**,不是独立字段
|
||
- **跨级判定**:每次好感度变动后,取当前值所属区间,与上一次等级比较
|
||
- 升级 → 发放目标等级的奖励(**只发最终落点等级**,跳级不补发中间等级)
|
||
- 降级(衰减导致) → 不追回奖励,但取消解锁内容访问权限
|
||
- **奖励幂等**:同一用户同一等级的「首次达到奖励」只发一次;降级后再升级不重复发放
|
||
|
||
### 6.4 区间约束
|
||
|
||
- 区间**不得重叠**,管理端保存时做校验
|
||
- 区间**不得有空隙**(如等级 2 的 max=40,则等级 3 的 min 必须是 41)
|
||
- 最低等级 `min=0`,最高等级 `max=max_affinity`
|
||
|
||
---
|
||
|
||
## 七、模块 5 — 数据统计
|
||
|
||
### 7.1 概览指标
|
||
- 平均好感度 / 中位数 / 最高好感度
|
||
- 活跃用户数(近 7 日有互动)
|
||
- 今日互动次数、今日新增好感度总量
|
||
|
||
### 7.2 分布分析
|
||
- 好感度区间分布(0-20、21-40…)
|
||
- 各等级用户数占比
|
||
- 各互动规则触发频次 Top N
|
||
|
||
### 7.3 趋势分析
|
||
- 日/周/月的平均好感度变化曲线
|
||
- 日互动量趋势
|
||
- 衰减命中用户数趋势
|
||
|
||
### 7.4 用户级查询
|
||
- 按用户 ID 查询其好感度当前值、等级、近期变化日志
|
||
- 管理员手动调整(加减好感度,走 `source='admin_adjust'` 记 log)
|
||
|
||
---
|
||
|
||
## 八、多端触发点一览
|
||
|
||
好感度变化可由以下端点触发,所有端都走**同一个服务端入口**:
|
||
|
||
| 触发来源 | 规则 | 通道 | 位置参考 |
|
||
|---|---|---|---|
|
||
| 设备端上报「用户发起对话」 | `chat` | WebSocket | [device_interaction/consumers.py](../qy_lty/device_interaction/consumers.py) `chat_message` |
|
||
| 设备端对话结束(陪伴时长) | `chat` 或独立 `companion_time` | WebSocket | `conversation_status` 的 begin/end |
|
||
| 手机端点击「唱歌/跳舞/抚摸」 | 对应 rule_key | WebSocket | consumers.py `sing` / `dance` / `touch` |
|
||
| 手机端赠礼 / 喂食 / 换装 / 用道具 | 对应 rule_key | HTTP | 对应业务 ViewSet 钩子 |
|
||
| 管理员手动调整 | 无 rule | HTTP Admin API | 管理后台 |
|
||
| 衰减定时任务 | `decay` | 后台任务 | 定时调度 |
|
||
|
||
**身份识别**:
|
||
- 设备端:MAC 登录获取 token → 服务端通过 `UserDevice` 找到 `user_id`
|
||
- 手机端:Redis token 认证 → 直接拿到 `user_id`
|
||
- 服务端只认 `user_id`,与触发端无关
|
||
|
||
---
|
||
|
||
## 九、数据契约(接口层摘要)
|
||
|
||
### 9.1 管理端
|
||
|
||
| 接口 | 方法 | 用途 |
|
||
|---|---|---|
|
||
| `/api/admin/affinity/rules/` | GET/POST/PATCH/DELETE | 互动规则 CRUD |
|
||
| `/api/admin/affinity/levels/` | GET/POST/PATCH/DELETE | 等级 CRUD |
|
||
| `/api/admin/affinity/settings/` | GET/PUT | 全局参数(单例) |
|
||
| `/api/admin/affinity/logs/` | GET | 变化日志查询(可按 user、rule、时间过滤) |
|
||
| `/api/admin/affinity/stats/` | GET | 统计聚合 |
|
||
| `/api/admin/affinity/adjust/` | POST | 管理员手动调整(必须留审计) |
|
||
|
||
### 9.2 客户端(手机端 / 设备端共用)
|
||
|
||
| 接口 | 方法 | 用途 |
|
||
|---|---|---|
|
||
| `/api/user/me/affinity/` | GET | 当前好感度、等级、下一级进度、近期变化 |
|
||
| `/api/user/me/affinity/claim-reward/` | POST | 领取等级奖励 |
|
||
|
||
### 9.3 WebSocket 实时推送
|
||
|
||
| 事件 | 方向 | payload |
|
||
|---|---|---|
|
||
| `affinity_update` | 服务端 → 用户 | `{change, before, after, rule_key, source}` |
|
||
| `level_up` | 服务端 → 用户 | `{old_level, new_level, reward}` |
|
||
| `level_down` | 服务端 → 用户 | `{old_level, new_level}`(衰减导致降级) |
|
||
|
||
---
|
||
|
||
## 十、待开发拼板
|
||
|
||
| 模块 | 前端 | 后端模型 | 后端接口 | 触发埋点 |
|
||
|---|---|---|---|---|
|
||
| 互动规则 CRUD | ✅ UI 完成 | ⚠️ 缺字段 | ⚠️ 需扩展 | — |
|
||
| 等级 CRUD | ✅ UI 完成 | ⚠️ 缺字段 | ⚠️ 需扩展 | — |
|
||
| 全局设置 | ✅ UI 完成 | ❌ 无表 | ❌ 无接口 | — |
|
||
| 衰减配置 | ✅ UI 完成 | ❌ 无表 | ❌ 无接口 | ❌ 无定时任务 |
|
||
| 变化日志 | ❌ 无 UI | ❌ 无表 | ❌ 无接口 | — |
|
||
| 数据统计 | ⚠️ Mock 展示 | — | ❌ 无聚合接口 | — |
|
||
| 客户端查询 | — | — | ❌ 无接口 | — |
|
||
| WS 实时推送 | — | — | — | ❌ 未接入 |
|
||
| 设备/手机事件埋点 | — | — | — | ❌ 未接入 |
|
||
|
||
图例:✅ 已完成 ⚠️ 部分完成 ❌ 未开始
|
||
|
||
---
|
||
|
||
## 十一、术语表
|
||
|
||
| 术语 | 含义 |
|
||
|---|---|
|
||
| rule_key | 互动规则的代码级标识(如 `chat`),客户端事件通过它匹配规则 |
|
||
| single_cap | 单次变化绝对值上限(保护性钳位) |
|
||
| daily_cap | 单规则每日累计变化上限 |
|
||
| 全局日上限 | 跨所有正向规则的顶层日增长上限(`AffinitySetting.daily_cap`) |
|
||
| 冷却 | 同一用户同一规则的最小触发间隔 |
|
||
| source | 变化来源:`device_event` / `mobile_event` / `system_decay` / `admin_adjust` |
|
||
| 跨级 | 好感度变化使得用户从一个等级区间移动到另一个 |
|