feat(db_compare): add PostgreSQL-based comparison engine with SF handling
Replace Excel-based undelivered comparison with DB queries for all four sites. The engine anchors on actual scan_time, reverse-lookups handover batches, and compares expected vs actual waybill-by-waybill. Shunxin SF waybills: use COUNT(*) instead of COUNT(DISTINCT piece_no) since SF piece numbers are random and not derivable from the waybill. Changes: - db_compare.py: new module with compare_site_date(), compare_site_batch(), write_result_excel(), and POST /compare API endpoint - runtime.py: switch _site_undelivered_handler from compare.write_site_file (Excel) to db_compare (DB); downloads succeed independently of comparison - server.py: add POST /compare endpoint with date validation - docs: implementation plan for Shunxin DB comparison Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ from inbound_verify.runtime import (
|
||||
launch_and_prepare,
|
||||
run_heartbeat,
|
||||
)
|
||||
from inbound_verify import db_compare
|
||||
|
||||
# 全部站点;百世固定下载当天,不可配置偏移
|
||||
ALL_SITES = ["顺心", "百世", "中通", "韵达", "安能"]
|
||||
@@ -245,6 +246,71 @@ def list_tasks(limit: int = 20):
|
||||
return state_store.list_tasks(limit)
|
||||
|
||||
|
||||
# ── DB 比对(基于 PostgreSQL,不依赖 Excel 文件)──
|
||||
|
||||
|
||||
class CompareRequest(BaseModel):
|
||||
site: str
|
||||
date: str # YYYY-MM-DD
|
||||
|
||||
|
||||
@app.post("/compare")
|
||||
def run_compare(req: CompareRequest):
|
||||
"""DB 差缺比对:以实到扫描日期为锚点,反推交接批次,展开全量比对。
|
||||
返回统计指标 + 差缺明细。
|
||||
"""
|
||||
# 合法性校验
|
||||
if req.site not in db_compare.SITE_COMPARE_CONFIG:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"不支持的站点: {req.site}(支持: {list(db_compare.SITE_COMPARE_CONFIG.keys())})",
|
||||
)
|
||||
try:
|
||||
target_date = datetime.strptime(req.date, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=400, detail=f"date 格式非法,需 YYYY-MM-DD: {req.date}"
|
||||
)
|
||||
today = datetime.now().date()
|
||||
if target_date > today:
|
||||
raise HTTPException(status_code=400, detail=f"date 不可为未来日期: {req.date}")
|
||||
|
||||
result = db_compare.compare_site_date(req.site, req.date)
|
||||
if result is None:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"{req.site} {req.date}: 当天无实到数据,无法比对",
|
||||
)
|
||||
|
||||
return {
|
||||
"site": result.site,
|
||||
"date": result.date,
|
||||
"batches": result.batches,
|
||||
"stats": {
|
||||
"waybill_count": result.stats.waybill_count,
|
||||
"sf_wb_count": result.stats.sf_wb_count,
|
||||
"expected_pieces": result.stats.expected_pieces,
|
||||
"arrived_pieces": result.stats.arrived_pieces,
|
||||
"undelivered_pieces": result.stats.undelivered_pieces,
|
||||
"undelivered_wb": result.stats.undelivered_wb,
|
||||
"full_miss": result.stats.full_miss,
|
||||
"part_miss": result.stats.part_miss,
|
||||
"sf_undelivered": result.stats.sf_undelivered,
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"handover_no": r.handover_no,
|
||||
"waybill_no": r.waybill_no,
|
||||
"total_pieces": r.total_pieces,
|
||||
"arrived_pieces": r.arrived_pieces,
|
||||
"arrived_list": r.arrived_list,
|
||||
"is_sf": r.is_sf,
|
||||
}
|
||||
for r in result.rows
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@app.get("/status")
|
||||
def get_status():
|
||||
"""各站登录态 + 数据态 + 入库态(前端状态盘用),另含 worker 就绪状态。"""
|
||||
|
||||
Reference in New Issue
Block a user