174 lines
6.1 KiB
Python
174 lines
6.1 KiB
Python
"""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}}
|