fix(flowpilot): extract _build_session_detail helper, fix link_ticket 500

The same model_validate bug existed in link_ticket endpoint (line 467).
Extracted the manual AISessionDetail construction into a shared helper
_build_session_detail() used by both get_session and link_ticket.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 06:28:11 +00:00
parent 0118b80b63
commit 6122dda71d

View File

@@ -52,6 +52,64 @@ logger = logging.getLogger(__name__)
router = APIRouter(prefix="/ai-sessions", tags=["ai-sessions"])
def _build_session_detail(session: AISession) -> AISessionDetail:
"""Build AISessionDetail from ORM session with properly mapped steps.
AISessionDetail.model_validate(session) fails because the ORM steps
relationship uses 'id' while AISessionStepResponse expects 'step_id'.
This helper manually maps all fields to avoid that validation error.
"""
step_responses = []
for step in (session.steps or []):
options = []
if step.options_presented:
options = [
StepOptionSchema(
label=opt.get("label", ""),
value=opt.get("value", ""),
followup_hint=opt.get("followup_hint"),
)
for opt in step.options_presented
]
content = step.content or {}
step_responses.append(AISessionStepResponse(
step_id=step.id,
step_order=step.step_order,
step_type=step.step_type,
content=content,
context_message=step.context_message,
options=options,
allow_free_text=content.get("allow_free_text", True),
allow_skip=content.get("allow_skip", True),
confidence_tier=session.confidence_tier,
confidence_score=step.confidence_at_step,
))
return AISessionDetail(
id=session.id,
status=session.status,
intake_type=session.intake_type,
intake_content=session.intake_content or {},
problem_summary=session.problem_summary,
problem_domain=session.problem_domain,
confidence_tier=session.confidence_tier,
step_count=session.step_count,
session_rating=session.session_rating,
psa_ticket_id=session.psa_ticket_id,
psa_connection_id=session.psa_connection_id,
escalation_reason=session.escalation_reason,
matched_flow_id=session.matched_flow_id,
match_score=getattr(session, 'match_score', None),
resolution_summary=session.resolution_summary,
resolution_action=getattr(session, 'resolution_action', None),
session_feedback=session.session_feedback,
ticket_data=session.ticket_data,
created_at=session.created_at,
resolved_at=session.resolved_at,
steps=step_responses,
)
def _require_ai_enabled() -> None:
if not settings.ai_enabled:
raise HTTPException(
@@ -464,8 +522,7 @@ async def link_ticket_to_session(
if not session:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Session not found")
detail = AISessionDetail.model_validate(session)
return detail
return _build_session_detail(session)
# ── Search sessions (Command Palette) ──
@@ -618,60 +675,7 @@ async def get_session(
if session.user_id != current_user.id and session.escalated_to_id != current_user.id and not is_handler:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Not authorized")
# Build step responses
step_responses = []
for step in session.steps:
options = []
if step.options_presented:
options = [
StepOptionSchema(
label=opt.get("label", ""),
value=opt.get("value", ""),
followup_hint=opt.get("followup_hint"),
)
for opt in step.options_presented
]
content = step.content or {}
step_responses.append(AISessionStepResponse(
step_id=step.id,
step_order=step.step_order,
step_type=step.step_type,
content=content,
context_message=step.context_message,
options=options,
allow_free_text=content.get("allow_free_text", True),
allow_skip=content.get("allow_skip", True),
confidence_tier=session.confidence_tier,
confidence_score=step.confidence_at_step,
))
# Build detail manually — AISessionDetail.model_validate(session) fails because
# the ORM relationship 'steps' has 'id' not 'step_id', causing validation errors.
# Instead, extract non-step fields from ORM and set step_responses separately.
detail = AISessionDetail(
id=session.id,
status=session.status,
intake_type=session.intake_type,
intake_content=session.intake_content or {},
problem_summary=session.problem_summary,
problem_domain=session.problem_domain,
confidence_tier=session.confidence_tier,
step_count=session.step_count,
session_rating=session.session_rating,
psa_ticket_id=session.psa_ticket_id,
psa_connection_id=session.psa_connection_id,
escalation_reason=session.escalation_reason,
matched_flow_id=session.matched_flow_id,
match_score=getattr(session, 'match_score', None),
resolution_summary=session.resolution_summary,
resolution_action=getattr(session, 'resolution_action', None),
session_feedback=session.session_feedback,
ticket_data=session.ticket_data,
created_at=session.created_at,
resolved_at=session.resolved_at,
steps=step_responses,
)
return detail
return _build_session_detail(session)
# ── Documentation ──