feat(admin): 比价记录展示口径共享模块(外部缺失判为成功)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
guke
2026-08-04 15:35:32 +08:00
parent 9036bc5a08
commit 9de73152ec
2 changed files with 93 additions and 0 deletions
@@ -0,0 +1,60 @@
"""admin 比价记录展示口径:把「流程跑完、外部原因致结果缺失」的记录判为成功。
#209 / C 端落库把 store_not_found / items_not_found / store_closed / no_delivery /
unsupported 归一化成 status='failed';admin 排查视角改按记录级原始业务结局
raw_payload.record_status)重判——这些「外部缺失」算成功(附缺失提示),
只有纯技术故障才是失败。仅 admin 用,不碰 C 端 / #209 落库。
"""
from __future__ import annotations
from sqlalchemy import func
from app.models.comparison import ComparisonRecord
# 记录级原始业务结局里算「成功(流程跑完)」的集合;其余(failed / 未知)才是技术故障。
ADMIN_SUCCESS_OUTCOMES = frozenset({
"success", "below_minimum", "store_closed",
"store_not_found", "items_not_found", "no_delivery", "unsupported",
})
# 有缺失的成功 → 感叹号 hover 提示;success 本身无提示。
OUTCOME_HINTS = {
"below_minimum": "未满起送",
"store_closed": "门店打烊",
"store_not_found": "未找到店",
"items_not_found": "未找到菜",
"no_delivery": "单点不配送",
"unsupported": "平台·场景不支持",
}
def derive_admin_outcome(raw_payload: dict | None, status: str) -> tuple[str, str | None]:
"""(admin_status, outcome_hint)。列表 Python 层派生(raw_payload 已随 ORM 加载)。"""
if status in ("cancelled", "running"):
return status, None
raw = raw_payload or {}
# 原始结局:优先 raw.record_status,其次 raw.status,兜底 status 列
# (兼容迁移未覆盖、细分值残留在 status 列的老记录;与下方 SQL 口径一致)。
original = raw.get("record_status") or raw.get("status") or status
if original in ADMIN_SUCCESS_OUTCOMES:
return "success", OUTCOME_HINTS.get(original)
return "failed", None
def _original_expr():
"""SQL:原始结局 = coalesce(raw.record_status, raw.status, status 列)。跨方言(as_string#209 迁移已验证)。"""
return func.coalesce(
ComparisonRecord.raw_payload["record_status"].as_string(),
ComparisonRecord.raw_payload["status"].as_string(),
ComparisonRecord.status,
)
def admin_success_sql():
"""SQL 层 admin 成功判定(概览 / 大盘的 case / where 共用)。
排除 cancelled / running(生命周期态,不看结局);其余按原始结局 ∈ S。
coalesce 兜底 status 列 → original 永非 NULL、且兼容 status 列残留的细分值。
"""
return ComparisonRecord.status.notin_(("cancelled", "running")) & _original_expr().in_(
tuple(ADMIN_SUCCESS_OUTCOMES)
)
+33
View File
@@ -0,0 +1,33 @@
"""admin 展示口径派生单测:记录级原始结局 → (admin_status, outcome_hint)。"""
from __future__ import annotations
import pytest
from app.admin.repositories.comparison_outcome import derive_admin_outcome
@pytest.mark.parametrize(
("raw_payload", "status", "expected"),
[
# 真实形态:status 已 normalize,细分在 raw_payload.record_status
({"record_status": "success"}, "success", ("success", None)),
({"record_status": "below_minimum"}, "success", ("success", "未满起送")),
({"record_status": "store_closed"}, "failed", ("success", "门店打烊")),
({"record_status": "store_not_found"}, "failed", ("success", "未找到店")),
({"record_status": "items_not_found"}, "failed", ("success", "未找到菜")),
({"record_status": "no_delivery"}, "failed", ("success", "单点不配送")),
({"record_status": "unsupported"}, "failed", ("success", "平台·场景不支持")),
({"record_status": "failed"}, "failed", ("failed", None)), # 纯技术故障
# POST 路径:细分在 raw_payload.status
({"status": "store_not_found"}, "failed", ("success", "未找到店")),
# 兜底 status 列:raw_payload 缺失(极老记录)或残留细分值
(None, "success", ("success", None)),
(None, "failed", ("failed", None)),
(None, "store_closed", ("success", "门店打烊")), # 迁移未覆盖的残留
# 生命周期态优先,不看结局
({}, "cancelled", ("cancelled", None)),
({"record_status": "success"}, "running", ("running", None)),
],
)
def test_derive_admin_outcome(raw_payload, status, expected):
assert derive_admin_outcome(raw_payload, status) == expected