feat(db_compare): DB-based full summary report + wire 跑比对 to it
新增 build_full_report:4 站走 compare_site_date、百世走 baishi_daily_stats(基数) + undelivered_record(按 ingested_at 日期过滤),复用 compare.build_summary 渲染 KPI/柱状图/口径说明,产 output/应到未到数据.xlsx。跑比对入口(__compare__)从 compare.main() Excel 路径切换到 build_full_report。百世未到件用基数差(undelivered_pieces)与应到/已到自洽。 集成验证:前端跑比对 -> build_full_report -> /report 下载,各站未到件 顺心8/中通10/韵达2/安能0/百世8。 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,7 @@ COUNT(DISTINCT piece_no) 去重计数,改为 COUNT(*) 行计数。
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import date, datetime
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
import psycopg
|
||||
import yaml
|
||||
@@ -33,7 +33,7 @@ from openpyxl import Workbook
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
|
||||
from inbound_verify.paths import CONFIG_PATH, OUTPUT_DIR, DOWNLOAD_DIR
|
||||
from inbound_verify.domain import _site_cfg, ALL_REPORT_SITES
|
||||
from inbound_verify.domain import _site_cfg, ALL_REPORT_SITES, BAISHI_COLUMNS
|
||||
|
||||
# ============================== 结果类型 ==============================
|
||||
|
||||
@@ -483,6 +483,130 @@ def _write_sheet(ws, result: CompareResult):
|
||||
ws.freeze_panes = "A2"
|
||||
|
||||
|
||||
# ============================== 全站汇总报表(DB 版)=============================
|
||||
|
||||
|
||||
def _stats_to_dict(s: CompareStats) -> dict:
|
||||
"""CompareStats -> build_summary 要的中文键 stats dict。"""
|
||||
return {
|
||||
"运单数": s.waybill_count,
|
||||
"应到件": s.expected_pieces,
|
||||
"已到件": s.arrived_pieces,
|
||||
"未到件": s.undelivered_pieces,
|
||||
"完全未到": s.full_miss,
|
||||
"部分未到": s.part_miss,
|
||||
}
|
||||
|
||||
|
||||
def _target_date_for(site: str) -> str:
|
||||
"""4 站比对锚点:today - expected_offset(与 _site_undelivered_handler 一致)。"""
|
||||
from inbound_verify import state_store # 懒导入,避免成环
|
||||
|
||||
offset = state_store.get_offset(site, "expected")
|
||||
return (date.today() - timedelta(days=offset)).strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
def _baishi_from_pg(cur, target: str):
|
||||
"""查百世当日基数(baishi_daily_stats)+ 当天未到明细(undelivered_record 按 ingested_at 过滤)。
|
||||
返回 (stats_dict_or_None, rows_or_None);基数与明细均无 → (None, None)。
|
||||
|
||||
undelivered_record 是 UPSERT 累积表;按 ingested_at::date = target 取当天入库的未到快照
|
||||
(= 当天下载的当前未到,站点已剔除已到),避免累积偏大。
|
||||
"""
|
||||
cur.execute(
|
||||
"SELECT expected_pieces, arrived_pieces, undelivered_pieces "
|
||||
"FROM baishi_daily_stats WHERE site = %s AND business_date = %s",
|
||||
("百世", target),
|
||||
)
|
||||
basis = cur.fetchone()
|
||||
cur.execute(
|
||||
"SELECT waybill_no, piece_no, biz_type, last_scan FROM undelivered_record "
|
||||
"WHERE site = %s AND ingested_at::date = %s",
|
||||
("百世", target),
|
||||
)
|
||||
detail = cur.fetchall()
|
||||
if basis is None and not detail:
|
||||
return (None, None)
|
||||
exp = basis[0] if basis else None
|
||||
arr = basis[1] if basis else None
|
||||
# 未到件优先取基数差(baishi_daily_stats.undelivered_pieces,与应到/已到同源自洽);
|
||||
# 基数缺失时退回明细行数。
|
||||
undel = basis[2] if (basis and basis[2] is not None) else len(detail)
|
||||
wb_count = len({r[0] for r in detail if r[0]}) # 运单号去重
|
||||
rows = [
|
||||
{
|
||||
"类型": r[2] or "",
|
||||
"子单号": r[1] or "",
|
||||
"运单号": r[0] or "",
|
||||
"最新扫描记录": r[3] or "",
|
||||
}
|
||||
for r in detail
|
||||
]
|
||||
stats = {
|
||||
"运单数": wb_count,
|
||||
"应到件": exp,
|
||||
"已到件": arr,
|
||||
"未到件": undel,
|
||||
"完全未到": None,
|
||||
"部分未到": None,
|
||||
}
|
||||
return (stats, rows)
|
||||
|
||||
|
||||
def build_full_report(date=None) -> str:
|
||||
"""DB 版全站汇总报表:4 站走 DB 比对、百世走 PG,复用 compare.build_summary 渲染。
|
||||
产出 output/应到未到数据.xlsx(/report 下载)。date=None 时各站按 expected_offset 算锚点。
|
||||
返回输出路径。"""
|
||||
from inbound_verify import compare # 复用 build_summary / write_station / OUTFILE
|
||||
|
||||
print("[db_compare] 开始生成全站汇总报表 ...")
|
||||
wb = Workbook()
|
||||
wb.remove(wb.active)
|
||||
summary_ws = wb.create_sheet("汇总报表")
|
||||
|
||||
results = [] # [(name, stats_dict_or_None)],顺序 ALL_REPORT_SITES
|
||||
site_targets = {} # name -> target_date(汇总表"数据日期"列)
|
||||
|
||||
conn = _connect()
|
||||
cur = conn.cursor()
|
||||
try:
|
||||
for name in ALL_REPORT_SITES:
|
||||
if name == "百世":
|
||||
target = date or datetime.now().strftime("%Y-%m-%d")
|
||||
site_targets[name] = target
|
||||
stats, rows = _baishi_from_pg(cur, target)
|
||||
results.append((name, stats))
|
||||
if rows is not None:
|
||||
compare.write_station(wb.create_sheet(name), BAISHI_COLUMNS, rows)
|
||||
continue
|
||||
if name not in SITE_COMPARE_CONFIG:
|
||||
results.append((name, None))
|
||||
continue
|
||||
target = date or _target_date_for(name)
|
||||
site_targets[name] = target
|
||||
result = compare_site_date(name, target)
|
||||
if result is not None:
|
||||
results.append((name, _stats_to_dict(result.stats)))
|
||||
_write_sheet(wb.create_sheet(name), result)
|
||||
else:
|
||||
results.append((name, None))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
compare.build_summary(
|
||||
summary_ws,
|
||||
results,
|
||||
datetime.now().strftime("%Y-%m-%d %H:%M"),
|
||||
dates=site_targets,
|
||||
)
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
wb.save(compare.OUTFILE)
|
||||
print(f"[db_compare] 全站汇总已输出: {compare.OUTFILE}")
|
||||
for name, s in results:
|
||||
print(f" {name}:未到 {s['未到件']} 件" if s else f" {name}:无数据,跳过")
|
||||
return compare.OUTFILE
|
||||
|
||||
|
||||
# ============================== 终端验证入口 ==============================
|
||||
|
||||
|
||||
|
||||
@@ -535,7 +535,19 @@ def _site_undelivered_handler(site):
|
||||
return handler
|
||||
|
||||
|
||||
# 「跑比对」= 纯离线比对(用 downloads/ 现有文件生成全站汇总;下载交由各站定时/手动)。
|
||||
# 「跑比对」= DB 版全站汇总报表(替代旧 compare.main Excel 路径;下载交由各站定时/手动)。
|
||||
|
||||
|
||||
def _run_db_full_report(date=None):
|
||||
"""生成 DB 版全站汇总报表(output/应到未到数据.xlsx)。
|
||||
懒导入 db_compare,best-effort:失败只告警,返回 True(与旧 lambda 契约一致)。"""
|
||||
try:
|
||||
from inbound_verify import db_compare
|
||||
|
||||
db_compare.build_full_report(date)
|
||||
except Exception as e:
|
||||
print(f">> [跑比对] DB 汇总报表生成失败: {e}")
|
||||
return True
|
||||
|
||||
|
||||
TASK_HANDLERS = {
|
||||
@@ -564,8 +576,8 @@ TASK_HANDLERS = {
|
||||
force=force, date=date
|
||||
),
|
||||
("安能", "undelivered"): _site_undelivered_handler("安能"),
|
||||
("__compare__", "compare"): lambda ctx, force=False, date=None: (
|
||||
compare.main() or True
|
||||
("__compare__", "compare"): lambda ctx, force=False, date=None: _run_db_full_report(
|
||||
date
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user