"""Scheduler API 路由。""" from datetime import datetime, timedelta from fastapi import APIRouter, Depends from sqlalchemy import and_, or_, select from sqlalchemy.ext.asyncio import AsyncSession from ..core.database import get_db from ..dependencies import require_manager_user from ..models import KnowledgeBaseDoc, Project, User from ..services.adaptive_rag_service import AdaptiveRAGService from ..services.ai_pmo_cadence import AIPMOCadenceService from ..services.project_knowledge_graph_service import ProjectKnowledgeGraphService from ..services.symbol_index import SymbolIndexService from ..services import workload as workload_service router = APIRouter(prefix="/scheduler", tags=["scheduler"]) @router.get("/health") async def scheduler_health() -> dict: """调度模块健康检查。""" return {"code": 0, "message": "success", "data": {"status": "ok"}} @router.post("/workloads/recalc") async def trigger_workload_recalc( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), ) -> dict: """手动触发一次负载重算。""" _ = current_user tenant_ids = await workload_service.list_tenant_ids(db) if not tenant_ids: tenant_ids = [1] total = 0 for tenant_id in tenant_ids: total += await workload_service.recalculate_all_users_workload_ratio(db, tenant_id=tenant_id) return { "code": 0, "message": "success", "data": {"tenant_count": len(tenant_ids), "user_count": total}, } @router.post("/knowledge-base/urls/refresh") async def trigger_knowledge_url_refresh( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), ) -> dict: """手动触发 URL 知识库回抓。""" tenant_id = int(current_user.tenant_id) cutoff = datetime.now() - timedelta(days=7) docs = ( await db.execute( select(KnowledgeBaseDoc).where( and_( KnowledgeBaseDoc.tenant_id == tenant_id, KnowledgeBaseDoc.source_type == "url", or_( KnowledgeBaseDoc.last_fetched_at.is_(None), KnowledgeBaseDoc.last_fetched_at <= cutoff, ), ) ) ) ).scalars().all() refreshed = 0 failed = 0 service = AdaptiveRAGService(db, tenant_id=tenant_id) for doc in docs: try: await service.refresh_url_doc(doc_id=int(doc.id), project_id=None) refreshed += 1 except Exception: failed += 1 return { "code": 0, "message": "success", "data": {"candidate_count": len(docs), "refreshed": refreshed, "failed": failed}, } @router.post("/knowledge-graph/incremental") async def trigger_knowledge_graph_incremental( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), ) -> dict: """手动触发一次知识图谱增量流水线。""" tenant_id = int(current_user.tenant_id) service = ProjectKnowledgeGraphService(db, tenant_id=tenant_id) data = await service.run_incremental_cycle(run_source="manual_scheduler_api") return { "code": 0, "message": "success", "data": data, } @router.post("/symbols/sync") async def trigger_symbol_index_sync( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), ) -> dict: """手动触发一次 Symbol 索引同步。""" tenant_id = int(current_user.tenant_id) service = SymbolIndexService(db, tenant_id=tenant_id) data = await service.sync_codebase_symbols(max_files=800, force_reindex=False) return { "code": 0, "message": "success", "data": data, } @router.post("/features/catalog/sync") async def trigger_feature_catalog_sync( current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), ) -> dict: """手动触发一次 Feature Catalog -> KG 同步。""" tenant_id = int(current_user.tenant_id) service = ProjectKnowledgeGraphService(db, tenant_id=tenant_id) data = await service.sync_feature_catalog_now() return { "code": 0, "message": "success", "data": data, } @router.post("/ai-pmo/cadence/run") async def trigger_ai_pmo_cadence_run( cadence_type: str = "all", project_id: int | None = None, current_user: User = Depends(require_manager_user), db: AsyncSession = Depends(get_db), ) -> dict: """手动触发 AI PMO cadence snapshot。""" tenant_id = int(current_user.tenant_id) service = AIPMOCadenceService(db, tenant_id=tenant_id) if cadence_type != "all": snapshot = await service.generate_snapshot(cadence_type=cadence_type, project_id=project_id) await db.commit() return {"code": 0, "message": "success", "data": {"count": 1, "items": [snapshot]}} items = [await service.generate_snapshot(cadence_type="risk_review", project_id=project_id)] if project_id is None: items.append(await service.generate_snapshot(cadence_type="daily_standup", project_id=None)) items.append(await service.generate_snapshot(cadence_type="milestone_check", project_id=None)) if datetime.now().weekday() == 0: items.append(await service.generate_snapshot(cadence_type="weekly_review", project_id=None)) project_ids = ( await db.execute( select(Project.id).where( and_( Project.tenant_id == tenant_id, Project.status.in_(["active", "planning", "doing", "todo"]), ) ) ) ).scalars().all() for pid in project_ids: items.append(await service.generate_snapshot(cadence_type="risk_review", project_id=int(pid))) else: items.append(await service.generate_snapshot(cadence_type="daily_standup", project_id=int(project_id))) items.append(await service.generate_snapshot(cadence_type="milestone_check", project_id=int(project_id))) await db.commit() return {"code": 0, "message": "success", "data": {"count": len(items), "items": items}}