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:
@@ -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 ──
|
||||
|
||||
Reference in New Issue
Block a user