Files
Mutual_Fund/service/questionnaire.py
T
2026-09-11 09:51:46 +08:00

86 lines
2.9 KiB
Python

"""问卷服务:提交风险测评 → 完整性/选项校验 → 算分定级 → 落风评记录 → 回写画像。"""
from __future__ import annotations
from datetime import date, timedelta
from sqlalchemy.ext.asyncio import AsyncSession
from model.fin_risk_assessment import FinRiskAssessment
from repositories.questionnaire import QuestionRepo, QuestionnaireRepo
from repositories.risk_assessment import CustomerProfileRepo, RiskAssessmentRepo
from utils.exceptions import NotFoundError, ParamError
# 分数 → 风险等级映射(总分 0-100,20 分一档;后续可迁移到 sys_config 运营化)
_SCORE_LEVELS = (
(80, "激进"),
(60, "进取"),
(40, "平衡"),
(20, "稳健"),
(0, "保守"),
)
def _score_to_level(score: int) -> str:
for threshold, level in _SCORE_LEVELS:
if score >= threshold:
return level
return "保守"
async def submit_assessment(
db: AsyncSession,
customer_id: int,
questionnaire_id: int,
answers: list[dict],
) -> dict:
questionnaire = await QuestionnaireRepo(db).get(questionnaire_id)
if questionnaire is None:
raise NotFoundError("问卷不存在")
if questionnaire.status != "启用":
raise ParamError("问卷未启用")
questions = await QuestionRepo(db).list_by_questionnaire(questionnaire_id)
if not questions:
raise ParamError("问卷尚未配置题目")
# 提交答案按 question_no(题号)建索引,校验必填完整性与选项合法性
by_no = {a["question_no"]: a["option"] for a in answers}
total_score = 0
detail = []
for q in questions:
option = by_no.get(q.question_no)
if option is None:
raise ParamError(f"第{q.question_no}题未作答")
scores = q.score_json or {}
if option not in scores:
raise ParamError(f"第{q.question_no}题选项非法: {option}")
score = int(scores[option])
total_score += score
detail.append({"q": q.question_no, "a": option, "score": score})
risk_level = _score_to_level(total_score)
today = date.today()
valid_until = today + timedelta(days=365) # 监管要求一年一评
record = FinRiskAssessment(
customer_id=customer_id,
assessment_date=today,
question_version=questionnaire.version,
total_score=total_score,
risk_level=risk_level,
answers=detail,
assessor_type="AI评估",
valid_until=valid_until,
)
record = await RiskAssessmentRepo(db).add(record)
# 回写画像:无画像初始化,有画像更新风险等级/评分并递增版本号
await CustomerProfileRepo(db).upsert_risk(customer_id, risk_level, total_score)
return {
"assessment_id": record.id,
"total_score": total_score,
"risk_level": risk_level,
"valid_until": valid_until.isoformat(),
}