fix(notifications): deterministic tiebreaker for paginated notification log list

Same-class gap as e369508 (alerts list): single-key ORDER BY created_at DESC plus offset/limit lets MySQL filesort return an unstable order inside same-second tie groups, so offset pagination duplicates and skips rows. Append secondary key id DESC, aligning with app/api/alerts.py, app/api/cash.py and app/api/data_quality.py.

Entity-scope caliber untouched: parent-key join attribution, alert_id IS NULL rows still excluded, policy/note wording unchanged. Local commit only - no push (release gated by ASK-20260925-04).
This commit is contained in:
Hermes CI Fix
2026-09-25 16:34:52 +08:00
parent e3695088a4
commit 38787a006f
2 changed files with 222 additions and 1 deletions
+6 -1
View File
@@ -124,8 +124,13 @@ def list_logs(page: int = 1,
.join(KPIAlert, NotificationLog.alert_id == KPIAlert.id)
.filter(KPIAlert.entity_id == entity_id))
total = scoped.count()
# 排序补**确定性次键** `id DESC`(任务 0-P3-project-20260925-cma-same-class-pagination-determinism-fullstack):
# 通知日志由预警推送批量写入,同一秒多行时 MySQL filesort 在并列组内顺序不受控 ⇒ 单键
# `created_at DESC` + offset/limit 会让偏移分页重复并漏行。两键后偏移分页成为全序分页。
# 同族先例:app/api/alerts.py(e369508)、app/api/cash.py:165、app/api/data_quality.py:230。
# 本行**不改**任何账套收口口径(join 归属 / `alert_id IS NULL` 不返回 / policy+note 文案)。
logs = scoped.order_by(
NotificationLog.created_at.desc()
NotificationLog.created_at.desc(), NotificationLog.id.desc()
).offset((page - 1) * 20).limit(20).all()
return {
"total": total,