Service layer (production code): - branch_manager: set account_id on SessionBranch (root + fork) and ForkPoint from session.account_id; load session in create_fork for this purpose - handoff_manager: set account_id on SessionHandoff from session.account_id - ai_suggestions endpoint: set account_id on AISuggestion from current_user - steps endpoint (/feedback): set account_id on StepRating from current_user - ratings endpoint: set account_id on StepRating from current_user Test infrastructure: - conftest.py: seed PLATFORM_ACCOUNT_ID (00000000-...-0001) account after Base.metadata.create_all so global categories and gallery items have a valid FK - test_rls_isolation: add _ensure_rls_schema fixture that runs 'alembic upgrade head' before module tests — previous function-scoped test_db fixtures drop the schema, leaving the RLS tests with no tables - test_branding: create Account before User in helper functions - test_admin_gallery: set account_id=PLATFORM_ACCOUNT_ID on Tree/ScriptTemplate - test_public_templates: set account_id=PLATFORM_ACCOUNT_ID on Tree, ScriptTemplate, TreeCategory - test_resolution_outputs: set account_id=session.account_id on SessionResolutionOutput - test_analytics_phase5: set account_id on PsaPostLog - test_draft_trees: replace account_id=None with PLATFORM_ACCOUNT_ID in migration default test (NOT NULL now enforced) - test_maintenance_schedules: set account_id on other_tree - test_save_session_as_tree: set account_id on all 5 Session() constructors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
81 lines
2.5 KiB
Python
81 lines
2.5 KiB
Python
"""AI Suggestion audit trail endpoints."""
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from datetime import datetime, timezone
|
|
|
|
from app.api.deps import get_current_active_user, get_db
|
|
from app.models.user import User
|
|
from app.models.ai_suggestion import AISuggestion
|
|
from app.schemas.ai_suggestion import (
|
|
AISuggestionCreate,
|
|
AISuggestionResponse,
|
|
AISuggestionResolve,
|
|
)
|
|
|
|
router = APIRouter(prefix="/ai/suggestions", tags=["ai-suggestions"])
|
|
|
|
|
|
@router.get("/tree/{tree_id}", response_model=list[AISuggestionResponse])
|
|
async def list_suggestions(
|
|
tree_id: UUID,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user),
|
|
):
|
|
"""List all suggestions for a flow, filtered to current user."""
|
|
result = await db.execute(
|
|
select(AISuggestion)
|
|
.where(AISuggestion.tree_id == tree_id, AISuggestion.user_id == current_user.id)
|
|
.order_by(AISuggestion.created_at.desc())
|
|
)
|
|
return result.scalars().all()
|
|
|
|
|
|
@router.post("", response_model=AISuggestionResponse, status_code=201)
|
|
async def create_suggestion(
|
|
data: AISuggestionCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user),
|
|
):
|
|
"""Record a new AI suggestion."""
|
|
suggestion = AISuggestion(
|
|
tree_id=data.tree_id,
|
|
user_id=current_user.id,
|
|
account_id=current_user.account_id,
|
|
session_id=data.session_id,
|
|
action_type=data.action_type,
|
|
target_node_id=data.target_node_id,
|
|
changes_json=data.changes_json,
|
|
)
|
|
db.add(suggestion)
|
|
await db.commit()
|
|
await db.refresh(suggestion)
|
|
return suggestion
|
|
|
|
|
|
@router.patch("/{suggestion_id}", response_model=AISuggestionResponse)
|
|
async def resolve_suggestion(
|
|
suggestion_id: UUID,
|
|
data: AISuggestionResolve,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user),
|
|
):
|
|
"""Accept or dismiss a suggestion."""
|
|
result = await db.execute(
|
|
select(AISuggestion).where(
|
|
AISuggestion.id == suggestion_id,
|
|
AISuggestion.user_id == current_user.id,
|
|
)
|
|
)
|
|
suggestion = result.scalar_one_or_none()
|
|
if not suggestion:
|
|
raise HTTPException(status_code=404, detail="Suggestion not found")
|
|
|
|
suggestion.status = data.status
|
|
suggestion.resolved_at = datetime.now(timezone.utc)
|
|
await db.commit()
|
|
await db.refresh(suggestion)
|
|
return suggestion
|