f5d1a1a20d
比价沉淀的价格事实(平台/门店/菜篮/到手价/时间/来源)无条件落库,server 侧存储、 与登录无关,独立于用户视角的 comparison_record。是未来"别人查过同店→秒回价格" 价格大数据的源头(先存数据,用法后续)。 - models/price_observation.py: 新增 price_observation 表。结构化列(平台/门店/价格/ 红包/地理/来源)+ dishes/attrs 两个 JSONB 兜底列;(trace_id,platform,scope) 幂等 唯一约束;observed_at/store_name/geohash/source_device_id/source_user_id 等索引 - alembic/versions/price_observation_table.py: 建表迁移,down_revision=wx_transfer_auth (已核为当前唯一 head,无多 head 风险) - schemas/ + repositories/price_observation.py: 批量收发模型 + 跨方言(PG/SQLite)幂等 批量插入(先查 trace 已有 (platform,scope) 只插新的,并发撞唯一约束则回滚跳过) - api/internal/price.py: 新增 POST /internal/price-observation,X-Internal-Secret 头校验 (常量时间比较,未配密钥→503),server→server 专用,不走用户 JWT - core/config.py + main.py: 新增 INTERNAL_API_SECRET 配置 + 注册内部路由 - .env.example: INTERNAL_API_SECRET(须与 pricebot 侧同值,留空=内部写端点关闭) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: pure <pure@192.168.0.104> Reviewed-on: #21
50 lines
1.8 KiB
Python
50 lines
1.8 KiB
Python
"""价格观测内部上报的收发模型。
|
|
|
|
pricebot 在比价 done 帧 server→server POST 一批观测(一次比价 = 源 + N 目标平台各一条)。
|
|
批级字段(门店 / 菜篮 / 地理 / 来源)所有观测共享,放外层;逐平台字段放 observations[]。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class PriceObservationItem(BaseModel):
|
|
"""单个平台的价格观测(一次比价里的一行)。"""
|
|
|
|
platform: str
|
|
is_source: bool = False
|
|
scope: str = "order" # order / dish
|
|
price_cents: int | None = None # 到手价(分);None=该平台失败/打烊
|
|
coupon_saved_cents: int | None = None
|
|
coupon_name: str | None = None
|
|
store_closed: str | None = None # 打烊原因(非空时 price_cents=None)
|
|
rank: int | None = None # 全局名次(1=最便宜)
|
|
platform_store_id: str | None = None
|
|
attrs: dict | None = None # 该平台的灵活明细兜底
|
|
|
|
|
|
class PriceObservationBatchIn(BaseModel):
|
|
"""一次比价的整批价格观测上报体。"""
|
|
|
|
trace_id: str
|
|
business_type: str = "food"
|
|
# 门店 / 菜篮 / 地理 —— 一次比价同一份,批级共享
|
|
store_name: str | None = None
|
|
city: str | None = None
|
|
geohash: str | None = None
|
|
lng: float | None = None
|
|
lat: float | None = None
|
|
dishes: list | None = None # [{name, qty, subtotal?}]
|
|
# 来源
|
|
source_device_id: str | None = None
|
|
source_user_id: int | None = None
|
|
# 逐平台观测(源 + 各目标)
|
|
observations: list[PriceObservationItem] = Field(default_factory=list)
|
|
|
|
|
|
class PriceObservationBatchOut(BaseModel):
|
|
"""上报结果:本批新写入条数 / 因幂等跳过条数。"""
|
|
|
|
inserted: int
|
|
skipped: int
|