feat(collect): show skipped reason summaries
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""Pure helpers for classifying and presenting collection skip reasons."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
ALIAS_UNMATCHED = "alias_unmatched"
|
||||
LOGIN_REQUIRED = "login_required"
|
||||
OTHER = "other"
|
||||
|
||||
SKIP_REASON_ORDER = (ALIAS_UNMATCHED, LOGIN_REQUIRED, OTHER)
|
||||
SKIP_REASON_LABELS = {
|
||||
ALIAS_UNMATCHED: "别名未匹配",
|
||||
LOGIN_REQUIRED: "账号未登录",
|
||||
OTHER: "其他原因",
|
||||
}
|
||||
|
||||
|
||||
def empty_skip_reason_counts():
|
||||
return {reason: 0 for reason in SKIP_REASON_ORDER}
|
||||
|
||||
|
||||
def classify_skip_reason(reason="", *, alias_matched=True):
|
||||
"""Classify historical skip text without treating uncertain login checks as logout."""
|
||||
if not alias_matched:
|
||||
return ALIAS_UNMATCHED
|
||||
|
||||
text = str(reason or "").strip().lower()
|
||||
if "别名未匹配" in text or "alias_unmatched" in text:
|
||||
return ALIAS_UNMATCHED
|
||||
if (
|
||||
"账号未登录" in text
|
||||
or "采集中途掉登录" in text
|
||||
or "login_page" in text
|
||||
or ("accounts.shopee." in text and "/seller/login" in text)
|
||||
):
|
||||
return LOGIN_REQUIRED
|
||||
return OTHER
|
||||
|
||||
|
||||
def skipped_stage_text(reason="", *, alias_matched=True):
|
||||
reason_code = classify_skip_reason(reason, alias_matched=alias_matched)
|
||||
return f"略过 · {SKIP_REASON_LABELS[reason_code]}"
|
||||
|
||||
|
||||
def normalize_skip_reason_counts(counts=None, *, skipped_total=0):
|
||||
normalized = empty_skip_reason_counts()
|
||||
source = counts if isinstance(counts, dict) else {}
|
||||
for reason in SKIP_REASON_ORDER:
|
||||
try:
|
||||
normalized[reason] = max(0, int(source.get(reason, 0) or 0))
|
||||
except (TypeError, ValueError):
|
||||
normalized[reason] = 0
|
||||
|
||||
total = max(0, int(skipped_total or 0))
|
||||
accounted = sum(normalized.values())
|
||||
if accounted < total:
|
||||
normalized[OTHER] += total - accounted
|
||||
return normalized
|
||||
|
||||
|
||||
def format_skip_reason_summary(counts=None, *, skipped_total=0):
|
||||
total = max(0, int(skipped_total or 0))
|
||||
if total == 0:
|
||||
return ""
|
||||
normalized = normalize_skip_reason_counts(counts, skipped_total=total)
|
||||
parts = [
|
||||
f"{SKIP_REASON_LABELS[reason]}{normalized[reason]}"
|
||||
for reason in SKIP_REASON_ORDER
|
||||
if normalized[reason] > 0
|
||||
]
|
||||
return f"略过{total}:" + ",".join(parts)
|
||||
+17
-5
@@ -2,7 +2,11 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .. import diagnostics
|
||||
from ..collect_skip import skipped_stage_text
|
||||
from .widgets import *
|
||||
|
||||
|
||||
class TaskTableModel(QAbstractTableModel):
|
||||
"""Table model for task rows shared by workflow tabs."""
|
||||
|
||||
@@ -88,11 +92,15 @@ class TaskTableModel(QAbstractTableModel):
|
||||
if role == Qt.ForegroundRole and index.column() == 3:
|
||||
return self._stage_color(task)
|
||||
if role == Qt.ToolTipRole:
|
||||
if self.is_unmatched(task):
|
||||
return "别名未匹配账号,采集时将略过"
|
||||
error = getattr(task, "last_error", "") or ""
|
||||
if self.is_unmatched(task) or getattr(task, "status", "") == "skipped":
|
||||
if error:
|
||||
return diagnostics.redact_log_text(str(error))
|
||||
if self.is_unmatched(task):
|
||||
return "别名未匹配账号,采集时将略过"
|
||||
return "未记录具体略过原因"
|
||||
if error:
|
||||
return str(error)
|
||||
return diagnostics.redact_log_text(str(error))
|
||||
return None
|
||||
|
||||
def flags(self, index):
|
||||
@@ -124,8 +132,12 @@ class TaskTableModel(QAbstractTableModel):
|
||||
return bool(error) and any(marker.lower() in error for marker in self.PRODUCT_UNAVAILABLE_MARKERS)
|
||||
|
||||
def _stage_text(self, task) -> str:
|
||||
if self.is_unmatched(task):
|
||||
return "略过"
|
||||
unmatched = self.is_unmatched(task)
|
||||
if unmatched or getattr(task, "status", "") == "skipped":
|
||||
return skipped_stage_text(
|
||||
getattr(task, "last_error", ""),
|
||||
alias_matched=not unmatched,
|
||||
)
|
||||
if self._is_product_unavailable(task):
|
||||
return "商品失效"
|
||||
if task.status in self.STATUS_TEXT and task.status != "pending":
|
||||
|
||||
+22
-3
@@ -2,6 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ...collect_skip import (
|
||||
ALIAS_UNMATCHED,
|
||||
LOGIN_REQUIRED,
|
||||
format_skip_reason_summary,
|
||||
normalize_skip_reason_counts,
|
||||
)
|
||||
from ..models import TaskTableModel
|
||||
from ..widgets import *
|
||||
from ..workers import CollectWorker as _RealCollectWorker, WriteBackWorker as _RealWriteBackWorker
|
||||
@@ -694,9 +700,19 @@ class CollectTab(QWidget):
|
||||
launched = payload.get("launched_accounts") or []
|
||||
reused = payload.get("reused_accounts") or []
|
||||
login_required = payload.get("login_required_accounts") or []
|
||||
if not launched and not reused and not login_required:
|
||||
skipped = max(0, int(payload.get("skipped", 0) or 0))
|
||||
skip_counts = normalize_skip_reason_counts(
|
||||
payload.get("skip_reason_counts"),
|
||||
skipped_total=skipped,
|
||||
)
|
||||
if not launched and not reused and not login_required and skipped == 0:
|
||||
return
|
||||
lines = [message]
|
||||
skip_summary = format_skip_reason_summary(skip_counts, skipped_total=skipped)
|
||||
if skip_summary:
|
||||
lines.append(skip_summary)
|
||||
if skip_counts[ALIAS_UNMATCHED] > 0:
|
||||
lines.append("请检查 Excel 别名是否与④账号管理中的账号别名一致。")
|
||||
if launched:
|
||||
lines.append(
|
||||
"本轮已自动启动账号 Chrome:"
|
||||
@@ -707,9 +723,12 @@ class CollectTab(QWidget):
|
||||
"以下账号需要补登录:"
|
||||
+ "、".join(self._account_label(item) for item in login_required)
|
||||
)
|
||||
lines.append("采集结束后不会自动关闭账号 Chrome,请按需自行关闭。")
|
||||
if skip_counts[LOGIN_REQUIRED] > 0:
|
||||
lines.append("请到④账号管理完成对应账号登录后,再重新采集略过任务。")
|
||||
if launched or reused or login_required:
|
||||
lines.append("采集结束后不会自动关闭账号 Chrome,请按需自行关闭。")
|
||||
text = "\n".join(lines)
|
||||
if login_required:
|
||||
if login_required or skip_counts[LOGIN_REQUIRED] > 0:
|
||||
QMessageBox.warning(self, "采集完成", text)
|
||||
else:
|
||||
QMessageBox.information(self, "采集完成", text)
|
||||
|
||||
+10
-1
@@ -8,6 +8,7 @@ import threading
|
||||
import time
|
||||
|
||||
from .. import image_studio, image_studio_export, image_studio_generation, image_studio_images
|
||||
from ..collect_skip import ALIAS_UNMATCHED, LOGIN_REQUIRED, empty_skip_reason_counts
|
||||
from .widgets import *
|
||||
|
||||
|
||||
@@ -1623,6 +1624,7 @@ class CollectWorker(BaseWorker):
|
||||
login_skip_reasons = {}
|
||||
login_required_accounts = {}
|
||||
preflight_info = {}
|
||||
skip_reason_counts = empty_skip_reason_counts()
|
||||
|
||||
self._run_id = self._create_run_log(eligible, batch_ids)
|
||||
self._log_run_event(
|
||||
@@ -1642,7 +1644,10 @@ class CollectWorker(BaseWorker):
|
||||
failed=failed,
|
||||
batch_ids=batch_ids,
|
||||
blocked=True,
|
||||
extra=blocked,
|
||||
extra={
|
||||
**blocked,
|
||||
"skip_reason_counts": dict(skip_reason_counts),
|
||||
},
|
||||
)
|
||||
self._finish_run_log("blocked", summary)
|
||||
return summary
|
||||
@@ -1665,6 +1670,7 @@ class CollectWorker(BaseWorker):
|
||||
account = account_by_alias.get(str(task.alias).strip())
|
||||
if account is None:
|
||||
skipped += 1
|
||||
skip_reason_counts[ALIAS_UNMATCHED] += 1
|
||||
done += 1
|
||||
reason = "别名未匹配账号"
|
||||
db.mark_skipped(task.id, reason, path=self.db_path)
|
||||
@@ -1684,6 +1690,7 @@ class CollectWorker(BaseWorker):
|
||||
alias = str(task.alias).strip()
|
||||
if alias in login_skip_reasons:
|
||||
skipped += 1
|
||||
skip_reason_counts[LOGIN_REQUIRED] += 1
|
||||
done += 1
|
||||
reason = login_skip_reasons[alias]
|
||||
db.mark_skipped(task.id, reason, path=self.db_path)
|
||||
@@ -1704,6 +1711,7 @@ class CollectWorker(BaseWorker):
|
||||
if self._is_definitive_logged_out(status):
|
||||
alias = str(task.alias).strip()
|
||||
skipped += 1
|
||||
skip_reason_counts[LOGIN_REQUIRED] += 1
|
||||
done += 1
|
||||
reason = self._midrun_login_skip_reason(status)
|
||||
login_skip_reasons[alias] = reason
|
||||
@@ -1840,6 +1848,7 @@ class CollectWorker(BaseWorker):
|
||||
extra={
|
||||
**preflight_info,
|
||||
"login_required_accounts": list(login_required_accounts.values()),
|
||||
"skip_reason_counts": dict(skip_reason_counts),
|
||||
},
|
||||
)
|
||||
self._finish_run_log("cancelled" if self.should_cancel() else "done", summary)
|
||||
|
||||
Reference in New Issue
Block a user