"""Project peripheral demo orders into the backend workbench order store.""" from __future__ import annotations import argparse import json import sys from datetime import datetime, timedelta from pathlib import Path from uuid import UUID from zoneinfo import ZoneInfo from psycopg.types.json import Jsonb ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from app.domain.schemas.workbench_runtime import WorkbenchRuntimeWorkOrder from app.native.wndb.core.connection import project_connection SCHEMA_SQL = ROOT / "resources/sql/011_workbench_work_orders.sql" SHANGHAI = ZoneInfo("Asia/Shanghai") PROJECTION_SOURCE = "合成数据预约工单" DEMO_WORK_ORDER_ID = "demo-work-order-peripheral-inspection" ORDER_DETAILS = { "合成通信核查": { "location": "DEMO-P001", "duration_minutes": 30, "reply_requirements": ["记录通信恢复时间", "确认缺口数据是否补传"], }, "合成压力偏差复核": { "location": "DEMO-P001 / DEMO-P002", "duration_minutes": 45, "reply_requirements": ["回填相邻测点对照结果", "确认合成样本复核结论"], }, "合成零流量样本确认": { "location": "DEMO-F001", "duration_minutes": 20, "reply_requirements": ["确认零值显示", "确认缺测值显示"], }, } def _parse_timestamp(value: str) -> datetime: return datetime.fromisoformat(value.replace("Z", "+00:00")) def _time_label(value: datetime) -> str: return value.astimezone(SHANGHAI).strftime("%H:%M") def _time_range(start: datetime, end: datetime) -> str: return f"{_time_label(start)}-{_time_label(end)}" def _subtask_status(status: str) -> str: mapped = { "open": "pending", "in_progress": "running", "completed": "completed", }.get(status) if mapped is None: raise ValueError(f"unsupported peripheral work-order status: {status}") return mapped def _stage_statuses(status: str) -> tuple[str, str, str]: if status == "completed": return "done", "done", "done" if status == "running": return "done", "current", "pending" return "current", "pending", "pending" def project_peripheral_subtask(document: dict) -> dict: status = _subtask_status(document.get("status")) scheduled_at = _parse_timestamp(document["scheduled_at"]) details = ORDER_DETAILS.get(document["title"], {}) duration_minutes = int(details.get("duration_minutes", 30)) execution_end = scheduled_at + timedelta(minutes=duration_minutes) reply_end = execution_end + timedelta(minutes=15) dispatch_status, execution_status, reply_status = _stage_statuses(status) assignee = document.get("assignee") or "待分派班组" return { "id": document["work_order_id"], "title": document["title"], "summary": document["summary"], "status": status, "scheduled_at": document["scheduled_at"], "location": details.get("location", "演示点位"), "assignee": assignee, "duration_minutes": duration_minutes, "reply_window_minutes": 15, "stages": [ { "id": "dispatch", "sequence": 1, "title": "派发", "status": dispatch_status, "owner": "演示调度", "time_label": _time_label(scheduled_at - timedelta(minutes=15)), "description": "预约工单子任务已进入演示调度流程。", }, { "id": "execution", "sequence": 2, "title": "执行", "status": execution_status, "owner": assignee, "time_label": _time_range(scheduled_at, execution_end), "description": "按外围合成场景执行本项核查。", }, { "id": "reply", "sequence": 3, "title": "复令", "status": reply_status, "owner": assignee, "time_label": _time_range(execution_end, reply_end), "description": "回传本项核查结果并完成子任务闭环。", }, ], "reply_requirements": details.get( "reply_requirements", ["回传演示核查结果"] ), } def project_peripheral_work_order(source_documents: list[dict]) -> dict: if not source_documents: raise ValueError("source documents must not be empty") subtasks = [project_peripheral_subtask(document) for document in source_documents] scheduled_values = [_parse_timestamp(item["scheduled_at"]) for item in source_documents] updated_values = [_parse_timestamp(item["updated_at"]) for item in source_documents] end_values = [ scheduled + timedelta(minutes=subtask["duration_minutes"]) for scheduled, subtask in zip(scheduled_values, subtasks, strict=True) ] statuses = {subtask["status"] for subtask in subtasks} if statuses == {"completed"}: status = "completed" elif statuses == {"pending"}: status = "pending" else: status = "running" priority = "urgent" if any( document.get("priority") in {"high", "urgent"} for document in source_documents ) else "normal" projected = { "id": DEMO_WORK_ORDER_ID, "code": "DEMO-WO-001", "scheduled_at": min(scheduled_values), "title": "外围合成数据核查", "summary": "依次核查合成通信、压力偏差和零流量样本。", "status": status, "risk_level": "attention" if priority == "urgent" else "normal", "updated_at": max(updated_values), "detail": "外围样板中的三项 synthetic 核查已组合为一张预约工单,仅用于演示。", "recommendation": "按子任务逐项核查并分别完成阶段回传。", "duration_minutes": max( 1, int((max(end_values) - min(scheduled_values)).total_seconds() // 60), ), "source": PROJECTION_SOURCE, "dispatcher": "演示调度", "priority": priority, "subtasks": subtasks, } return WorkbenchRuntimeWorkOrder.model_validate(projected).model_dump(mode="json") def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--database-name", default="tjwater_v2") parser.add_argument("--project-id", required=True, type=UUID) args = parser.parse_args() with project_connection(args.database_name) as conn, conn.transaction(): conn.execute(SCHEMA_SQL.read_text(encoding="utf-8")) source_rows = conn.execute( """ SELECT document FROM peripheral_sample.work_orders WHERE project_id = %s AND deleted_at IS NULL AND document->>'origin' = 'synthetic' AND document->>'scheduled_at' IS NOT NULL AND document->>'status' IN ('open', 'in_progress', 'completed') ORDER BY (document->>'scheduled_at')::timestamptz, work_order_id """, (args.project_id,), ).fetchall() source_documents = [row["document"] for row in source_rows] if not source_documents: raise RuntimeError("no eligible peripheral demo work orders found") documents = [project_peripheral_work_order(source_documents)] conn.execute( """ DELETE FROM workbench.work_orders WHERE project_id = %s AND ( work_order_id LIKE 'demo-work-order-%%' OR document->>'source' = %s OR document->>'source' = '外围样板预约工单' ) """, (args.project_id, PROJECTION_SOURCE), ) for document in documents: conn.execute( """ INSERT INTO workbench.work_orders (project_id, work_order_id, scheduled_at, updated_at, document) VALUES (%s, %s, %s, %s, %s) ON CONFLICT (project_id, work_order_id) DO UPDATE SET scheduled_at = EXCLUDED.scheduled_at, updated_at = EXCLUDED.updated_at, document = EXCLUDED.document """, ( args.project_id, document["id"], document["scheduled_at"], document["updated_at"], Jsonb(document), ), ) print(json.dumps({ "database": args.database_name, "project_id": str(args.project_id), "source": PROJECTION_SOURCE, "work_orders": [ { "id": document["id"], "code": document["code"], "title": document["title"], "subtask_count": len(document["subtasks"]), } for document in documents ], }, ensure_ascii=False, indent=2)) return 0 if __name__ == "__main__": raise SystemExit(main())