PMFlow/backend/app/services/sstc/shared.py

148 lines
5.0 KiB
Python

from __future__ import annotations
from __future__ import annotations
import asyncio
import json
import re
from datetime import datetime, timedelta, timezone
from io import BytesIO
from pathlib import Path
from typing import Any, Optional
from uuid import uuid4
from fastapi import APIRouter, BackgroundTasks, Body, Depends, File, HTTPException, Query, UploadFile
from fastapi.responses import JSONResponse, StreamingResponse
from pydantic import BaseModel
from sqlalchemy import and_, delete, select
from sqlalchemy.ext.asyncio import AsyncSession
from ...core.response import error_response, success_response
from ...core.database import get_db
from ...models import (
AICallLog,
AlignmentRecord,
Integration,
ModelRequirementBinding,
ModelVersion,
ProjectSignalLink,
Signal,
Task,
TruthNode,
User,
UserProject,
)
from ...schemas.sstc import (
AlignmentConfirmRequest,
AlignmentStartRequest,
CharterUpdateRequest,
ReleaseVerificationItemPatchRequest,
ReleaseCreateRequest,
ReleaseStatusPatchRequest,
ReviewArchiveRequest,
ReviewContentPatchRequest,
ReviewCreateRequest,
ReviewImprovementToTaskRequest,
WorkloadOptimizeRequest,
TaskStatusPatchRequest,
)
from ...services.llm_service import llm_service
from ...services.ai_service_core import AIService
from ...services.ai_pmo.ai_pmo_kernel import AIPMOKernelService
from ...services.operation_log_service import log_operation
from ...services.auth import has_any_role
from ...services.skill import recommend_personnel
from ...services.skill import skill_score_service
from ...services.sstc_service_core import (
ALIGNED_STATUSES,
DONE_STATUSES,
RELEASE_VERIFIED_STATUSES,
SSTCService,
)
from ...services import workload as workload_service
from ...services.budget_enforcement import BudgetEnforcementService
from ...services.integrations.feishu_integration_service import get_feishu_service
from ...services.adaptive_rag_service import AdaptiveRAGService
from ...services.context_cache_service import context_cache_service
from ...services.task_persistence_core import build_task_create_draft, persist_task
from ...dependencies import get_current_user, require_project_member_or_manager
from ...utils.title import compact_title
from ...utils.timezone import CN_TZ, now_cn_aware, now_cn_naive
_FEISHU_PLACEHOLDER_RE = re.compile(r"^飞书用户\(([^)]+)\)$")
_SIGNAL_PREFIX_RE = re.compile(r"^\[(.*?)\]\s*:\s*")
_GENERIC_SIGNAL_SOURCE_TITLE_RE = re.compile(
r"(加入到信号源|加入信号源|新增(?:一个)?信号源|创建(?:一个)?信号源|添加(?:一个)?信号源)"
)
DEFAULT_SIGNAL_AUTO_ENTER_THRESHOLD = 0.80
DEFAULT_PROJECT_AUTO_CREATE_THRESHOLD = 0.85
DEFAULT_PROJECT_UNCERTAIN_THRESHOLD = 0.60
_SIGNAL_STATUS_CODE_PENDING = "pending"
_SIGNAL_STATUS_CODE_LINKED = "linked"
_SIGNAL_STATUS_CODE_CLOSED = "closed"
_SIGNAL_STATUS_LABEL_BY_CODE = {
_SIGNAL_STATUS_CODE_PENDING: "待处理",
_SIGNAL_STATUS_CODE_LINKED: "已关联宪章",
_SIGNAL_STATUS_CODE_CLOSED: "已关闭",
}
_SIGNAL_STATUS_FILTER_ALIAS = {
"pending": _SIGNAL_STATUS_CODE_PENDING,
"待处理": _SIGNAL_STATUS_CODE_PENDING,
"linked": _SIGNAL_STATUS_CODE_LINKED,
"已关联宪章": _SIGNAL_STATUS_CODE_LINKED,
"closed": _SIGNAL_STATUS_CODE_CLOSED,
"archived": _SIGNAL_STATUS_CODE_CLOSED,
"已关闭": _SIGNAL_STATUS_CODE_CLOSED,
"已归档": _SIGNAL_STATUS_CODE_CLOSED,
}
_SIGNAL_LOCK_REASON = "信号已关联宪章,禁止重复新建"
_BEIJING_UTC_OFFSET = timedelta(hours=8)
_ALLOWED_ATTACHMENT_TYPES = {"image", "file", "media", "document"}
_SIGNAL_ATTACHMENT_MAX_BYTES = 20 * 1024 * 1024
_SIGNAL_ATTACHMENT_ALLOWED_EXTENSIONS = {
".jpg": "image",
".jpeg": "image",
".png": "image",
".gif": "image",
".pdf": "document",
".doc": "document",
".docx": "document",
".zip": "file",
}
_SIGNAL_ATTACHMENT_ALLOWED_MIME_TYPES = {
"image/jpeg",
"image/png",
"image/gif",
"application/pdf",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/zip",
"application/x-zip-compressed",
"multipart/x-zip",
"application/octet-stream",
}
_CHARTER_REQUIRED_FIELDS: dict[str, tuple[str, ...]] = {
"module_1": ("problem_statement", "goal_statement", "kpis"),
"module_2": ("in_scope", "out_of_scope", "alignment_qa"),
"module_3": ("primary_model", "fallback_model", "capability_table", "dependency_map"),
"module_4": ("milestones", "role_effort_estimates", "capability_owner_assignment"),
"module_5": ("risk_register", "trigger_rules", "execution_rule"),
"module_6": ("attachment_metadata", "attachment_summary", "search_tags"),
}
RELEASE_STATUS_TRANSITIONS: dict[str, set[str]] = {
"pending": {"releasing", "online"},
"releasing": {"online", "rolled_back"},
"online": {"rolled_back"},
"rolled_back": {"releasing"},
"verified": set(),
}
# Keep underscore-prefixed constants/functions available to `from .shared import *`.
__all__ = [name for name in globals() if not name.startswith("__")]