feat(store): persist 百世 daily basis to PG (baishi_daily_stats)

百世应到/实到基数(应扫/已扫)原仅在 state_store(单值、无历史)。新增百世专用聚合表 baishi_daily_stats(site+business_date UPSERT),baishi 下载时抓到基数直接落库(store.upsert_baishi_daily_stats,一步,不绕 state_store→store)。state_store 双写保留以兼容旧 Excel 汇总(process_baishi),后续统一清理。

真机端到端验证通过:PG (2026-07-31, 194, 186, 8),state_store 一致。

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-07-31 21:23:08 +08:00
parent c6ad6a0ca2
commit 8521c200ab
3 changed files with 58 additions and 0 deletions

View File

@@ -200,6 +200,11 @@ def baishi_download_undelivered_data_impl(page):
if _exp_n > 0: if _exp_n > 0:
state_store.set_setting("百世", "scan_expected_pieces", str(_exp_n)) state_store.set_setting("百世", "scan_expected_pieces", str(_exp_n))
state_store.set_setting("百世", "scan_arrived_pieces", str(_arr_n)) state_store.set_setting("百世", "scan_arrived_pieces", str(_arr_n))
from inbound_verify import (
store,
) # 直接落库 PG一步不绕 state_store→store
store.upsert_baishi_daily_stats(_exp_n, _arr_n)
print(f" 已记录百世应到/实到基数:应扫 {_exp_n} / 已扫 {_arr_n}") print(f" 已记录百世应到/实到基数:应扫 {_exp_n} / 已扫 {_arr_n}")
except Exception as _e: except Exception as _e:
# 抓取失败绝不影响未到明细下载主流程 # 抓取失败绝不影响未到明细下载主流程

View File

@@ -268,6 +268,18 @@ _SQL_UNDELIVERED = """
ingested_at = now() ingested_at = now()
""" """
_SQL_BAISHI_DAILY_STATS = """
INSERT INTO baishi_daily_stats
(site, business_date, expected_pieces, arrived_pieces, undelivered_pieces, raw)
VALUES (%s,%s,%s,%s,%s,%s)
ON CONFLICT (site, business_date) DO UPDATE SET
expected_pieces = COALESCE(EXCLUDED.expected_pieces, baishi_daily_stats.expected_pieces),
arrived_pieces = COALESCE(EXCLUDED.arrived_pieces, baishi_daily_stats.arrived_pieces),
undelivered_pieces = COALESCE(EXCLUDED.undelivered_pieces, baishi_daily_stats.undelivered_pieces),
raw = EXCLUDED.raw,
ingested_at = now()
"""
# ============================== 入库 ============================== # ============================== 入库 ==============================
@@ -369,6 +381,34 @@ def _ingest_undelivered_baishi(cur):
return len(rows) return len(rows)
def upsert_baishi_daily_stats(exp, arr, business_date=None):
"""直接落库百世当日应到/实到基数(应扫/已扫,站级日聚合)。
供 baishi 下载时抓到基数后直接调用(一步落库,不绕 state_store→store
business_date 默认今天百世固定当天。best-effort失败只告警不影响下载流程。"""
biz = business_date or date.today()
if exp is None and arr is None:
return
undel = (exp - arr) if (exp is not None and arr is not None) else None
try:
with _connect(_load_pg_config()["dbname"]) as conn:
with conn.cursor() as cur:
cur.execute(
_SQL_BAISHI_DAILY_STATS,
(
"百世",
biz,
exp,
arr,
undel,
Jsonb({"expected": exp, "arrived": arr, "undelivered": undel}),
),
)
conn.commit()
print(f" [基数] 百世 {biz}: 应扫 {exp} / 已扫 {arr} / 未扫 {undel}")
except Exception as e:
print(f" [基数] 百世 {biz} 入库失败(不影响下载): {e}")
def ingest(site=None): def ingest(site=None):
"""入库:指定 site 则单站(百世只入未到),否则全站。返回总条数。""" """入库:指定 site 则单站(百世只入未到),否则全站。返回总条数。"""
dates = _read_business_dates() dates = _read_business_dates()

View File

@@ -53,3 +53,16 @@ CREATE TABLE IF NOT EXISTS undelivered_record (
ingested_at TIMESTAMPTZ NOT NULL DEFAULT now(), ingested_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (site, piece_no) UNIQUE (site, piece_no)
); );
-- 百世日聚合(应扫/已扫基数:站级日聚合,区别于运单级/件级/子单级表)
CREATE TABLE IF NOT EXISTS baishi_daily_stats (
id BIGSERIAL PRIMARY KEY,
site TEXT NOT NULL, -- 百世
business_date DATE NOT NULL, -- 业务日期(百世固定当天)
expected_pieces INTEGER, -- 应扫(应到基数)
arrived_pieces INTEGER, -- 已扫(实到基数)
undelivered_pieces INTEGER, -- 未扫(=应扫-已扫,任一缺失则 NULL
raw JSONB NOT NULL, -- 原始抓取值
ingested_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (site, business_date)
);