149 lines
5.5 KiB
Python
149 lines
5.5 KiB
Python
"""市場情報人工候選審核 queue 草案預覽。
|
||
|
||
本模組只把 sample result handoff 轉成 review queue draft;
|
||
不建立正式 queue、不寫 DB、不掛 scheduler、不連外。
|
||
"""
|
||
|
||
from services.market_intel.manual_sample_review import (
|
||
build_manual_sample_candidate_handoff_preview,
|
||
)
|
||
|
||
|
||
def _priority_for_candidate(candidate):
|
||
band = str(candidate.get("confidence_band") or "").lower()
|
||
if band == "high":
|
||
return 80
|
||
if band == "medium":
|
||
return 50
|
||
return 20
|
||
|
||
|
||
def _build_queue_item(candidate, batch_id):
|
||
candidate_key = str(candidate.get("candidate_key") or "")
|
||
return {
|
||
"queue_item_key": f"review:{candidate_key}",
|
||
"candidate_key": candidate_key,
|
||
"platform_code": str(candidate.get("platform_code") or ""),
|
||
"candidate_url": str(candidate.get("candidate_url") or ""),
|
||
"candidate_text": str(candidate.get("candidate_text") or ""),
|
||
"confidence_band": str(candidate.get("confidence_band") or "unknown"),
|
||
"score": candidate.get("score") or 0,
|
||
"rank_position": candidate.get("rank_position") or 0,
|
||
"review_state": "needs_review",
|
||
"review_priority": _priority_for_candidate(candidate),
|
||
"created_from_batch_id": batch_id,
|
||
"write_status": "blocked_preview_only",
|
||
"approval_required": True,
|
||
}
|
||
|
||
|
||
def build_manual_sample_candidate_queue_draft_preview(
|
||
*,
|
||
runtime_status,
|
||
acceptance_contract,
|
||
sample_result=None,
|
||
payload_error=None,
|
||
limit=20,
|
||
):
|
||
"""建立候選活動人工審核 queue 草案;只回 preview,不保存。"""
|
||
handoff = build_manual_sample_candidate_handoff_preview(
|
||
runtime_status=runtime_status,
|
||
acceptance_contract=acceptance_contract,
|
||
sample_result=sample_result,
|
||
payload_error=payload_error,
|
||
limit=limit,
|
||
)
|
||
batch_id = ""
|
||
if isinstance(sample_result, dict):
|
||
batch_id = str(sample_result.get("batch_id") or "")
|
||
queue_items = [
|
||
_build_queue_item(candidate, batch_id)
|
||
for candidate in handoff.get("candidates", [])
|
||
]
|
||
queue_draft_ready = bool(handoff.get("handoff_ready") and queue_items)
|
||
blocked_reasons = list(handoff.get("blocked_reasons", []))
|
||
if not queue_draft_ready:
|
||
blocked_reasons.append("review_queue_draft_not_ready")
|
||
blocked_reasons.append("review_queue_persist_still_blocked")
|
||
|
||
return {
|
||
"mode": "manual_sample_candidate_queue_draft_preview",
|
||
"handoff": {
|
||
"mode": handoff["mode"],
|
||
"handoff_ready": handoff["handoff_ready"],
|
||
"candidate_handoff_created": handoff["candidate_handoff_created"],
|
||
"candidate_handoff_persisted": handoff["candidate_handoff_persisted"],
|
||
"handoff_summary": handoff["handoff_summary"],
|
||
},
|
||
"payload_received": handoff["payload_received"],
|
||
"payload_valid_json_object": handoff["payload_valid_json_object"],
|
||
"payload_error": handoff["payload_error"],
|
||
"payload_persisted": False,
|
||
"sample_result_persisted": False,
|
||
"handoff_ready": handoff["handoff_ready"],
|
||
"queue_draft_ready": queue_draft_ready,
|
||
"review_queue_draft_created": bool(queue_items),
|
||
"review_queue_created": False,
|
||
"review_queue_persisted": False,
|
||
"candidate_import_allowed": False,
|
||
"scheduler_attached": False,
|
||
"external_network_executed": False,
|
||
"database_connection_opened": False,
|
||
"database_session_created": False,
|
||
"database_write_executed": False,
|
||
"database_commit_executed": False,
|
||
"writes_executed": False,
|
||
"would_write_database": False,
|
||
"blocked_reasons": blocked_reasons,
|
||
"queue_summary": {
|
||
"queue_item_count": len(queue_items),
|
||
"review_queue_created": False,
|
||
"queue_persisted": False,
|
||
"import_allowed": False,
|
||
},
|
||
"queue_contract": {
|
||
"contract_type": "draft_contract_only",
|
||
"table_name": "market_campaign_candidate_review_queue",
|
||
"required_fields": [
|
||
"queue_item_key",
|
||
"candidate_key",
|
||
"platform_code",
|
||
"candidate_url",
|
||
"candidate_text",
|
||
"confidence_band",
|
||
"score",
|
||
"review_state",
|
||
"review_priority",
|
||
"created_from_batch_id",
|
||
],
|
||
"forbidden_actions": [
|
||
"insert_review_queue_rows",
|
||
"auto_approve_candidates",
|
||
"insert_market_campaigns",
|
||
"attach_scheduler_jobs",
|
||
],
|
||
},
|
||
"queue_items": queue_items,
|
||
"operator_next_actions": [
|
||
{
|
||
"key": "review_queue_draft_manually",
|
||
"label": "人工確認候選 URL、分數與優先序後,才可提出正式 queue 建立申請",
|
||
"write_status": "blocked",
|
||
},
|
||
{
|
||
"key": "approve_queue_persistence_later",
|
||
"label": "正式寫入候選審核 queue 需要另一次明確批准與 DB 備援檢查",
|
||
"write_status": "blocked",
|
||
},
|
||
],
|
||
"safe_boundaries": [
|
||
*handoff["safe_boundaries"],
|
||
"do_not_create_candidate_review_queue_from_preview",
|
||
"do_not_persist_candidate_review_queue_draft",
|
||
"do_not_write_market_tables_from_queue_draft",
|
||
"do_not_auto_approve_candidates",
|
||
"do_not_attach_scheduler_from_queue_draft",
|
||
"do_not_touch_momo_db_lifecycle",
|
||
],
|
||
}
|