feat(platform): 首页轮播 feed 接入真实数据 + 三统计配置增强

轮播 feed(marquee_seed,全新):
- 新增 marquee_seed 表 + admin CRUD/批量生成/预览;真实比价记录(success 且 0<省额≤300元、按 user 去重)优先,种子兜底混播
- 种子为「生成规则」:用户名可空(空则按脱敏格式随机合成、避开同屏撞名),金额改 [min,max] 区间随机,feed 公平随机抽取(不看 sort_order)
- 新增 GET /api/v1/platform/savings-feed(无鉴权);展示时间统一刷新为相对当前的最近时刻

三统计配置(platform_stat_display)增强:
- 自增长新增「绝对增量」方式(random_kind=add,每周期 +[step_min,step_max])
- 真实值模式加基数偏移(real_offset,展示=真实+偏移)
- 只增不减护栏(allow_decrease 默认关,real/manual 不回退防门面缩水)
- 累计节省 real 口径只计 0<单条≤300元防虚高;倍率上限 5.0→1.5;PATCH 支持 apply_now 立即更新

迁移:marquee_seed_table / marquee_seed_range / platform_stat_growth_offset(均可逆,已验证)
文档:API/DB 对应文档同步更新

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
OuYingJun1024
2026-06-07 12:05:17 +08:00
parent cfeacb4bab
commit c9578f5ba2
23 changed files with 930 additions and 62 deletions
+1
View File
@@ -43,6 +43,7 @@
| 表 | 用途 | 模型 | 文档 |
|---|---|---|---|
| `platform_stat_display` | 首页三统计展示配置(real/manual/random) | `models/platform_stat.py` | [详情](./platform_stat_display.md) |
| `marquee_seed` | 首页轮播种子(真实不足时兜底混播) | `models/marquee_seed.py` | [详情](./marquee_seed.md) |
### 运营后台 admin(独立子应用 `app/admin/`,独立鉴权)
| 表 | 用途 | 模型 | 文档 |
+24
View File
@@ -0,0 +1,24 @@
# marquee_seed — 首页轮播种子(生成规则)
> 模型 `app/models/marquee_seed.py` | 关联接口 [platform-savings-feed](../api/platform-savings-feed.md) / [admin-marquee-seeds](../api/admin-marquee-seeds.md) | [← 表索引](./README.md)
首页「用户****xxx 比价后节省 xx 元」轮播的兜底假数据。轮播真实数据源是全平台比价记录,真实不足 N 条时用本表启用的种子补齐「混播」,保证轮播不空。种子是「生成规则」而非死记录:用户名可空(→随机合成)、金额是区间(→随机取值)。运营后台增删改。逻辑见 `app/repositories/marquee.py`
## 字段
| 列 | 类型 | 约束 / 默认 | 说明 |
|---|---|---|---|
| `id` | Integer | PK, autoincrement | |
| `masked_user` | String(64) | NULL 可空 | 脱敏用户名,如 `用户********a52`(整串存)。**留空 → feed 展示时按脱敏格式随机合成**(`用户********`+3 位 hex,避开同屏撞名),风格与真实条一致、永不穿帮 |
| `min_cents` | Integer | NOT NULL | 节省金额区间下限(分) |
| `max_cents` | Integer | NOT NULL | 节省金额区间上限(分);feed 每次在 `[min,max]` 随机取值,**固定金额则 min==max** |
| `enabled` | Boolean | NOT NULL, default true | 停用的不参与混播 |
| `sort_order` | Integer | NOT NULL, default 0 | **仅后台列表排序**(小在前);feed 是公平随机抽取,不看此字段 |
| `created_at` | DateTime(tz) | server_default now() | 创建时间 |
## 初始数据(migration 播种)
6 条 = 客户端原写死的轮播条目(金额转分、`min==max` 固定值):a52/18.60、k89/42.40、m71/7.90、p46/156.30、r93/23.50、c28/89.40。
(`marquee_seed02` 迁移把旧单值列 `saved_amount_cents` 拆成 `min_cents=max_cents`,并把 `masked_user` 改为可空。)
## 说明
- feed 取真实(`comparison_record` success 且 `0<saved≤300 元`,按 user 去重)优先,不足用本表启用种子**公平随机抽取**补齐(不再固定取前 N,所有种子都有机会露出)。种子用户名空则随机合成、金额区间随机取值。展示时间统一刷新成「相对现在的最近时刻」(详见 [platform-savings-feed](../api/platform-savings-feed.md))。
- 改种子走 admin `/admin/api/marquee-seeds` CRUD + 批量生成(`/bulk`)+ 预览(`/preview`),写操作带审计。
+13 -6
View File
@@ -16,8 +16,13 @@
| `random_mult_max` | Integer | NOT NULL, default 1100 | 倍率上限(千分比) |
| `random_tick_seconds` | Integer | NOT NULL, default 86400 | 更新间隔(秒) |
| `random_anchor_minutes` | Integer | NOT NULL, default 0 | 触发时刻对齐偏移(距北京 0 点分钟数):天=每日时刻 h*60+m、小时=每小时第几分、分钟=0 |
| `random_current` | Integer | nullable | random 当前累积值(基础单位);惰性 tick 改写 |
| `random_current` | Integer | nullable | 当前展示值(所有模式,基础单位);惰性 tick 改写 |
| `random_last_tick_at` | DateTime(tz) | nullable | 上次 tick 时刻 |
| `random_kind` | String(8) | NOT NULL, default `mult` | 自增长方式:`mult` ×倍率 / `add` +绝对增量 |
| `random_step_min` | Integer | NOT NULL, default 0 | add 模式增量下限(基础单位) |
| `random_step_max` | Integer | NOT NULL, default 0 | add 模式增量上限(基础单位) |
| `real_offset` | Integer | NOT NULL, default 0 | real 模式基数偏移(基础单位):展示 = 真实值 + 偏移 |
| `allow_decrease` | Boolean | NOT NULL, default false | 是否允许展示值下降(默认 false = 只增不减) |
| `updated_by_admin_id` | Integer | nullable | 最后修改的管理员 |
| `updated_at` | DateTime(tz) | server_default now(), onupdate now() | 更新时间 |
@@ -34,13 +39,15 @@
`random_current` 现是**所有模式**的「当前展示值」(不止 random),用户侧 `/stats` 直接返回它。
- 触发边界 = 北京时间 `anchor + k*interval`(interval=`random_tick_seconds`,anchor=`random_anchor_minutes*60`,sub-day 间隔取 `anchor % interval` 作相位)。例:每天 09:00 / 每小时 :30 / 每 5 分刻度。
- 读取(`get_display_values`/`get_config`)时跑 `_refresh`:统计 `random_last_tick_at``now` 跨过几个边界 N,N≥1 才刷新:
- **real** → `random_current` = 重新查库的真实值(跨多少边界都只取最新)
- **manual** → `random_current` = 当前 `manual_value`
- **random** → `random_current` 连乘 N 随机倍率(`randint(min,max)/1000`,恒 ≥1.0 只增不减)
- **real** → `random_current` = 重新查库的真实值 + `real_offset`(跨多少边界都只取最新),**经只增不减护栏**
- **manual** → `random_current` = 当前 `manual_value`,**经只增不减护栏**
- **random** → `random_kind` N 个周期:`mult` 连乘随机倍率(`randint(min,max)/1000`,恒 ≥1.0)/ `add` 连加随机增量(`randint(step_min,step_max)`,≥0)。天然只增
- **只增不减护栏**(`allow_decrease=false`,默认):real/manual 刷新时若新目标值 < 当前 `random_current`,保持当前值不回退(门面忌缩水)。开 `allow_decrease` 才允许下降。
- 用边界索引比较,`random_last_tick_at` 直接记 `now`,不重复计。
- ⚠️ **接受刷新时刻的微小并发竞态**(不加行锁):纯门面数字,无业务后果。
- `random_current` 首次为空时按当前模式播种(`update_config``_refresh` 兜底);`random_initial` 可立即设起点。
- `random_current` 首次为空时按当前模式播种(`update_config``_refresh` 兜底);`random_initial` 可立即设起点(不受护栏约束)
## 说明
- real 口径仅统计 `comparison_record``status='success'` 的记录(去重用户数 / 记录数 / `saved_amount_cents` 求和)。
- real 口径仅统计 `comparison_record``status='success'` 的记录(去重用户数 / 记录数)。`total_saved` 的求和**只计入 `0 < saved_amount_cents ≤ 300 元` 的条目**(剔除 bug 异常大值,与轮播口径一致,防一条异常配合「只增不减」永久撑高门面)。
- `倍率上限收紧为 1.500`(单次 ×1.5,原 5.0 太大一跳就假)。
- 改配置走 admin `PATCH /admin/api/dashboard-display/{metric}`,带审计。