docs: 修正 spec 成功率口径 + 派生逻辑落为共享模块
- 两页成功率分母其实都是 total-cancelled(概览 queries.py:532、大盘 stats.py:290), 一致、本次不动;真正的既有差异在分子(概览含 below_minimum、大盘不含), 本次统一为 original∈S,顺带对齐两页口径 - 派生逻辑落为独立模块 comparison_outcome.py,供 queries/stats 共用避免循环 import Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
- **日期**:2026-08-04
|
||||
- **范围**:后端 `shaguabijia-app-server`(admin 层,**方案 A**:无 schema 变更、无迁移、不碰 #209 落库与 C 端)+ 前端 `shaguabijia-admin-web`
|
||||
- **涉及文件**:
|
||||
- 后端:`app/admin/repositories/queries.py`(列表派生 + 概览口径)、`app/admin/schemas/comparison.py`(新增两字段)、`app/admin/repositories/stats.py`(大盘成功率分子口径)
|
||||
- 后端:`app/admin/repositories/comparison_outcome.py`(**新建**:共享常量 + Python 派生 + SQL 判定)、`app/admin/repositories/queries.py`(列表派生 + 概览口径)、`app/admin/schemas/comparison.py`(新增两字段)、`app/admin/repositories/stats.py`(大盘成功率分子口径)
|
||||
- 前端:`src/app/(main)/comparison-records/page.tsx`、`src/lib/types.ts`
|
||||
- 文档:`shaguabijia-app-server/docs/guides/比价结果卡片-状态口径与交互参考.md`(补 admin 口径说明)
|
||||
|
||||
@@ -56,29 +56,46 @@ outcome_hint: str | None = None # 缺失提示文案;None = 无缺失
|
||||
|
||||
## 后端实现(方案 A)
|
||||
|
||||
### 1. 常量 + 派生(`queries.py`,紧邻既有 `_COMPARISON_STATUS_ALIASES`)
|
||||
### 1. 新模块 `app/admin/repositories/comparison_outcome.py`(供 `queries.py` 与 `stats.py` 共用,避免循环 import)
|
||||
|
||||
```python
|
||||
_ADMIN_SUCCESS_OUTCOMES = frozenset({
|
||||
from sqlalchemy import func
|
||||
from app.models.comparison import ComparisonRecord
|
||||
|
||||
ADMIN_SUCCESS_OUTCOMES = frozenset({
|
||||
"success", "below_minimum", "store_closed",
|
||||
"store_not_found", "items_not_found", "no_delivery", "unsupported",
|
||||
})
|
||||
_OUTCOME_HINTS = {
|
||||
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]:
|
||||
def derive_admin_outcome(raw_payload: dict | None, status: str) -> tuple[str, str | None]:
|
||||
"""Python 层派生(列表用;raw_payload 已随 ORM 加载,零额外查询)。"""
|
||||
if status in ("cancelled", "running"):
|
||||
return status, None
|
||||
raw = raw_payload or {}
|
||||
original = raw.get("record_status") or raw.get("status")
|
||||
if original is None: # 极老记录兜底
|
||||
return ("success" if status == "success" else "failed"), None
|
||||
if original in _ADMIN_SUCCESS_OUTCOMES:
|
||||
return "success", _OUTCOME_HINTS.get(original)
|
||||
if original in ADMIN_SUCCESS_OUTCOMES:
|
||||
return "success", OUTCOME_HINTS.get(original)
|
||||
return "failed", None
|
||||
|
||||
def _original_expr():
|
||||
return func.coalesce(
|
||||
ComparisonRecord.raw_payload["record_status"].as_string(),
|
||||
ComparisonRecord.raw_payload["status"].as_string(),
|
||||
)
|
||||
|
||||
def admin_success_sql():
|
||||
"""SQL 层 admin 成功判定(概览/大盘的 case/where 共用)。"""
|
||||
original = _original_expr()
|
||||
return original.in_(tuple(ADMIN_SUCCESS_OUTCOMES)) | (
|
||||
original.is_(None) & (ComparisonRecord.status == "success")
|
||||
)
|
||||
```
|
||||
|
||||
### 2. 列表 `list_comparison_records`
|
||||
@@ -101,17 +118,17 @@ _original_expr = func.coalesce(
|
||||
ComparisonRecord.raw_payload["status"].as_string(),
|
||||
)
|
||||
# admin 成功 = original ∈ S OR (original IS NULL AND status == 'success')
|
||||
# completed = admin 成功 + 纯 failed(admin 口径失败)
|
||||
# success_rate = admin 成功 / completed
|
||||
# completed = admin 成功 + 纯 failed(展示字段;admin 口径)
|
||||
# success_rate = admin 成功 / (started - cancelled) # 分母不变,见 queries.py:532
|
||||
```
|
||||
|
||||
**耗时分位**(avg / p50 / p95,`_comparison_duration_aggregate*`):统计集从「`status == "success"`」改为「admin 成功集」——用户已确认**统一口径纳入**这 6 类。改动点:`_comparison_status_condition` 系的耗时过滤条件改用 `_original_expr ∈ S`(或复用一个 `_admin_success_condition()` 表达式,列表/概览/大盘共用)。
|
||||
|
||||
### 4. 大盘 `stats.py` `dashboard_overview`
|
||||
|
||||
`period_comparison_success`(分子,[stats.py:288](../../../shaguabijia-app-server/app/admin/repositories/stats.py) 的聚合 case)改用 `_original_expr ∈ S` 口径,与概览一致。
|
||||
`period_comparison_stats` 的 success 分子([stats.py:278-281](../../../shaguabijia-app-server/app/admin/repositories/stats.py) 的 `case(status=='success')`)现在**不含 below_minimum**(#209 只改了概览、没同步大盘)。改用 `admin_success_sql()`(`original ∈ S`)口径,与概览一致。
|
||||
|
||||
**范围界定(重要)**:大盘成功率分母是 `total - cancelled`([stats.py:290](../../../shaguabijia-app-server/app/admin/repositories/stats.py)),与比价记录页概览的 `completed`(success+failed)分母**本就不同**(running 的处理差异)。**此既有差异不在本次范围**——本次只统一「6 类算成功」这个**分子**口径;两页分母口径保持现状,避免波及大盘既有数字。
|
||||
**范围界定**:两页成功率**分母都是 `total - cancelled`**(概览 [queries.py:532](../../src/../../../shaguabijia-app-server/app/admin/repositories/queries.py)、大盘 [stats.py:290](../../../shaguabijia-app-server/app/admin/repositories/stats.py)),口径一致、本次不动。真正的既有差异在**分子**:概览 success 含 `below_minimum`、大盘不含。本次把两处分子都统一为 `original ∈ S`,改完两页口径**完全一致**。副作用:大盘成功率因补上 `below_minimum` + 5 类而上升,概览因补上 5 类上升——均属口径调整、非数据异常。
|
||||
|
||||
## 前端实现
|
||||
|
||||
|
||||
Reference in New Issue
Block a user