# 好感度系统功能与规则设计 > 文档范围:本文档描述**洛天依管理系统**中「好感度系统」的全部功能模块与规则设计,用于产品对齐、联调对接与后续服务端落地。 > > 当前状态:管理后台 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` | | 跨级 | 好感度变化使得用户从一个等级区间移动到另一个 |