Files
2026-02-28 15:06:52 +08:00

402 lines
12 KiB
Python

"""
满意度调查 API 接口
"""
from typing import Optional, List
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel, Field, ConfigDict
from datetime import datetime
import json
from app.core.database import get_db
from app.models.models import SurveyStatus, SurveyType, QuestionType
from app.services.survey_service import SurveyService
router = APIRouter(prefix="/surveys", tags=["满意度调查"])
# ==================== Pydantic Schemas ====================
class QuestionCreate(BaseModel):
"""题目创建"""
question_text: str = Field(..., description="题目内容")
question_type: str = Field(..., description="题目类型")
options: Optional[List[dict]] = Field(None, description="选项列表")
score_max: int = Field(5, description="最高分值")
is_required: bool = Field(True, description="是否必答")
class QuestionResponse(BaseModel):
"""题目响应"""
model_config = ConfigDict(from_attributes=True)
id: int
question_text: str
question_type: str
options: Optional[str] = None
score_max: int
is_required: bool
sort_order: int
class SurveyCreate(BaseModel):
"""问卷创建"""
survey_name: str = Field(..., description="问卷名称")
survey_code: str = Field(..., description="问卷编码")
survey_type: str = Field(..., description="问卷类型")
description: Optional[str] = Field(None, description="描述")
target_departments: Optional[List[int]] = Field(None, description="目标科室")
is_anonymous: bool = Field(True, description="是否匿名")
questions: Optional[List[QuestionCreate]] = Field(None, description="题目列表")
class SurveyUpdate(BaseModel):
"""问卷更新"""
survey_name: Optional[str] = None
description: Optional[str] = None
target_departments: Optional[List[int]] = None
is_anonymous: Optional[bool] = None
class SurveyResponse(BaseModel):
"""问卷响应"""
model_config = ConfigDict(from_attributes=True)
id: int
survey_name: str
survey_code: str
survey_type: str
description: Optional[str] = None
target_departments: Optional[str] = None
total_questions: int
status: str
start_date: Optional[datetime] = None
end_date: Optional[datetime] = None
is_anonymous: bool
created_at: datetime
class SurveyDetailResponse(SurveyResponse):
"""问卷详情响应"""
questions: Optional[List[QuestionResponse]] = None
class AnswerSubmit(BaseModel):
"""回答提交"""
question_id: int
answer_value: str
class ResponseSubmit(BaseModel):
"""问卷回答提交"""
department_id: Optional[int] = None
respondent_type: str = "patient"
respondent_id: Optional[int] = None
respondent_phone: Optional[str] = None
answers: List[AnswerSubmit]
class ResponseResult(BaseModel):
"""回答结果"""
model_config = ConfigDict(from_attributes=True)
id: int
survey_id: int
department_id: Optional[int] = None
total_score: float
max_score: float
satisfaction_rate: float
submitted_at: datetime
# ==================== API Endpoints ====================
@router.get("", summary="获取问卷列表")
async def get_survey_list(
survey_type: Optional[str] = Query(None, description="问卷类型"),
status: Optional[str] = Query(None, description="状态"),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
db: AsyncSession = Depends(get_db)
):
"""获取问卷列表"""
type_filter = SurveyType(survey_type) if survey_type else None
status_filter = SurveyStatus(status) if status else None
surveys, total = await SurveyService.get_survey_list(
db, type_filter, status_filter, page, page_size
)
return {
"code": 200,
"message": "success",
"data": [
SurveyResponse.model_validate(s) for s in surveys
],
"total": total,
"page": page,
"page_size": page_size
}
@router.get("/{survey_id}", summary="获取问卷详情")
async def get_survey_detail(
survey_id: int,
db: AsyncSession = Depends(get_db)
):
"""获取问卷详情"""
survey = await SurveyService.get_survey_by_id(db, survey_id)
if not survey:
raise HTTPException(status_code=404, detail="问卷不存在")
detail = SurveyDetailResponse.model_validate(survey)
if survey.questions:
detail.questions = [QuestionResponse.model_validate(q) for q in survey.questions]
return {
"code": 200,
"message": "success",
"data": detail
}
@router.post("", summary="创建问卷")
async def create_survey(
survey_data: SurveyCreate,
db: AsyncSession = Depends(get_db)
):
"""创建问卷"""
try:
survey_type = SurveyType(survey_data.survey_type)
except ValueError:
raise HTTPException(status_code=400, detail="无效的问卷类型")
survey = await SurveyService.create_survey(
db,
survey_name=survey_data.survey_name,
survey_code=survey_data.survey_code,
survey_type=survey_type,
description=survey_data.description,
target_departments=survey_data.target_departments,
is_anonymous=survey_data.is_anonymous
)
# 添加题目
if survey_data.questions:
for question in survey_data.questions:
try:
question_type = QuestionType(question.question_type)
except ValueError:
continue
await SurveyService.add_question(
db,
survey_id=survey.id,
question_text=question.question_text,
question_type=question_type,
options=question.options,
score_max=question.score_max,
is_required=question.is_required
)
await db.commit()
return {
"code": 200,
"message": "创建成功",
"data": SurveyResponse.model_validate(survey)
}
@router.put("/{survey_id}", summary="更新问卷")
async def update_survey(
survey_id: int,
survey_data: SurveyUpdate,
db: AsyncSession = Depends(get_db)
):
"""更新问卷"""
try:
update_dict = survey_data.model_dump(exclude_unset=True)
survey = await SurveyService.update_survey(db, survey_id, **update_dict)
if not survey:
raise HTTPException(status_code=404, detail="问卷不存在")
await db.commit()
return {
"code": 200,
"message": "更新成功",
"data": SurveyResponse.model_validate(survey)
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/{survey_id}/publish", summary="发布问卷")
async def publish_survey(
survey_id: int,
db: AsyncSession = Depends(get_db)
):
"""发布问卷"""
try:
survey = await SurveyService.publish_survey(db, survey_id)
if not survey:
raise HTTPException(status_code=404, detail="问卷不存在")
await db.commit()
return {
"code": 200,
"message": "发布成功",
"data": SurveyResponse.model_validate(survey)
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/{survey_id}/close", summary="结束问卷")
async def close_survey(
survey_id: int,
db: AsyncSession = Depends(get_db)
):
"""结束问卷"""
try:
survey = await SurveyService.close_survey(db, survey_id)
if not survey:
raise HTTPException(status_code=404, detail="问卷不存在")
await db.commit()
return {
"code": 200,
"message": "问卷已结束",
"data": SurveyResponse.model_validate(survey)
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.delete("/{survey_id}", summary="删除问卷")
async def delete_survey(
survey_id: int,
db: AsyncSession = Depends(get_db)
):
"""删除问卷"""
try:
success = await SurveyService.delete_survey(db, survey_id)
if not success:
raise HTTPException(status_code=404, detail="问卷不存在")
await db.commit()
return {
"code": 200,
"message": "删除成功"
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.post("/{survey_id}/submit", summary="提交问卷回答")
async def submit_response(
survey_id: int,
response_data: ResponseSubmit,
db: AsyncSession = Depends(get_db)
):
"""提交问卷回答"""
try:
answers = [a.model_dump() for a in response_data.answers]
response = await SurveyService.submit_response(
db,
survey_id=survey_id,
department_id=response_data.department_id,
answers=answers,
respondent_type=response_data.respondent_type,
respondent_id=response_data.respondent_id,
respondent_phone=response_data.respondent_phone
)
await db.commit()
return {
"code": 200,
"message": "提交成功",
"data": ResponseResult.model_validate(response)
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@router.get("/{survey_id}/stats", summary="获取问卷统计")
async def get_survey_stats(
survey_id: int,
db: AsyncSession = Depends(get_db)
):
"""获取问卷各题目统计"""
survey = await SurveyService.get_survey_by_id(db, survey_id)
if not survey:
raise HTTPException(status_code=404, detail="问卷不存在")
stats = await SurveyService.get_question_stats(db, survey_id)
return {
"code": 200,
"message": "success",
"data": {
"survey_id": survey_id,
"survey_name": survey.survey_name,
"total_responses": await _get_response_count(db, survey_id),
"question_stats": stats
}
}
@router.get("/stats/department", summary="获取科室满意度统计")
async def get_department_satisfaction(
survey_id: Optional[int] = Query(None, description="问卷ID"),
department_id: Optional[int] = Query(None, description="科室ID"),
period_year: Optional[int] = Query(None, description="年度"),
period_month: Optional[int] = Query(None, description="月份"),
db: AsyncSession = Depends(get_db)
):
"""获取科室满意度统计"""
stats = await SurveyService.get_department_satisfaction(
db, survey_id, department_id, period_year, period_month
)
return {
"code": 200,
"message": "success",
"data": stats
}
@router.get("/stats/trend/{department_id}", summary="获取满意度趋势")
async def get_satisfaction_trend(
department_id: int,
months: int = Query(6, ge=1, le=12, description="查询月数"),
db: AsyncSession = Depends(get_db)
):
"""获取满意度趋势"""
trend = await SurveyService.get_satisfaction_trend(db, department_id, months)
return {
"code": 200,
"message": "success",
"data": trend
}
async def _get_response_count(db: AsyncSession, survey_id: int) -> int:
"""获取问卷回答数"""
from sqlalchemy import select, func
from app.models.models import SurveyResponse
result = await db.execute(
select(func.count(SurveyResponse.id))
.where(SurveyResponse.survey_id == survey_id)
)
return result.scalar() or 0