Files
kefu/deploy/review-visibility-20260916/strengthen_qt_handoff.py
T
2026-09-21 10:34:06 +08:00

115 lines
7.7 KiB
Python

from pathlib import Path
import os
artifact = Path(r'C:\kefu\deploy\review-visibility-20260916')
review = (artifact / 'review_state.py').read_text(encoding='utf-8')
review = review.replace("delivery_uncertain = send_state == 'uncertain' or stage == 'uncertain'", "delivery_uncertain = send_state in {'uncertain', 'sent_uncommitted', 'unknown'} or stage in {'uncertain', 'receipt_check', 'unknown'}")
review = review.replace("protected = send_state in {'sending', 'uncertain', 'sent_uncommitted', 'failed'} or stage in {'sending', 'receipt_check'}", "protected = delivery_uncertain or send_state in {'sending', 'failed'} or stage == 'sending'")
review = review.replace("and not approved\n", "and not approved and not protected\n")
review = review.replace(" if delivery_uncertain:\n", " if send_state == 'sent_uncommitted':\n reason = '回复已发送但归档尚未完成。请先在企业微信核对发送结果,再转交人工;保留发送记录,不会自动重发。' + ('\\n' + reason if reason else '')\n elif delivery_uncertain:\n")
(artifact / 'review_state.py').write_text(review, encoding='utf-8')
method = ''' def _handoff_queue_tasks(self, keys) -> None:
requested = list(dict.fromkeys(str(key or "").strip() for key in (keys or []) if str(key or "").strip()))
if not requested:
return
def confirm_receipt():
return QMessageBox.question(
self, "核对发送结果后转人工",
"这条回复可能已经发出。请先在企业微信的原会话核对发送结果。\\n\\n"
"继续后将从自动队列移出,由人工跟进;保留发送记录,不会自动重发,也不会标记为已确认发送。\\n\\n"
"已核对,确认转人工处理?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No,
) == QMessageBox.StandardButton.Yes
def handoff(targets, confirmed):
thread = getattr(self, "_thread", None)
if thread is not None and thread.is_alive():
return thread.handoff_pending_tasks(targets, confirm_uncertain=confirmed)
from gui_runtime import handoff_pending_reply_file
path = OFFLINE_QUEUE_PATH
return handoff_pending_reply_file(targets, str(path), confirm_uncertain=confirmed)
pending = getattr(self.queue_page, "pending_states", {}) or {}
confirmed = any(task_review_state(pending.get(key))["deliveryUncertain"] for key in requested)
if confirmed and not confirm_receipt():
return
try:
result = handoff(requested, confirmed)
# The worker may have started receipt checking after the UI snapshot.
# Re-check at the backend and never treat a stale UI as confirmation.
needs_confirmation = list(result.get("requires_confirmation") or [])
if needs_confirmation:
result["protected"] = [key for key in result.get("protected", []) if key not in needs_confirmation]
if confirm_receipt():
followup = handoff(needs_confirmation, True)
for field in ("handed_off", "deleted", "scheduled", "protected"):
result[field] = sorted(set(result.get(field) or []) | set(followup.get(field) or []))
if followup.get("error"):
result["error"] = followup["error"]
except Exception as exc:
QMessageBox.warning(self, "转人工失败", str(exc))
return
if result.get("error"):
QMessageBox.warning(self, "转人工失败", str(result["error"]))
accepted = set(result.get("handed_off") or result.get("deleted") or [])
scheduled = set(result.get("scheduled") or [])
if accepted:
self.append_log(f"已将 {len(accepted)} 条任务转交人工,保留发送记录,不再自动重发。", "notify")
if scheduled:
self.append_log(f"已登记 {len(scheduled)} 条人工接管操作,监听线程就绪后执行。", "notify")
if result.get("protected"):
QMessageBox.information(self, "暂不能转人工", "任务正在发送或属于其他账号,请等待发送结束并在对应账号下处理。")
self.queue_page.refresh_data()
self._schedule_console_push(urgent=True)
'''
for root in (Path(r'C:\kefu\wechat_rpa'), Path(r'C:\wechat_rpa')):
def save(name, content):
path = root / name
tmp = path.with_suffix('.audit-tmp')
tmp.write_text(content, encoding='utf-8')
os.replace(tmp, path)
save('review_state.py', review)
path = root / 'wechat_gui_qt.py'
text = path.read_text(encoding='utf-8')
start = text.index(' def _handoff_queue_tasks(self, keys) -> None:')
end = text.index(' def _approve_queue_tasks(self, keys) -> None:', start)
queue = 'queue_path_for_transport(self.runtime_settings.get("reply_transport", VISUAL), root=SCRIPT_DIR)' if root == Path(r'C:\wechat_rpa') else 'SCRIPT_DIR / "pending_replies.json"'
text = text[:start] + method.replace('OFFLINE_QUEUE_PATH', queue) + text[end:]
text = text.replace('if send_state == "sending":\n return "正在发送"\n if review["deliveryUncertain"]:', 'if send_state == "sending" or state.get("stage") == "sending":\n return "正在发送"\n if send_state == "sent_uncommitted":\n return "已发送,待归档(需人工核对)"\n if review["deliveryUncertain"]:')
text = text.replace(' if send_state == "sent_uncommitted":\n return "已发送,待归档"\n', '')
save('wechat_gui_qt.py', text)
for name in ('test_desktop_review_visibility.py', 'test_qt_console_bridge.py'):
text = (root / name).read_text(encoding='utf-8')
text = text.replace("thread.handoff_pending_tasks.assert_called_once_with(['uncertain'])", "thread.handoff_pending_tasks.assert_called_once_with(['uncertain'], confirm_uncertain=True)")
text = text.replace('thread.handoff_pending_tasks.assert_called_once_with(["done", "protected"])', 'thread.handoff_pending_tasks.assert_called_once_with(["done", "protected"], confirm_uncertain=False)')
if name == 'test_desktop_review_visibility.py':
text += '''
def test_stale_snapshot_requires_explicit_confirmation_before_backend_handoff(self):
self.seed()
thread=SimpleNamespace(is_alive=lambda:True, handoff_pending_tasks=mock.Mock(side_effect=[
{'handed_off':[], 'protected':['ordinary'], 'requires_confirmation':['ordinary']},
{'handed_off':['ordinary'], 'protected':[]},
]))
self.host._thread=thread
with mock.patch.object(QMessageBox,'question',return_value=QMessageBox.StandardButton.Yes) as question:
qt.MainWindow._handoff_queue_tasks(self.host,['ordinary'])
self.assertEqual(thread.handoff_pending_tasks.call_args_list,[
mock.call(['ordinary'],confirm_uncertain=False), mock.call(['ordinary'],confirm_uncertain=True)])
question.assert_called_once()
def test_legacy_receipt_states_are_visible_but_not_sendable(self):
for state in ({'stage':'receipt_check'}, {'send_state':'unknown'}, {'send_state':'sent_uncommitted'}):
with self.subTest(state=state):
flags=task_review_state(state)
self.assertTrue(flags['deliveryUncertain']);self.assertTrue(flags['needsHuman'])
self.assertFalse(flags['canApprove']);self.assertFalse(flags['canRetry'])
self.assertEqual(self.page._display_task_status(self.page._state_text(state)),'需人工')
'''
save(name, text)
print(str(root) + ': receipt confirmation strengthened')