From 1f62aca6f7234e048feea6eabc35831ef489f47f Mon Sep 17 00:00:00 2001 From: Windows <19353512109@163.com> Date: Sat, 12 Sep 2026 17:32:24 +0800 Subject: [PATCH] =?UTF-8?q?fix(seed):=20=E5=BD=93=E6=97=A5=E8=A1=8C?= =?UTF-8?q?=E6=83=85=E9=87=8D=E8=B7=91=E5=BF=85=E9=A1=BB=E5=88=B7=E6=96=B0?= =?UTF-8?q?=20source=5Fupdated=5Fat=EF=BC=88=E4=BF=AE=20503=20FUND=5FQUOTE?= =?UTF-8?q?=5FUNAVAILABLE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机实测到的缺陷:`tools/seed_sim_account_demo.py` 的 `_upsert_market_price()` 在"当天已有行"时直接 `return`,于是同一天重跑种子**不刷新 `source_updated_at`**。 行情是时效数据,过期后 `FundQuoteService` 返回 `503 FUND_QUOTE_UNAVAILABLE:产品 510300 行情已过期`, 连带 `T001` 仪表盘与 `T006` 持仓一起不可用(`T010` 权益不查行情,仍 200)。 表现极具误导性:**刚灌完种子能用,过十几分钟就 503** —— 看起来像行情适配器或缓存故障,实际根因在种子脚本。归因过程: `memory_sync` / Redis / Milvus 全部正常,`fin_market_price` 里当天那行 `source_updated_at` 停在首次灌入时刻。 修法:把列值抽成共享 `values` 字典,当天已有行时 `update` 刷新全部行情列 (含 `source_updated_at`),不存在才 `insert`。 **幂等的正确含义是"不产生重复行"(`(product_id, trade_date)` 唯一), 不是"不更新值"。** 账户/持仓的"已存在则跳过"保持不变 —— 那是业务数据,不该被种子覆盖。 验证(本机,测试客户 9001 `cust_t`): - `python -X utf8 -m tools.seed_sim_account_demo --customer-id 9001` 正常 - `GET /api/v1/users/me/account/dashboard` → 200(修前 503) - `GET /api/v1/users/me/holdings` → 200 - `GET /api/v1/users/me/entitlements` → 200 门禁:ruff `app tests tools alembic` 全过;`mypy app` 0 错(252 文件); `audit_schema.py` 90 张业务表无差异;文档编号/端点编号/RBAC 种子一致性全过。 --- tools/seed_sim_account_demo.py | 54 +++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/tools/seed_sim_account_demo.py b/tools/seed_sim_account_demo.py index 311b808..1256269 100644 --- a/tools/seed_sim_account_demo.py +++ b/tools/seed_sim_account_demo.py @@ -26,7 +26,7 @@ import sys from datetime import UTC, datetime, timedelta from decimal import Decimal -from sqlalchemy import func, insert, select +from sqlalchemy import func, insert, select, update from sqlalchemy.orm import Session from app.core.config import get_settings @@ -118,7 +118,32 @@ async def _upsert_product(session: Session, spec: dict) -> int: async def _upsert_market_price(session: Session, product_id: int, spec: dict) -> None: + """写入/刷新当日行情。 + + ⚠️ 当天已有行时**必须刷新**,不能直接 return(2026-09-12 实测到的缺陷): + + 行情是**时效数据**,`FundQuoteService` 会按 `source_updated_at` 判新鲜度, + 过期即返回 `503 FUND_QUOTE_UNAVAILABLE`,连带 `T001` 仪表盘与 `T006` 持仓一起不可用。 + 原先"已存在就跳过"会让同一天重跑**不更新 `source_updated_at`** —— + 表现为"刚灌完种子能用,过十几分钟仪表盘就 503",而这与种子无关、极难归因。 + + 幂等的正确含义是**不产生重复行**(`(product_id, trade_date)` 唯一), + **不是"不更新值"**。账户/持仓的"已存在则跳过"是另一回事 —— + 那是业务数据,不该被种子覆盖。 + """ today = datetime.now(UTC).date() + now = datetime.now(UTC).replace(tzinfo=None) + values = { + "open_price": spec["close_price"], + "high_price": spec["close_price"] + Decimal("0.05"), + "low_price": spec["close_price"] - Decimal("0.05"), + "close_price": spec["close_price"], + "volume": Decimal("1000000"), + "turnover_amount": spec["close_price"] * Decimal("1000000"), + "total_fund_shares": spec["total_fund_shares"], + "source": "eastmoney_demo_seed", + "source_updated_at": now, + } existing = ( await session.execute( select(FundMarketPrice.id).where( @@ -128,25 +153,20 @@ async def _upsert_market_price(session: Session, product_id: int, spec: dict) -> ) ).scalar_one_or_none() if existing is not None: + await session.execute( + update(FundMarketPrice).where(FundMarketPrice.id == existing).values(**values) + ) return - now = datetime.now(UTC).replace(tzinfo=None) next_id = await _next_id(session, FundMarketPrice) - stmt = insert(FundMarketPrice).values( - id=next_id, - product_id=product_id, - trade_date=today, - open_price=spec["close_price"], - high_price=spec["close_price"] + Decimal("0.05"), - low_price=spec["close_price"] - Decimal("0.05"), - close_price=spec["close_price"], - volume=Decimal("1000000"), - turnover_amount=spec["close_price"] * Decimal("1000000"), - total_fund_shares=spec["total_fund_shares"], - source="eastmoney_demo_seed", - source_updated_at=now, - created_at=now, + await session.execute( + insert(FundMarketPrice).values( + id=next_id, + product_id=product_id, + trade_date=today, + created_at=now, + **values, + ) ) - await session.execute(stmt) async def _upsert_account(session: Session, customer_id: int) -> FundSimAccount: