补采新增设备并限制次日截止

This commit is contained in:
2026-09-01 23:18:22 +08:00
parent 4be0ba10d2
commit 9b322cb6dd
3 changed files with 39 additions and 5 deletions

View File

@@ -840,13 +840,22 @@ def _poll_screenshot_results(public_id: str) -> None:
def retry_failed_acceptance_devices(public_id: str) -> dict[str, Any]: def retry_failed_acceptance_devices(public_id: str) -> dict[str, Any]:
"""仅对截图失败的“关键词 × 设备”组合重新立即下发。""" """补采失败及批次开始后新增的设备,仍写入原自然日批次。"""
with connection() as conn: with connection() as conn:
task = conn.execute( task = conn.execute(
"SELECT * FROM acceptance_tasks WHERE public_task_id=?", (public_id,), "SELECT * FROM acceptance_tasks WHERE public_task_id=?", (public_id,),
).fetchone() ).fetchone()
if not task: if not task:
raise TaskError("ACCEPTANCE_TASK_NOT_FOUND", "验收任务不存在", 404) raise TaskError("ACCEPTANCE_TASK_NOT_FOUND", "验收任务不存在", 404)
if task["batch_date"]:
batch_day = datetime.strptime(task["batch_date"], "%Y-%m-%d").date()
today = datetime.now().astimezone().date()
if today > batch_day + timedelta(days=1):
raise TaskError(
"ACCEPTANCE_RETRY_WINDOW_EXPIRED",
f"{task['batch_date']} 批次仅允许在批次当天或次日重采",
409,
)
if task["acceptance_method"] not in {"screenshot", "combined"}: if task["acceptance_method"] not in {"screenshot", "combined"}:
raise TaskError("SCREENSHOT_RETRY_NOT_SUPPORTED", "该任务不包含截图采集", 409) raise TaskError("SCREENSHOT_RETRY_NOT_SUPPORTED", "该任务不包含截图采集", 409)
rows = conn.execute( rows = conn.execute(
@@ -878,8 +887,24 @@ def retry_failed_acceptance_devices(public_id: str) -> dict[str, Any]:
) )
if status in terminal_failures or (status == "completed" and not has_screenshot): if status in terminal_failures or (status == "completed" and not has_screenshot):
failed_by_keyword.setdefault(keyword, []).append(account_id) failed_by_keyword.setdefault(keyword, []).append(account_id)
active_device_ids = {
str(item.get("xhs_account_id") or "").strip()
for item in list_xhs_wen_xapi_devices()
if str(item.get("xhs_account_id") or "").strip()
}
for keyword in keyword_runs:
collected_ids = {
account_id for (row_keyword, account_id) in latest if row_keyword == keyword
}
missing_ids = active_device_ids - collected_ids
if missing_ids:
failed_by_keyword.setdefault(keyword, []).extend(sorted(missing_ids))
failed_by_keyword = {
keyword: list(dict.fromkeys(account_ids))
for keyword, account_ids in failed_by_keyword.items() if account_ids
}
if not failed_by_keyword: if not failed_by_keyword:
raise TaskError("NO_FAILED_SCREENSHOT_DEVICES", "当前没有可重新采集的失败设备", 409) raise TaskError("NO_FAILED_SCREENSHOT_DEVICES", "当前没有失败或新增设备需要补采", 409)
dispatched_devices: set[str] = set() dispatched_devices: set[str] = set()
dispatched_pairs = 0 dispatched_pairs = 0
@@ -1705,6 +1730,10 @@ def _task_dict(conn, task) -> dict[str, Any]:
"dashboard_batch_key": task["dashboard_batch_key"], "dashboard_batch_key": task["dashboard_batch_key"],
"use_all_devices": bool(task["use_all_devices"]), "use_all_devices": bool(task["use_all_devices"]),
"batch_date": task["batch_date"], "batch_date": task["batch_date"],
"retry_deadline": (
(datetime.strptime(task["batch_date"], "%Y-%m-%d").date() + timedelta(days=1)).isoformat()
if task["batch_date"] else None
),
"schedule_enabled": bool(task["schedule_enabled"]), "schedule_enabled": bool(task["schedule_enabled"]),
"schedule_weekday": task["schedule_weekday"], "schedule_weekday": task["schedule_weekday"],
"schedule_weekdays": _decode_json(task["schedule_weekdays"], None) or ( "schedule_weekdays": _decode_json(task["schedule_weekdays"], None) or (

View File

@@ -99,8 +99,8 @@
}, },
"/api/v1/acceptance-tasks/{task_id}/retry-failed-devices": { "/api/v1/acceptance-tasks/{task_id}/retry-failed-devices": {
"post": { "post": {
"summary": "重新采集全部失败截图设备", "summary": "补采失败及新增截图设备",
"description": "按每个关键词读取最新 XAPI 结果,仅将失败、取消、异常或已完成但无截图的设备精确重新下发。成功设备不会重复执行,新批次继续归入原验收任务。", "description": "按关键词补采失败设备,以及批次完成后新加入设备库但尚未参与该批次的设备。结果仍归原日期批次;只允许在批次当天和次日执行。",
"tags": ["项目验收"], "tags": ["项目验收"],
"parameters": [{"$ref": "#/components/parameters/TaskId"}], "parameters": [{"$ref": "#/components/parameters/TaskId"}],
"responses": { "responses": {

View File

@@ -174,9 +174,14 @@ class AcceptanceCreationTest(unittest.TestCase):
@patch("acceptance_service.threading.Thread", _ThreadWithoutStart) @patch("acceptance_service.threading.Thread", _ThreadWithoutStart)
@patch("acceptance_service.schedule_xapi_screenshots") @patch("acceptance_service.schedule_xapi_screenshots")
@patch("acceptance_service.list_xhs_wen_xapi_devices")
@patch("acceptance_service.get_wen_resources") @patch("acceptance_service.get_wen_resources")
def test_retry_failed_devices_dispatches_only_failed_accounts(self, resources, schedule): def test_retry_failed_devices_dispatches_only_failed_accounts(self, resources, devices, schedule):
resources.return_value = {"cookie_count": 0, "device_count": 2} resources.return_value = {"cookie_count": 0, "device_count": 2}
devices.return_value = [
{"xhs_account_id": "failed-account"},
{"xhs_account_id": "successful-account"},
]
schedule.return_value = { schedule.return_value = {
"execution_mode": "immediate", "execution_mode": "immediate",
"batches": [{ "batches": [{