定时下载(APScheduler) + 跑比对纯离线 + 偏移拆 expected/actual + 报告下载
定时下载:
- state_store:site_config 加 schedule_enabled/schedule_time(旧库迁移)+ set_schedule/get_all_config
- server:APScheduler BackgroundScheduler,启动按配置注册各站 cron job、改配置重排;job 投 undelivered 任务到队列(错过跳过);/config 扩展 schedule
- runtime:("__compare__","compare") 改纯离线 expected_undelivered.main()(不下载)
- requirements:加 apscheduler
偏移拆分 + 报告下载:
- state_store:date_offset 拆 expected_offset/actual_offset(迁移)+ get_offset(site,kind)/set_offset(site,kind,n)
- 4 站 actual impl 改读 actual 偏移(expected 走默认 kind)
- server:/config 用 expected_offset/actual_offset;新增 GET /report(服务 output/应到未到数据.xlsx)
- paths:加 OUTPUT_DIR
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -69,11 +69,33 @@ def init_db():
|
||||
""")
|
||||
conn.execute("""
|
||||
CREATE TABLE IF NOT EXISTS site_config (
|
||||
site TEXT PRIMARY KEY,
|
||||
date_offset INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at TEXT
|
||||
site TEXT PRIMARY KEY,
|
||||
expected_offset INTEGER NOT NULL DEFAULT 0,
|
||||
actual_offset INTEGER NOT NULL DEFAULT 0,
|
||||
schedule_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
schedule_time TEXT NOT NULL DEFAULT '',
|
||||
updated_at TEXT
|
||||
)
|
||||
""")
|
||||
# 旧库迁移:补 schedule 两列 + expected/actual 偏移(新库已含;重复添加抛错忽略)
|
||||
for _col, _typedef in [
|
||||
("schedule_enabled", "INTEGER NOT NULL DEFAULT 0"),
|
||||
("schedule_time", "TEXT NOT NULL DEFAULT ''"),
|
||||
("expected_offset", "INTEGER NOT NULL DEFAULT 0"),
|
||||
("actual_offset", "INTEGER NOT NULL DEFAULT 0"),
|
||||
]:
|
||||
try:
|
||||
conn.execute(f"ALTER TABLE site_config ADD COLUMN {_col} {_typedef}")
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
# 旧库若有 date_offset 列,把值搬到 expected/actual(一次性;新库无此列则跳过)
|
||||
try:
|
||||
conn.execute(
|
||||
"UPDATE site_config SET expected_offset=date_offset, actual_offset=date_offset "
|
||||
"WHERE expected_offset=0 AND date_offset IS NOT NULL AND date_offset>0"
|
||||
)
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
conn.commit()
|
||||
|
||||
|
||||
@@ -197,38 +219,67 @@ def get_all_status():
|
||||
MAX_DATE_OFFSET = 30 # 0=今天,最大回溯 30 天
|
||||
|
||||
|
||||
def get_offset(site):
|
||||
"""读取单站下载日期偏移(0=今天,1=昨天…);未配置返回 0。"""
|
||||
def get_offset(site, kind="expected"):
|
||||
"""读取单站下载日期偏移(kind: 'expected'/'actual';0=今天,1=昨天…);未配置返回 0。"""
|
||||
col = "expected_offset" if kind == "expected" else "actual_offset"
|
||||
if not os.path.exists(STATE_DB_PATH):
|
||||
return 0
|
||||
with sqlite3.connect(STATE_DB_PATH) as conn:
|
||||
row = conn.execute(
|
||||
"SELECT date_offset FROM site_config WHERE site=?", (site,)
|
||||
f"SELECT {col} FROM site_config WHERE site=?", (site,)
|
||||
).fetchone()
|
||||
return int(row[0]) if row else 0
|
||||
|
||||
|
||||
def set_offset(site, offset):
|
||||
"""设置单站下载日期偏移,钳制到 [0, MAX_DATE_OFFSET],返回实际写入值。"""
|
||||
def set_offset(site, kind, offset):
|
||||
"""设置单站下载日期偏移(kind: 'expected'/'actual'),钳制到 [0, MAX_DATE_OFFSET]。"""
|
||||
col = "expected_offset" if kind == "expected" else "actual_offset"
|
||||
offset = max(0, min(MAX_DATE_OFFSET, int(offset)))
|
||||
with sqlite3.connect(STATE_DB_PATH) as conn:
|
||||
conn.execute(
|
||||
"INSERT INTO site_config (site, date_offset, updated_at) VALUES (?, ?, ?) "
|
||||
"ON CONFLICT(site) DO UPDATE SET "
|
||||
"date_offset=excluded.date_offset, updated_at=excluded.updated_at",
|
||||
f"INSERT INTO site_config (site, {col}, updated_at) VALUES (?, ?, ?) "
|
||||
f"ON CONFLICT(site) DO UPDATE SET {col}=excluded.{col}, "
|
||||
f"updated_at=excluded.updated_at",
|
||||
(site, offset, _now()),
|
||||
)
|
||||
conn.commit()
|
||||
return offset
|
||||
|
||||
|
||||
def get_all_offsets():
|
||||
"""返回 {site: date_offset};未配置站点不在结果中(调用方按 0 兜底)。"""
|
||||
def set_schedule(site, enabled, time_str):
|
||||
"""设置单站每日定时下载(enabled: bool;time_str: 'HH:MM' 或 '')。"""
|
||||
enabled_int = 1 if enabled else 0
|
||||
with sqlite3.connect(STATE_DB_PATH) as conn:
|
||||
conn.execute(
|
||||
"INSERT INTO site_config (site, schedule_enabled, schedule_time, updated_at) "
|
||||
"VALUES (?, ?, ?, ?) "
|
||||
"ON CONFLICT(site) DO UPDATE SET "
|
||||
"schedule_enabled=excluded.schedule_enabled, "
|
||||
"schedule_time=excluded.schedule_time, updated_at=excluded.updated_at",
|
||||
(site, enabled_int, time_str or "", _now()),
|
||||
)
|
||||
conn.commit()
|
||||
return bool(enabled_int), (time_str or "")
|
||||
|
||||
|
||||
def get_all_config():
|
||||
"""返回 {site: {expected_offset, actual_offset, schedule_enabled, schedule_time}}。"""
|
||||
if not os.path.exists(STATE_DB_PATH):
|
||||
return {}
|
||||
with sqlite3.connect(STATE_DB_PATH) as conn:
|
||||
rows = conn.execute("SELECT site, date_offset FROM site_config").fetchall()
|
||||
return {r[0]: int(r[1]) for r in rows}
|
||||
rows = conn.execute(
|
||||
"SELECT site, expected_offset, actual_offset, schedule_enabled, schedule_time "
|
||||
"FROM site_config"
|
||||
).fetchall()
|
||||
return {
|
||||
r[0]: {
|
||||
"expected_offset": int(r[1]),
|
||||
"actual_offset": int(r[2]),
|
||||
"schedule_enabled": bool(r[3]),
|
||||
"schedule_time": r[4] or "",
|
||||
}
|
||||
for r in rows
|
||||
}
|
||||
|
||||
|
||||
# ============================ 任务历史 ============================
|
||||
|
||||
Reference in New Issue
Block a user