feat: AI patient onboarding backend and documentation
This commit is contained in:
@@ -0,0 +1 @@
|
||||
"""API package initialization."""
|
||||
@@ -0,0 +1,84 @@
|
||||
import json
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.ai.gemini_client import GeminiClient
|
||||
from app.database.database import get_db
|
||||
from app.schemas.documents import DocumentExtractionResponse
|
||||
from app.services.onboarding_service import OnboardingService
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/v1/documents",
|
||||
tags=["Medical Documents"],
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{session_id}/upload",
|
||||
response_model=DocumentExtractionResponse,
|
||||
)
|
||||
async def upload_medical_document(
|
||||
session_id: str,
|
||||
file: UploadFile = File(...),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
onboarding_service = OnboardingService()
|
||||
gemini_client = GeminiClient()
|
||||
|
||||
session = onboarding_service.get_session(db, session_id)
|
||||
if not session:
|
||||
raise HTTPException(status_code=404, detail="Onboarding session not found")
|
||||
|
||||
state = onboarding_service.get_state(db, session_id)
|
||||
file_bytes = await file.read()
|
||||
mime_type = file.content_type or "application/pdf"
|
||||
|
||||
patient_context = {
|
||||
"id": session.patient_id,
|
||||
"name": session.patient_name,
|
||||
"age": session.age,
|
||||
"gender": session.gender,
|
||||
"category": session.category_name,
|
||||
}
|
||||
|
||||
# Extract clinical data with Gemini
|
||||
extracted = await gemini_client.extract_from_document(
|
||||
file_bytes=file_bytes,
|
||||
mime_type=mime_type,
|
||||
patient_context=patient_context,
|
||||
)
|
||||
|
||||
# Merge document findings into onboarding state
|
||||
current_state = json.loads(state.state_json)
|
||||
|
||||
# Append medications if found
|
||||
meds_found = extracted.get("medications_found", [])
|
||||
if meds_found:
|
||||
existing_meds = current_state.get("medications", {})
|
||||
items = existing_meds.get("items", []) if isinstance(existing_meds, dict) else (existing_meds or [])
|
||||
items = list(set(items + meds_found))
|
||||
current_state["medications"] = {"status": "reported", "items": items}
|
||||
|
||||
# Append allergies if found
|
||||
allergies_found = extracted.get("allergies_found", [])
|
||||
if allergies_found:
|
||||
existing_allergies = current_state.get("allergies", {})
|
||||
items = existing_allergies.get("items", []) if isinstance(existing_allergies, dict) else (existing_allergies or [])
|
||||
items = list(set(items + allergies_found))
|
||||
current_state["allergies"] = {"status": "reported", "items": items}
|
||||
|
||||
# Add document findings to state
|
||||
current_state["document_findings"] = extracted.get("findings", [])
|
||||
|
||||
onboarding_service.update_state(db=db, state=state, data=current_state)
|
||||
|
||||
return DocumentExtractionResponse(
|
||||
success=True,
|
||||
session_id=session_id,
|
||||
document_type=extracted.get("document_type", "medical_report"),
|
||||
summary=extracted.get("summary", "Document analyzed successfully"),
|
||||
findings=extracted.get("findings", []),
|
||||
medications_found=meds_found,
|
||||
allergies_found=allergies_found,
|
||||
message="Medical document analyzed and integrated into onboarding session.",
|
||||
)
|
||||
@@ -0,0 +1,325 @@
|
||||
import json
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database.database import get_db
|
||||
from app.database.models import (
|
||||
OnboardingMessage,
|
||||
OnboardingSession,
|
||||
OnboardingState,
|
||||
)
|
||||
from app.schemas.onboarding import (
|
||||
CompleteOnboardingRequest,
|
||||
CompleteOnboardingResponse,
|
||||
SendMessageRequest,
|
||||
SendMessageResponse,
|
||||
StartOnboardingRequest,
|
||||
StartOnboardingResponse,
|
||||
)
|
||||
from app.services.ai_onboarding_service import (
|
||||
AIOnboardingService,
|
||||
)
|
||||
from app.services.completion_service import (
|
||||
CompletionService,
|
||||
)
|
||||
from app.services.onboarding_service import (
|
||||
OnboardingService,
|
||||
)
|
||||
from app.services.patient_submission_service import (
|
||||
PatientSubmissionService,
|
||||
)
|
||||
from app.services.result_extraction_service import (
|
||||
ResultExtractionService,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/v1/onboarding",
|
||||
tags=["Patient Onboarding"],
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/start",
|
||||
response_model=StartOnboardingResponse,
|
||||
)
|
||||
async def start_onboarding(
|
||||
request: StartOnboardingRequest,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
onboarding_service = OnboardingService()
|
||||
session_id = f"ONB-{uuid4().hex[:8].upper()}"
|
||||
|
||||
session = OnboardingSession(
|
||||
session_id=session_id,
|
||||
patient_id=request.patient.id,
|
||||
patient_name=request.patient.name,
|
||||
age=request.patient.age,
|
||||
gender=request.patient.gender,
|
||||
category_type=request.category.type,
|
||||
category_code=request.category.code,
|
||||
category_name=request.category.name,
|
||||
language=request.language,
|
||||
status="in_progress",
|
||||
stage="greeting",
|
||||
)
|
||||
|
||||
db.add(session)
|
||||
db.commit()
|
||||
db.refresh(session)
|
||||
|
||||
# Initialize empty state
|
||||
onboarding_service.get_state(db, session_id)
|
||||
|
||||
greeting_msg = (
|
||||
f"Hello {request.patient.name or 'there'}, "
|
||||
"welcome to CureSelect Healthcare. "
|
||||
"I'll ask you a few questions to understand "
|
||||
"your current health concern. Shall we begin?"
|
||||
)
|
||||
|
||||
# Save initial assistant greeting
|
||||
onboarding_service.save_message(
|
||||
db=db,
|
||||
session_id=session_id,
|
||||
role="assistant",
|
||||
message=greeting_msg,
|
||||
stage="greeting",
|
||||
)
|
||||
|
||||
return StartOnboardingResponse(
|
||||
success=True,
|
||||
session_id=session.session_id,
|
||||
patient_id=session.patient_id,
|
||||
category=request.category,
|
||||
language=session.language,
|
||||
status=session.status,
|
||||
stage=session.stage,
|
||||
message=greeting_msg,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{session_id}/message",
|
||||
response_model=SendMessageResponse,
|
||||
)
|
||||
async def send_message(
|
||||
session_id: str,
|
||||
request: SendMessageRequest,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
onboarding_service = OnboardingService()
|
||||
ai_service = AIOnboardingService()
|
||||
|
||||
# 1. Get session
|
||||
session = onboarding_service.get_session(
|
||||
db,
|
||||
session_id,
|
||||
)
|
||||
|
||||
if not session:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Onboarding session not found",
|
||||
)
|
||||
|
||||
# 2. Check session
|
||||
if session.status != "in_progress":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Onboarding session is not active",
|
||||
)
|
||||
|
||||
# 3. Save patient message
|
||||
onboarding_service.save_message(
|
||||
db=db,
|
||||
session_id=session_id,
|
||||
role="patient",
|
||||
message=request.message,
|
||||
stage=session.stage,
|
||||
)
|
||||
|
||||
# 4. Get state
|
||||
state = onboarding_service.get_state(
|
||||
db,
|
||||
session_id,
|
||||
)
|
||||
|
||||
# 5. Get conversation history
|
||||
messages = (
|
||||
db.query(OnboardingMessage)
|
||||
.filter(
|
||||
OnboardingMessage.session_id == session_id
|
||||
)
|
||||
.order_by(
|
||||
OnboardingMessage.created_at.asc()
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
conversation_history = [
|
||||
{
|
||||
"role": message.role,
|
||||
"message": message.message,
|
||||
"stage": message.stage,
|
||||
}
|
||||
for message in messages
|
||||
]
|
||||
|
||||
# 6. Send to AI
|
||||
ai_response = await ai_service.process_message(
|
||||
session=session,
|
||||
state=state,
|
||||
conversation_history=conversation_history,
|
||||
user_message=request.message,
|
||||
)
|
||||
|
||||
# 7. Update state
|
||||
current_state = json.loads(
|
||||
state.state_json
|
||||
)
|
||||
|
||||
updates = ai_response.get(
|
||||
"state_updates",
|
||||
{},
|
||||
)
|
||||
|
||||
for key, value in updates.items():
|
||||
if value is not None:
|
||||
if isinstance(value, list) and len(value) == 0:
|
||||
continue
|
||||
current_state[key] = value
|
||||
|
||||
onboarding_service.update_state(
|
||||
db=db,
|
||||
state=state,
|
||||
data=current_state,
|
||||
)
|
||||
|
||||
# 8. Update stage
|
||||
session.stage = ai_response.get("next_stage", session.stage)
|
||||
|
||||
# 9. Check completion
|
||||
if ai_response.get("completed"):
|
||||
session.status = "completed"
|
||||
|
||||
db.commit()
|
||||
|
||||
# 10. Save AI message
|
||||
onboarding_service.save_message(
|
||||
db=db,
|
||||
session_id=session_id,
|
||||
role="assistant",
|
||||
message=ai_response["reply"],
|
||||
stage=session.stage,
|
||||
)
|
||||
|
||||
return SendMessageResponse(
|
||||
success=True,
|
||||
session_id=session_id,
|
||||
stage=session.stage,
|
||||
message=ai_response["reply"],
|
||||
status=session.status,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{session_id}/complete",
|
||||
response_model=CompleteOnboardingResponse,
|
||||
)
|
||||
async def complete_onboarding(
|
||||
session_id: str,
|
||||
request: CompleteOnboardingRequest,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
onboarding_service = OnboardingService()
|
||||
completion_service = CompletionService()
|
||||
result_service = ResultExtractionService()
|
||||
submission_service = PatientSubmissionService()
|
||||
|
||||
# 1. Find session
|
||||
session = onboarding_service.get_session(
|
||||
db,
|
||||
session_id,
|
||||
)
|
||||
|
||||
if not session:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Onboarding session not found",
|
||||
)
|
||||
|
||||
# 2. Load state
|
||||
state = onboarding_service.get_state(
|
||||
db,
|
||||
session_id,
|
||||
)
|
||||
|
||||
# Check if complete
|
||||
has_details = completion_service.is_complete(session, state)
|
||||
|
||||
# 4. Get conversation history
|
||||
messages = (
|
||||
db.query(OnboardingMessage)
|
||||
.filter(OnboardingMessage.session_id == session_id)
|
||||
.order_by(OnboardingMessage.created_at.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
conversation_history = [
|
||||
{
|
||||
"role": message.role,
|
||||
"message": message.message,
|
||||
"stage": message.stage,
|
||||
}
|
||||
for message in messages
|
||||
]
|
||||
|
||||
# 5. Generate final structured result
|
||||
result = await result_service.extract_result(
|
||||
session=session,
|
||||
state=state,
|
||||
conversation_history=conversation_history,
|
||||
)
|
||||
|
||||
# 6. If patient has not confirmed yet, return summary for review
|
||||
if not request.confirmed:
|
||||
return CompleteOnboardingResponse(
|
||||
success=True,
|
||||
session_id=session_id,
|
||||
status="awaiting_confirmation",
|
||||
result=result,
|
||||
message="Please review the information and confirm.",
|
||||
)
|
||||
|
||||
# 7. Patient confirmed -> submit to existing patient / hospital API
|
||||
submission = await submission_service.submit(result)
|
||||
|
||||
if submission.get("success"):
|
||||
# 8. Successful submission -> clean up temporary SQLite session
|
||||
db.query(OnboardingMessage).filter(OnboardingMessage.session_id == session_id).delete()
|
||||
db.query(OnboardingState).filter(OnboardingState.session_id == session_id).delete()
|
||||
db.delete(session)
|
||||
db.commit()
|
||||
|
||||
return CompleteOnboardingResponse(
|
||||
success=True,
|
||||
session_id=session_id,
|
||||
status="submitted_and_cleaned",
|
||||
result=result,
|
||||
message="Onboarding successfully completed, saved to patient record, and temporary session cleaned.",
|
||||
)
|
||||
|
||||
# If submission failed, preserve SQLite session for retry
|
||||
session.status = "submission_pending_retry"
|
||||
db.commit()
|
||||
|
||||
return CompleteOnboardingResponse(
|
||||
success=False,
|
||||
session_id=session_id,
|
||||
status="submission_pending_retry",
|
||||
result=result,
|
||||
message="Patient API submission failed. Session preserved for retry.",
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
from app.api.voice_onboarding import router
|
||||
|
||||
# Export the router
|
||||
__all__ = ["router"]
|
||||
@@ -0,0 +1,343 @@
|
||||
import json
|
||||
from uuid import uuid4
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database.database import get_db
|
||||
from app.database.models import (
|
||||
OnboardingMessage,
|
||||
OnboardingSession,
|
||||
OnboardingState,
|
||||
)
|
||||
from app.schemas.onboarding import (
|
||||
CompleteOnboardingRequest,
|
||||
CompleteOnboardingResponse,
|
||||
)
|
||||
from app.schemas.voice import (
|
||||
StartVoiceOnboardingRequest,
|
||||
StartVoiceOnboardingResponse,
|
||||
VoiceTurnResponse,
|
||||
)
|
||||
from app.services.ai_onboarding_service import AIOnboardingService
|
||||
from app.services.completion_service import CompletionService
|
||||
from app.services.onboarding_service import OnboardingService
|
||||
from app.services.patient_submission_service import PatientSubmissionService
|
||||
from app.services.result_extraction_service import ResultExtractionService
|
||||
from app.services.voice_service import VoiceService
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/v1/voice-onboarding",
|
||||
tags=["Voice Onboarding"],
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/start",
|
||||
response_model=StartVoiceOnboardingResponse,
|
||||
)
|
||||
async def start_voice_onboarding(
|
||||
request: StartVoiceOnboardingRequest,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
onboarding_service = OnboardingService()
|
||||
voice_service = VoiceService()
|
||||
|
||||
session_id = f"VOICE-{uuid4().hex[:8].upper()}"
|
||||
|
||||
session = OnboardingSession(
|
||||
session_id=session_id,
|
||||
patient_id=request.patient.id,
|
||||
patient_name=request.patient.name,
|
||||
age=request.patient.age,
|
||||
gender=request.patient.gender,
|
||||
category_type=request.category.type,
|
||||
category_code=request.category.code,
|
||||
category_name=request.category.name,
|
||||
language=request.language,
|
||||
status="in_progress",
|
||||
stage="greeting",
|
||||
)
|
||||
|
||||
db.add(session)
|
||||
db.commit()
|
||||
db.refresh(session)
|
||||
|
||||
# Initialize empty state
|
||||
onboarding_service.get_state(db, session_id)
|
||||
|
||||
# Generate localized voice greeting
|
||||
greeting_text = voice_service.get_initial_greeting(
|
||||
request.patient.name,
|
||||
request.language,
|
||||
)
|
||||
|
||||
# Synthesize audio greeting
|
||||
audio_id, _ = await voice_service.text_to_speech(
|
||||
greeting_text,
|
||||
request.language,
|
||||
)
|
||||
|
||||
# Save initial assistant message
|
||||
onboarding_service.save_message(
|
||||
db=db,
|
||||
session_id=session_id,
|
||||
role="assistant",
|
||||
message=greeting_text,
|
||||
stage="greeting",
|
||||
)
|
||||
|
||||
return StartVoiceOnboardingResponse(
|
||||
success=True,
|
||||
session_id=session.session_id,
|
||||
patient_id=session.patient_id,
|
||||
category=request.category,
|
||||
language=session.language,
|
||||
status=session.status,
|
||||
stage=session.stage,
|
||||
greeting_text=greeting_text,
|
||||
audio_url=f"/v1/voice-onboarding/audio/{audio_id}",
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{session_id}/turn",
|
||||
response_model=VoiceTurnResponse,
|
||||
)
|
||||
async def voice_turn(
|
||||
session_id: str,
|
||||
audio: Optional[UploadFile] = File(None),
|
||||
text_fallback: Optional[str] = Form(None),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
onboarding_service = OnboardingService()
|
||||
voice_service = VoiceService()
|
||||
ai_service = AIOnboardingService()
|
||||
|
||||
# 1. Fetch active session
|
||||
session = onboarding_service.get_session(db, session_id)
|
||||
if not session:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Voice onboarding session not found",
|
||||
)
|
||||
|
||||
if session.status != "in_progress":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Voice onboarding session is not active",
|
||||
)
|
||||
|
||||
# 2. Transcribe speech if audio provided with multi-model Indic recognition
|
||||
patient_transcript = ""
|
||||
if audio:
|
||||
audio_bytes = await audio.read()
|
||||
if len(audio_bytes) > 0:
|
||||
patient_transcript = await voice_service.speech_to_text(
|
||||
audio_bytes=audio_bytes,
|
||||
filename=audio.filename or "recording.webm",
|
||||
language_code=session.language,
|
||||
specialty_code=session.category_code or "general",
|
||||
patient_context={
|
||||
"name": session.patient_name,
|
||||
"age": session.age,
|
||||
"gender": session.gender,
|
||||
},
|
||||
)
|
||||
|
||||
# If backend STT is empty, use frontend real-time transcript fallback
|
||||
if (not patient_transcript or not patient_transcript.strip()) and text_fallback and text_fallback.strip():
|
||||
from app.services.medical_speech_normalizer import normalize_medical_speech
|
||||
patient_transcript = normalize_medical_speech(text_fallback.strip(), language_code=session.language)
|
||||
|
||||
if not patient_transcript or not patient_transcript.strip():
|
||||
if session.language.startswith("ta"):
|
||||
patient_transcript = "எனக்கு உடல்நலக்குறைவு உள்ளது."
|
||||
elif session.language.startswith("hi"):
|
||||
patient_transcript = "मुझे स्वास्थ्य संबंधी समस्या है।"
|
||||
else:
|
||||
patient_transcript = "I am experiencing symptoms."
|
||||
|
||||
|
||||
|
||||
# 3. Save patient message
|
||||
onboarding_service.save_message(
|
||||
db=db,
|
||||
session_id=session_id,
|
||||
role="patient",
|
||||
message=patient_transcript,
|
||||
stage=session.stage,
|
||||
)
|
||||
|
||||
# 4. Fetch state & history
|
||||
state = onboarding_service.get_state(db, session_id)
|
||||
messages = (
|
||||
db.query(OnboardingMessage)
|
||||
.filter(OnboardingMessage.session_id == session_id)
|
||||
.order_by(OnboardingMessage.created_at.asc())
|
||||
.all()
|
||||
)
|
||||
conversation_history = [
|
||||
{
|
||||
"role": m.role,
|
||||
"message": m.message,
|
||||
"stage": m.stage,
|
||||
}
|
||||
for m in messages
|
||||
]
|
||||
|
||||
# 5. AI Reasoning & Extraction
|
||||
ai_response = await ai_service.process_message(
|
||||
session=session,
|
||||
state=state,
|
||||
conversation_history=conversation_history,
|
||||
user_message=patient_transcript,
|
||||
)
|
||||
|
||||
# 6. Update Structured Clinical State
|
||||
current_state = json.loads(state.state_json)
|
||||
updates = ai_response.get("state_updates", {})
|
||||
for k, v in updates.items():
|
||||
if v is not None:
|
||||
if isinstance(v, list) and len(v) == 0:
|
||||
continue
|
||||
current_state[k] = v
|
||||
|
||||
onboarding_service.update_state(
|
||||
db=db,
|
||||
state=state,
|
||||
data=current_state,
|
||||
)
|
||||
|
||||
# 7. Update session stage & status
|
||||
ai_reply_text = ai_response["reply"]
|
||||
session.stage = ai_response.get("next_stage", session.stage)
|
||||
is_completed = bool(ai_response.get("completed", False))
|
||||
if is_completed:
|
||||
session.status = "completed"
|
||||
|
||||
db.commit()
|
||||
|
||||
# 8. Save Assistant Message
|
||||
onboarding_service.save_message(
|
||||
db=db,
|
||||
session_id=session_id,
|
||||
role="assistant",
|
||||
message=ai_reply_text,
|
||||
stage=session.stage,
|
||||
)
|
||||
|
||||
# 9. Synthesize AI Speech Audio (TTS)
|
||||
audio_id, _ = await voice_service.text_to_speech(
|
||||
text=ai_reply_text,
|
||||
language_code=session.language,
|
||||
)
|
||||
|
||||
return VoiceTurnResponse(
|
||||
success=True,
|
||||
session_id=session_id,
|
||||
patient_transcript=patient_transcript,
|
||||
ai_text=ai_reply_text,
|
||||
stage=session.stage,
|
||||
completed=is_completed,
|
||||
audio_url=f"/v1/voice-onboarding/audio/{audio_id}",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/audio/{audio_id}")
|
||||
async def get_audio(audio_id: str):
|
||||
voice_service = VoiceService()
|
||||
audio_path = voice_service.get_audio_path(audio_id)
|
||||
|
||||
if not audio_path or not audio_path.exists():
|
||||
raise HTTPException(status_code=404, detail="Audio file not found")
|
||||
|
||||
media_type = "audio/mpeg" if audio_path.suffix == ".mp3" else "audio/wav"
|
||||
return FileResponse(
|
||||
path=str(audio_path),
|
||||
media_type=media_type,
|
||||
filename=audio_path.name,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{session_id}/complete",
|
||||
response_model=CompleteOnboardingResponse,
|
||||
)
|
||||
async def complete_voice_onboarding(
|
||||
session_id: str,
|
||||
request: CompleteOnboardingRequest,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
onboarding_service = OnboardingService()
|
||||
completion_service = CompletionService()
|
||||
result_service = ResultExtractionService()
|
||||
submission_service = PatientSubmissionService()
|
||||
|
||||
session = onboarding_service.get_session(db, session_id)
|
||||
if not session:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Voice onboarding session not found",
|
||||
)
|
||||
|
||||
state = onboarding_service.get_state(db, session_id)
|
||||
|
||||
# Check if there is any conversation to summarize
|
||||
has_details = completion_service.is_complete(session, state)
|
||||
|
||||
|
||||
messages = (
|
||||
db.query(OnboardingMessage)
|
||||
.filter(OnboardingMessage.session_id == session_id)
|
||||
.order_by(OnboardingMessage.created_at.asc())
|
||||
.all()
|
||||
)
|
||||
conversation_history = [
|
||||
{"role": m.role, "message": m.message, "stage": m.stage}
|
||||
for m in messages
|
||||
]
|
||||
|
||||
result = await result_service.extract_result(
|
||||
session=session,
|
||||
state=state,
|
||||
conversation_history=conversation_history,
|
||||
)
|
||||
|
||||
if not request.confirmed:
|
||||
return CompleteOnboardingResponse(
|
||||
success=True,
|
||||
session_id=session_id,
|
||||
status="awaiting_confirmation",
|
||||
result=result,
|
||||
message="Please review the collected information and confirm by voice or tap.",
|
||||
)
|
||||
|
||||
submission = await submission_service.submit(result)
|
||||
|
||||
if submission.get("success"):
|
||||
db.query(OnboardingMessage).filter(OnboardingMessage.session_id == session_id).delete()
|
||||
db.query(OnboardingState).filter(OnboardingState.session_id == session_id).delete()
|
||||
db.delete(session)
|
||||
db.commit()
|
||||
|
||||
return CompleteOnboardingResponse(
|
||||
success=True,
|
||||
session_id=session_id,
|
||||
status="submitted_and_cleaned",
|
||||
result=result,
|
||||
message="Voice onboarding successfully saved to patient record.",
|
||||
)
|
||||
|
||||
session.status = "submission_pending_retry"
|
||||
db.commit()
|
||||
|
||||
return CompleteOnboardingResponse(
|
||||
success=False,
|
||||
session_id=session_id,
|
||||
status="submission_pending_retry",
|
||||
result=result,
|
||||
message="Patient API submission failed. Session preserved for retry.",
|
||||
)
|
||||
Reference in New Issue
Block a user