Compare commits

..

1 Commits

Author SHA1 Message Date
guke 7c241b4772 比价记录失败卡展示具体原因(新增 fail_reason)
失败记录不再一律「网络开小差」:新增记录级 fail_reason 派生列——information
具体则直出,笼统则从 platform_results 救出业务原因(找不到店/菜、未起送、打烊、
单点不配送等),纯系统失败为 None → 端侧品牌兜底。store_closed/no_delivery 被
pricebot 漏成 status=failed 的按 reason 补判,打烊脏店名统一简短模板。接入
harvest_done 与灰度期 upsert_record 两条写路径。

- models: comparison_record.fail_reason 列
- repositories: _derive_fail_display + 补判/清洗 helper,两条写路径接入
- schemas: ComparisonRecordOut 暴露 fail_reason
- alembic: 加列 + 回填老 specific 失败记录
- tests: _derive_fail_display 单测(8 例)+ harvest 失败落库集成测试

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 13:13:14 +08:00
4 changed files with 5 additions and 20 deletions
+3 -11
View File
@@ -108,7 +108,7 @@ def _harvest_done_blocking(
def _harvest_abort_blocking(
trace_id: str, status_hint: str, reason: str | None, trace_url: str | None,
) -> int | None:
) -> None:
with SessionLocal() as db:
rec = crud_compare.harvest_abort(
db, trace_id=trace_id, status=status_hint, reason=reason, trace_url=trace_url,
@@ -118,7 +118,6 @@ def _harvest_abort_blocking(
extra={"phase": "harvest_abort",
"status": (rec.status if rec else None), "reason": reason},
)
return rec.id if rec is not None else None
async def _forward(
@@ -292,10 +291,7 @@ async def trace_epilogue(
@router.post("/trace/finalize", summary="比价 trace 收尾上云 (透传 + 夭折落库)")
async def trace_finalize(
request: Request,
background_tasks: BackgroundTasks,
user: OptionalUser,
db: DbSession,
request: Request, user: OptionalUser, db: DbSession
) -> dict[str, Any]:
_ensure_compare_allowed(user, db)
# 用户终止 / Phase1 未识别没到 done 帧: pricebot 打包半截上云返回 {trace_url};
@@ -306,16 +302,12 @@ async def trace_finalize(
request, "/api/trace/finalize", user, harvest_first_frame=False,
)
try:
record_id = await run_in_threadpool(
await run_in_threadpool(
_harvest_abort_blocking, trace_id,
(meta.get("status") or "cancelled"),
(meta.get("reason") or meta.get("information")),
(resp.get("trace_url") if isinstance(resp, dict) else None),
)
if record_id is not None:
background_tasks.add_task(
backfill_comparison_llm_cost, record_id, trace_id
)
except Exception as e: # noqa: BLE001
logger.warning("harvest_abort failed trace=%s: %s", trace_id, e)
return resp
+1 -1
View File
@@ -135,7 +135,7 @@ def repair_missing_comparison_llm_costs(
select(ComparisonRecord.id, ComparisonRecord.trace_id)
.where(
*date_conditions,
ComparisonRecord.status.in_(("success", "failed", "cancelled")),
ComparisonRecord.status.in_(("success", "failed")),
ComparisonRecord.llm_cost_yuan.is_(None),
)
.order_by(ComparisonRecord.created_at.desc(), ComparisonRecord.id.desc())
+1 -4
View File
@@ -263,9 +263,7 @@ def test_trace_finalize_harvests_abort(client) -> None:
with SessionLocal() as db: # 先有 running 行(帧0建的)
crud.harvest_running(db, trace_id=tid, user_id=None)
p, _cap = _mock_pricebot({"trace_url": "https://price.shaguabijia.com/traces/fin/"})
with p, patch(
"app.api.v1.compare.backfill_comparison_llm_cost"
) as backfill:
with p:
r = client.post("/api/v1/trace/finalize",
json={"trace_id": tid, "status": "cancelled", "reason": "用户终止"})
assert r.status_code == 200
@@ -273,7 +271,6 @@ def test_trace_finalize_harvests_abort(client) -> None:
rec = _get(db, tid)
assert rec is not None and rec.status == "cancelled"
assert rec.trace_url.endswith("/fin/")
backfill.assert_called_once_with(rec.id, tid)
def test_price_step_binds_user_when_authed(client) -> None:
-4
View File
@@ -72,7 +72,6 @@ def test_backfill_retries_then_persists_cost(monkeypatch):
def test_repair_batch_only_targets_terminal_missing_rows(monkeypatch):
missing_id = _record("llm-repair-missing")
cancelled_id = _record("llm-repair-cancelled", status="cancelled")
running_id = _record("llm-repair-running", status="running")
calls = [
{
@@ -97,15 +96,12 @@ def test_repair_batch_only_targets_terminal_missing_rows(monkeypatch):
)
assert result["repaired"] >= 1
assert "llm-repair-missing" in seen
assert "llm-repair-cancelled" in seen
assert "llm-repair-running" not in seen
with SessionLocal() as db:
assert db.get(ComparisonRecord, missing_id).llm_cost_yuan is not None
assert db.get(ComparisonRecord, cancelled_id).llm_cost_yuan is not None
assert db.get(ComparisonRecord, running_id).llm_cost_yuan is None
finally:
_delete(missing_id)
_delete(cancelled_id)
_delete(running_id)