"""比价记录 endpoint(「我的比价记录」数据源)。 路由前缀 `/api/v1/compare`: POST /record 上报一次比价结果(幂等:同 user+trace_id 覆盖) GET /records 比价记录列表(游标分页) GET /records/{id} 单条详情(含 raw_payload 全量) **均需鉴权**(CurrentUser)——与同文件无关的不鉴权透传 `compare.py` 分开:那个是 转发壳(MVP 不鉴权),这里是按用户维度落库的业务接口,必须有 user_id。 注:本轮只做 server 端,客户端(android 仓)在 done 帧后调 POST /record 上报的改动 另起一轮(见 app-server docs/待办与技术债.md P1)。 """ from __future__ import annotations import logging from fastapi import APIRouter, HTTPException, Query, status from app.api.deps import CurrentUser, DbSession from app.repositories import comparison as crud_compare from app.schemas.compare_record import ( CompareStatsOut, ComparisonRecordCreatedOut, ComparisonRecordDetailOut, ComparisonRecordIn, ComparisonRecordPage, ComparisonRecordOut, ) logger = logging.getLogger("shagua.compare_record") router = APIRouter(prefix="/api/v1/compare", tags=["compare-record"]) @router.post( "/record", response_model=ComparisonRecordCreatedOut, summary="上报一次比价结果(幂等)", ) def report_record( payload: ComparisonRecordIn, user: CurrentUser, db: DbSession ) -> ComparisonRecordCreatedOut: rec = crud_compare.upsert_record(db, user_id=user.id, payload=payload) logger.info( "compare record user=%s trace=%s biz=%s status=%s saved=%s", user.id, rec.trace_id, rec.business_type, rec.status, rec.saved_amount_cents, ) return ComparisonRecordCreatedOut(id=rec.id) @router.get( "/stats", response_model=CompareStatsOut, summary="比价口径战绩(完成比价数 + 累计发现可省)", ) def stats(user: CurrentUser, db: DbSession) -> CompareStatsOut: count, saved = crud_compare.get_stats(db, user.id) return CompareStatsOut(compare_count=count, discovered_saved_cents=saved) @router.get( "/records", response_model=ComparisonRecordPage, summary="比价记录列表(游标分页)", ) def list_records( user: CurrentUser, db: DbSession, limit: int = Query(20, ge=1, le=100), cursor: int | None = Query(None, description="上一页末条 id"), ) -> ComparisonRecordPage: items, next_cursor = crud_compare.list_records( db, user.id, limit=limit, cursor=cursor ) return ComparisonRecordPage( items=[ComparisonRecordOut.model_validate(it) for it in items], next_cursor=next_cursor, ) @router.get( "/records/{record_id}", response_model=ComparisonRecordDetailOut, summary="比价记录详情(含 raw_payload)", ) def get_record( record_id: int, user: CurrentUser, db: DbSession ) -> ComparisonRecordDetailOut: rec = crud_compare.get_record(db, user.id, record_id) if rec is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="record not found") return ComparisonRecordDetailOut.model_validate(rec)