290 lines
11 KiB
Python
290 lines
11 KiB
Python
from __future__ import annotations
|
|
|
|
from .shared import *
|
|
|
|
from ...services.ai_project_control_service import AIProjectControlService
|
|
from ...services.sstc.entity_lookup import _ensure_charter, _ensure_project
|
|
from ...services.sstc.signal_core import _parse_node_id, _parse_project_id
|
|
|
|
router = APIRouter(tags=["sstc"])
|
|
|
|
|
|
class ModelBindingRequest(BaseModel):
|
|
catalog_item_id: int
|
|
charter_id: int | None = None
|
|
estimated_daily_tokens: int = 0
|
|
selection_reason: dict[str, Any] | None = None
|
|
|
|
|
|
class PromptPackageCreateRequest(BaseModel):
|
|
task_id: int | None = None
|
|
|
|
|
|
class ReleaseValidationRunRequest(BaseModel):
|
|
charter_id: int | None = None
|
|
release_node_id: int | None = None
|
|
|
|
|
|
class FeedbackIngestRequest(BaseModel):
|
|
charter_id: int | None = None
|
|
source_type: str = "manual"
|
|
title: str | None = None
|
|
content: str
|
|
metrics: dict[str, Any] | None = None
|
|
samples: list[dict[str, Any]] | None = None
|
|
|
|
|
|
class ComplianceCheckRunRequest(BaseModel):
|
|
charter_id: int | None = None
|
|
release_node_id: int | None = None
|
|
|
|
|
|
class RevenueEventCreateRequest(BaseModel):
|
|
source_type: str = "manual"
|
|
category: str = "other"
|
|
currency: str = "USD"
|
|
amount_usd: float
|
|
external_ref: str | None = None
|
|
event_payload: dict[str, Any] | None = None
|
|
occurred_at: str | None = None
|
|
|
|
|
|
@router.get("/projects/{project_id}/model-recommendations")
|
|
async def get_model_recommendations(
|
|
project_id: str,
|
|
charter_id: int | None = Query(default=None),
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
if charter_id is not None:
|
|
await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=int(charter_id))
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
return success_response(await control.recommend_models(project_id=pid, charter_id=charter_id))
|
|
|
|
|
|
@router.post("/projects/{project_id}/model-binding")
|
|
async def bind_project_model(
|
|
project_id: str,
|
|
body: ModelBindingRequest,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
if body.charter_id is not None:
|
|
await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=int(body.charter_id))
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
try:
|
|
payload = await control.bind_model(
|
|
project_id=pid,
|
|
catalog_item_id=int(body.catalog_item_id),
|
|
selected_by_user_id=int(current_user.id),
|
|
charter_id=int(body.charter_id) if body.charter_id is not None else None,
|
|
estimated_daily_tokens=max(int(body.estimated_daily_tokens or 0), 0),
|
|
selection_reason=dict(body.selection_reason or {}),
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
return success_response(payload)
|
|
|
|
|
|
@router.get("/projects/{project_id}/model-usage")
|
|
async def get_project_model_usage(
|
|
project_id: str,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
return success_response(await control.get_model_usage(project_id=pid))
|
|
|
|
|
|
@router.post("/projects/{project_id}/charters/{charter_id}/prompt-packages")
|
|
async def create_prompt_package(
|
|
project_id: str,
|
|
charter_id: str,
|
|
body: PromptPackageCreateRequest,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
cid = _parse_node_id(charter_id, "charter_id")
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=cid)
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
try:
|
|
payload = await control.create_prompt_package(
|
|
project_id=pid,
|
|
charter_id=cid,
|
|
generated_by_user_id=int(current_user.id),
|
|
task_id=int(body.task_id) if body.task_id is not None else None,
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
return success_response(payload)
|
|
|
|
|
|
@router.post("/projects/{project_id}/release-validations/run")
|
|
async def run_release_validation(
|
|
project_id: str,
|
|
body: ReleaseValidationRunRequest,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
if body.charter_id is not None:
|
|
await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=int(body.charter_id))
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
payload = await control.run_release_validation(
|
|
project_id=pid,
|
|
actor_user_id=int(current_user.id),
|
|
charter_id=int(body.charter_id) if body.charter_id is not None else None,
|
|
release_node_id=int(body.release_node_id) if body.release_node_id is not None else None,
|
|
)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.post("/projects/{project_id}/feedback-ingest")
|
|
async def ingest_project_feedback(
|
|
project_id: str,
|
|
body: FeedbackIngestRequest,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
if body.charter_id is not None:
|
|
await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=int(body.charter_id))
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
try:
|
|
payload = await control.ingest_feedback(
|
|
project_id=pid,
|
|
actor_user_id=int(current_user.id),
|
|
charter_id=int(body.charter_id) if body.charter_id is not None else None,
|
|
source_type=body.source_type,
|
|
title=body.title,
|
|
content=body.content,
|
|
metrics=dict(body.metrics or {}),
|
|
samples=list(body.samples or []),
|
|
)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
return success_response(payload)
|
|
|
|
|
|
@router.post("/projects/{project_id}/revenue-events")
|
|
async def create_revenue_event(
|
|
project_id: str,
|
|
body: RevenueEventCreateRequest,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
payload = await control.record_revenue_event(
|
|
project_id=pid,
|
|
amount_usd=float(body.amount_usd or 0.0),
|
|
source_type=body.source_type,
|
|
category=body.category,
|
|
currency=body.currency,
|
|
external_ref=body.external_ref,
|
|
event_payload=dict(body.event_payload or {}),
|
|
occurred_at=body.occurred_at,
|
|
)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.get("/projects/{project_id}/roi-snapshots")
|
|
async def get_roi_snapshots(
|
|
project_id: str,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
return success_response(await control.get_roi_snapshots(project_id=pid))
|
|
|
|
|
|
@router.post("/projects/{project_id}/compliance-checks/run")
|
|
async def run_compliance_check(
|
|
project_id: str,
|
|
body: ComplianceCheckRunRequest,
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
if body.charter_id is not None:
|
|
await _ensure_charter(db, tenant_id=tenant_id, project_id=pid, charter_id=int(body.charter_id))
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
payload = await control.run_compliance_check(
|
|
project_id=pid,
|
|
actor_user_id=int(current_user.id),
|
|
charter_id=int(body.charter_id) if body.charter_id is not None else None,
|
|
release_node_id=int(body.release_node_id) if body.release_node_id is not None else None,
|
|
)
|
|
return success_response(payload)
|
|
|
|
|
|
@router.get("/projects/{project_id}/compliance-checks/latest/export")
|
|
async def export_latest_compliance_evidence(
|
|
project_id: str,
|
|
format: str = Query(default="json"),
|
|
permission_user: User = Depends(require_project_member_or_manager),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
_ = permission_user
|
|
pid = _parse_project_id(project_id)
|
|
tenant_id = int(current_user.tenant_id)
|
|
svc = SSTCService(db, tenant_id=tenant_id)
|
|
await _ensure_project(svc, pid)
|
|
control = AIProjectControlService(db, tenant_id=tenant_id)
|
|
try:
|
|
payload = await control.export_latest_compliance_evidence(project_id=pid, export_format=format)
|
|
except ValueError as exc:
|
|
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
return success_response(payload)
|