新增市場情報 queue review inventory
Some checks failed
CD Pipeline / deploy (push) Has been cancelled

This commit is contained in:
OoO
2026-05-19 13:00:46 +08:00
parent e7e045253d
commit bdae154237
11 changed files with 1030 additions and 93 deletions

View File

@@ -19,7 +19,8 @@
| `edm_routes.py` | EDM 與節慶儀表板 | `/edm`, `/festival` |
| `monthly_routes.py` | 月結分析 | `/monthly_summary_analysis`, `/api/monthly_summary_data` |
| `daily_sales_routes.py` | 當日業績 | `/daily_sales`, `/daily_sales/export*` |
| `market_intel_routes.py` | 市場情報 Phase 64 candidate queue review handoff | `/market_intel`, `/market_intel/*`, `/api/market_intel/status`, `/api/market_intel/schema`, `/api/market_intel/schema_smoke`, `/api/market_intel/schema_db_probe`, `/api/market_intel/platform_seed_db_diff`, `/api/market_intel/legacy_source_bridge`, `/api/market_intel/mcp_readiness`, `/api/market_intel/mcp_tool_contract`, `/api/market_intel/mcp_deploy_preflight`, `/api/market_intel/mcp_activation_runbook`, `/api/market_intel/mcp_fetch_gate`, `/api/market_intel/scheduler_plan`, `/api/market_intel/manual_sample_plan`, `/api/market_intel/manual_sample_acceptance`, `/api/market_intel/manual_sample_review`, `/api/market_intel/manual_sample_review/evaluate`, `/api/market_intel/manual_sample_review/candidate_handoff`, `/api/market_intel/manual_sample_review/candidate_queue_draft`, `/api/market_intel/manual_sample_review/candidate_queue_approval`, `/api/market_intel/manual_sample_review/candidate_queue_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_writer_status`, `/api/market_intel/manual_sample_review/candidate_queue_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_writer_postwrite_smoke`, `/api/market_intel/manual_sample_review/candidate_queue_writer_operator_drill`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_handoff`, `/api/market_intel/match_review_plan`, `/api/market_intel/opportunity_plan`, `/api/market_intel/opportunity_scoring_plan`, `/api/market_intel/opportunity_evidence_plan`, `/api/market_intel/opportunity_alert_plan`, `/api/market_intel/adapters`, `/api/market_intel/dry_run_plan`, `/api/market_intel/discovery_plan`, `/api/market_intel/manual_discovery`, `/api/market_intel/candidate_preview`, `/api/market_intel/platform_seed_plan`, `/api/market_intel/platform_seed_write_guard`, `/api/market_intel/platform_seed_writer_plan`, `/api/market_intel/migration_blueprint`, `/api/market_intel/migration_apply_drill`, `/api/market_intel/migration_catalog_review`, `/api/market_intel/migration_live_smoke`, `/api/market_intel/live_db_inventory`, `/api/market_intel/seed_writer_cli_status`, `/api/market_intel/write_approval_runbook`, `/api/market_intel/deployment_readiness` |
| `market_intel_routes.py` | 市場情報 Phase 65 candidate queue review inventory 主路由 | `/market_intel`, `/market_intel/*`, `/api/market_intel/status`, `/api/market_intel/schema`, `/api/market_intel/schema_smoke`, `/api/market_intel/schema_db_probe`, `/api/market_intel/platform_seed_db_diff`, `/api/market_intel/legacy_source_bridge`, `/api/market_intel/mcp_readiness`, `/api/market_intel/mcp_tool_contract`, `/api/market_intel/mcp_deploy_preflight`, `/api/market_intel/mcp_activation_runbook`, `/api/market_intel/mcp_fetch_gate`, `/api/market_intel/scheduler_plan`, `/api/market_intel/manual_sample_plan`, `/api/market_intel/manual_sample_acceptance`, `/api/market_intel/manual_sample_review`, `/api/market_intel/manual_sample_review/evaluate`, `/api/market_intel/manual_sample_review/candidate_handoff`, `/api/market_intel/manual_sample_review/candidate_queue_draft`, `/api/market_intel/manual_sample_review/candidate_queue_approval`, `/api/market_intel/manual_sample_review/candidate_queue_transaction`, `/api/market_intel/manual_sample_review/candidate_queue_writer_status`, `/api/market_intel/manual_sample_review/candidate_queue_writer_preflight`, `/api/market_intel/manual_sample_review/candidate_queue_writer_postwrite_smoke`, `/api/market_intel/manual_sample_review/candidate_queue_writer_operator_drill`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_package`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_readiness`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_receipt`, `/api/market_intel/manual_sample_review/candidate_queue_writer_run_closeout`, `/api/market_intel/manual_sample_review/candidate_queue_review_handoff`, `/api/market_intel/match_review_plan`, `/api/market_intel/opportunity_plan`, `/api/market_intel/opportunity_scoring_plan`, `/api/market_intel/opportunity_evidence_plan`, `/api/market_intel/opportunity_alert_plan`, `/api/market_intel/adapters`, `/api/market_intel/dry_run_plan`, `/api/market_intel/discovery_plan`, `/api/market_intel/manual_discovery`, `/api/market_intel/candidate_preview`, `/api/market_intel/platform_seed_plan`, `/api/market_intel/platform_seed_write_guard`, `/api/market_intel/platform_seed_writer_plan`, `/api/market_intel/migration_blueprint`, `/api/market_intel/migration_apply_drill`, `/api/market_intel/migration_catalog_review`, `/api/market_intel/migration_live_smoke`, `/api/market_intel/live_db_inventory`, `/api/market_intel/seed_writer_cli_status`, `/api/market_intel/write_approval_runbook`, `/api/market_intel/deployment_readiness` |
| `market_intel_review_routes.py` | 市場情報人工 queue review 只讀延伸 API | `/api/market_intel/manual_sample_review/candidate_queue_review_inventory` |
| `api_routes.py` | 通用任務與查詢 API | `/api/run_task`, `/api/history/*` |
| `export_routes.py` | 匯出功能 | `/api/export/*` |
| `import_routes.py` | 匯入功能 | `/api/import_excel`, `/api/import/monthly_summary` |

View File

@@ -0,0 +1,166 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""市場情報人工審核延伸 API。"""
from flask import Blueprint, jsonify, request
from auth import login_required
from services.market_intel import MarketIntelService
from services.market_intel.candidate_queue_review_handoff import (
build_candidate_queue_review_handoff,
)
from services.market_intel.candidate_queue_review_inventory import (
build_candidate_queue_review_inventory,
)
from services.market_intel.candidate_queue_writer_cli import (
build_candidate_queue_writer_cli_plan,
)
from services.market_intel.candidate_queue_writer_operator_drill import (
build_candidate_queue_writer_operator_drill,
)
from services.market_intel.candidate_queue_writer_postwrite_smoke import (
build_candidate_queue_writer_postwrite_smoke,
)
from services.market_intel.candidate_queue_writer_preflight import (
build_candidate_queue_writer_preflight,
)
from services.market_intel.candidate_queue_writer_run_closeout import (
build_candidate_queue_writer_run_closeout,
)
from services.market_intel.candidate_queue_writer_run_package import (
build_candidate_queue_writer_run_package,
)
from services.market_intel.candidate_queue_writer_run_readiness import (
build_candidate_queue_writer_run_readiness,
)
from services.market_intel.candidate_queue_writer_run_receipt import (
build_candidate_queue_writer_run_receipt,
)
market_intel_review_bp = Blueprint("market_intel_review", __name__)
def _extract_run_payload():
payload = request.get_json(silent=True)
limit = request.args.get("limit", default=20, type=int)
if not isinstance(payload, dict):
return None, {}, {}, {}, "invalid_json_object", limit
return (
payload.get("sample_result", payload),
payload.get("operator_evidence", {}),
payload.get("writer_output", {}),
payload.get("postwrite_smoke_result", {}),
None,
limit,
)
def _build_closeout_stack(
*,
service,
sample_result,
payload_error,
operator_evidence,
writer_output,
postwrite_smoke_result,
limit,
):
transaction_preview = service.build_manual_sample_candidate_queue_transaction(
sample_result=sample_result,
payload_error=payload_error,
limit=limit,
)
writer_preflight = build_candidate_queue_writer_preflight(
transaction_preview=transaction_preview,
execute_requested=False,
)
writer_status = build_candidate_queue_writer_cli_plan(
transaction_preview=transaction_preview,
writer_preflight=writer_preflight,
approval_token=None,
approval_token_secret=None,
)
planned_smoke = build_candidate_queue_writer_postwrite_smoke(
transaction_preview=transaction_preview,
execute_requested=False,
)
operator_drill = build_candidate_queue_writer_operator_drill(
transaction_preview=transaction_preview,
writer_preflight=writer_preflight,
writer_status=writer_status,
postwrite_smoke=planned_smoke,
)
run_package = build_candidate_queue_writer_run_package(
transaction_preview=transaction_preview,
writer_preflight=writer_preflight,
writer_status=writer_status,
postwrite_smoke=planned_smoke,
operator_drill=operator_drill,
)
run_readiness = build_candidate_queue_writer_run_readiness(
transaction_preview=transaction_preview,
writer_preflight=writer_preflight,
writer_status=writer_status,
postwrite_smoke=planned_smoke,
operator_drill=operator_drill,
run_package=run_package,
operator_evidence=operator_evidence,
)
receipt = build_candidate_queue_writer_run_receipt(
transaction_preview=transaction_preview,
run_readiness=run_readiness,
writer_output=writer_output,
postwrite_smoke_result=postwrite_smoke_result,
operator_evidence=operator_evidence,
)
closeout = build_candidate_queue_writer_run_closeout(
transaction_preview=transaction_preview,
run_receipt=receipt,
operator_evidence=operator_evidence,
)
handoff = build_candidate_queue_review_handoff(
transaction_preview=transaction_preview,
run_closeout=closeout,
operator_evidence=operator_evidence,
)
return transaction_preview, handoff
@market_intel_review_bp.route(
"/api/market_intel/manual_sample_review/candidate_queue_review_inventory",
methods=["POST"],
)
@login_required
def market_intel_manual_sample_candidate_queue_review_inventory():
service = MarketIntelService()
execute_requested = request.args.get("execute", "false").lower() == "true"
sample_result, operator_evidence, writer_output, smoke_result, payload_error, limit = (
_extract_run_payload()
)
transaction_preview, handoff = _build_closeout_stack(
service=service,
sample_result=sample_result,
payload_error=payload_error,
operator_evidence=operator_evidence,
writer_output=writer_output,
postwrite_smoke_result=smoke_result,
limit=limit,
)
read_only_allowed = bool(execute_requested and handoff.get("handoff_ready"))
postwrite_smoke = build_candidate_queue_writer_postwrite_smoke(
transaction_preview=transaction_preview,
execute_requested=read_only_allowed,
)
live_db_inventory = service.build_live_db_inventory(
execute_requested=read_only_allowed,
)
data = build_candidate_queue_review_inventory(
review_handoff=handoff,
postwrite_smoke=postwrite_smoke,
live_db_inventory=live_db_inventory,
operator_evidence=operator_evidence,
execute_requested=execute_requested,
)
data["phase"] = service.phase
return jsonify(data), 400 if payload_error else 200