from __future__ import annotations from .shared import * from .helpers import _ensure_project_signal_link from .schemas import SignalSourceListResponse, SignalSourceOnboardRequest, SignalSourceRepairRequest @router.post("/signal-sources/onboard") @audited_write(caller="user") async def onboard_signal_source( request: SignalSourceOnboardRequest, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) signal = Signal( tenant_id=effective_tenant_id, source="PMFlow", content=f"[PMFlow用户#{int(current_user.id)}]: {request.instruction[:1800]}", impact_score=0.7, priority="medium", processed=False, strategic_analysis=json.dumps( { "source": "pmflow_manual_signal_source_onboard", "instruction": request.instruction[:2000], "created_at": now_cn_aware().isoformat(), }, ensure_ascii=False, ), tasks_generated=json.dumps([], ensure_ascii=False), matched_project_id=int(request.project_id), title=(request.instruction[:80] or "新增信号源"), signal_type="新需求", summary=request.instruction[:300], reasoning=json.dumps(["来自 AI Steward 手动信号源接入请求。"], ensure_ascii=False), confidence=0.82, recommended_action="enter_only", status="待处理", ) if not request.dry_run: db.add(signal) await db.flush() await _ensure_project_signal_link( db, tenant_id=effective_tenant_id, project_id=int(request.project_id), signal_id=int(signal.id), ) await db.commit() await db.refresh(signal) signal_id_value = int(signal.id) if not request.dry_run and getattr(signal, "id", None) is not None else None service = SignalSourceOrchestrationService(db) result = await service.orchestrate_from_signal( tenant_id=effective_tenant_id, project_id=int(request.project_id), signal_id=signal_id_value, initiating_text=request.instruction, requested_via="pmflow", operator_user_id=int(current_user.id), dry_run=bool(request.dry_run), response_mode=request.response_mode, ) return success_response( { "signal_id": signal_id_value, **result, } ) @router.get("/orchestration-runs/{trace_id}") async def get_orchestration_run( trace_id: str, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) row = await AIOrchestrationRunService(db).get_run( tenant_id=effective_tenant_id, trace_id=str(trace_id or "").strip(), ) if row is None: raise HTTPException(status_code=404, detail="orchestration run not found") return success_response(AIOrchestrationRunService.serialize_run(row)) @router.get("/projects/{project_id}/signal-sources") async def list_project_signal_sources( project_id: int, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) items = await SignalSourceOrchestrationService(db).list_project_sources( tenant_id=effective_tenant_id, project_id=int(project_id), ) return success_response(SignalSourceListResponse(items=items).model_dump()) @router.post("/signal-sources/repair-dirty") @audited_write(caller="system") async def repair_dirty_signal_sources( request: SignalSourceRepairRequest, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), tenant_id: int = Depends(get_tenant_id), ): effective_tenant_id = int(getattr(current_user, "tenant_id", tenant_id) or tenant_id) try: rows = await SignalSourceRepairService(db).repair_registries( tenant_id=effective_tenant_id, project_id=int(request.project_id), registry_ids=[int(item) for item in request.registry_ids], sender=str(request.sender or "PMFlow修复器").strip()[:64] or "PMFlow修复器", ) except SignalSourceRepairTimeoutError as exc: raise HTTPException(status_code=504, detail=str(exc)) from exc except SignalSourceRepairMissingSignalError as exc: raise HTTPException(status_code=502, detail=str(exc)) from exc return success_response( { "project_id": int(request.project_id), "items": rows, } )