You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
326 lines
8.9 KiB
326 lines
8.9 KiB
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"sort"
|
|
"time"
|
|
|
|
"health-ai/internal/model"
|
|
"health-ai/internal/repository/impl"
|
|
)
|
|
|
|
type ConstitutionService struct {
|
|
repo *impl.ConstitutionRepository
|
|
}
|
|
|
|
func NewConstitutionService() *ConstitutionService {
|
|
return &ConstitutionService{
|
|
repo: impl.NewConstitutionRepository(),
|
|
}
|
|
}
|
|
|
|
// ================= 请求结构体 =================
|
|
|
|
// AnswerRequest 单个答案请求
|
|
type AnswerRequest struct {
|
|
QuestionID uint `json:"question_id" binding:"required"`
|
|
Score int `json:"score" binding:"required,min=1,max=5"`
|
|
}
|
|
|
|
// SubmitAssessmentRequest 提交测评请求
|
|
type SubmitAssessmentRequest struct {
|
|
Answers []AnswerRequest `json:"answers" binding:"required,dive"`
|
|
}
|
|
|
|
// ================= 响应结构体 =================
|
|
|
|
// ConstitutionScore 体质得分
|
|
type ConstitutionScore struct {
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Score float64 `json:"score"`
|
|
Description string `json:"description"`
|
|
}
|
|
|
|
// AssessmentResult 测评结果
|
|
type AssessmentResult struct {
|
|
ID uint `json:"id"`
|
|
PrimaryConstitution ConstitutionScore `json:"primary_constitution"`
|
|
SecondaryConstitutions []ConstitutionScore `json:"secondary_constitutions"`
|
|
AllScores []ConstitutionScore `json:"all_scores"`
|
|
Recommendations map[string]map[string]string `json:"recommendations"`
|
|
AssessedAt time.Time `json:"assessed_at"`
|
|
}
|
|
|
|
// QuestionGroup 问题分组
|
|
type QuestionGroup struct {
|
|
ConstitutionType string `json:"constitution_type"`
|
|
ConstitutionName string `json:"constitution_name"`
|
|
Questions []model.QuestionBank `json:"questions"`
|
|
}
|
|
|
|
// ================= Service 方法 =================
|
|
|
|
// GetQuestions 获取所有问卷题目
|
|
func (s *ConstitutionService) GetQuestions() ([]model.QuestionBank, error) {
|
|
return s.repo.GetQuestions()
|
|
}
|
|
|
|
// GetQuestionsGrouped 获取分组的问卷题目
|
|
func (s *ConstitutionService) GetQuestionsGrouped() ([]QuestionGroup, error) {
|
|
questions, err := s.repo.GetQuestions()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 按体质类型分组
|
|
groupMap := make(map[string][]model.QuestionBank)
|
|
for _, q := range questions {
|
|
groupMap[q.ConstitutionType] = append(groupMap[q.ConstitutionType], q)
|
|
}
|
|
|
|
// 转换为数组
|
|
var groups []QuestionGroup
|
|
typeOrder := []string{
|
|
model.ConstitutionPinghe,
|
|
model.ConstitutionQixu,
|
|
model.ConstitutionYangxu,
|
|
model.ConstitutionYinxu,
|
|
model.ConstitutionTanshi,
|
|
model.ConstitutionShire,
|
|
model.ConstitutionXueyu,
|
|
model.ConstitutionQiyu,
|
|
model.ConstitutionTebing,
|
|
}
|
|
|
|
for _, cType := range typeOrder {
|
|
if qs, ok := groupMap[cType]; ok {
|
|
groups = append(groups, QuestionGroup{
|
|
ConstitutionType: cType,
|
|
ConstitutionName: model.ConstitutionNames[cType],
|
|
Questions: qs,
|
|
})
|
|
}
|
|
}
|
|
|
|
return groups, nil
|
|
}
|
|
|
|
// SubmitAssessment 提交测评并计算结果
|
|
func (s *ConstitutionService) SubmitAssessment(userID uint, req *SubmitAssessmentRequest) (*AssessmentResult, error) {
|
|
// 获取所有问题
|
|
questions, err := s.repo.GetQuestions()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(questions) == 0 {
|
|
return nil, errors.New("问卷题库为空,请联系管理员")
|
|
}
|
|
|
|
// 构建问题ID到体质类型的映射
|
|
questionTypeMap := make(map[uint]string)
|
|
typeQuestionCount := make(map[string]int)
|
|
for _, q := range questions {
|
|
questionTypeMap[q.ID] = q.ConstitutionType
|
|
typeQuestionCount[q.ConstitutionType]++
|
|
}
|
|
|
|
// 计算各体质原始分
|
|
typeScores := make(map[string]int)
|
|
for _, answer := range req.Answers {
|
|
if cType, ok := questionTypeMap[answer.QuestionID]; ok {
|
|
typeScores[cType] += answer.Score
|
|
}
|
|
}
|
|
|
|
// 计算转化分
|
|
// 转化分 = (原始分 - 条目数) / (条目数 × 4) × 100
|
|
allScores := make([]ConstitutionScore, 0)
|
|
for cType, rawScore := range typeScores {
|
|
questionCount := typeQuestionCount[cType]
|
|
if questionCount == 0 {
|
|
continue
|
|
}
|
|
transformedScore := float64(rawScore-questionCount) / float64(questionCount*4) * 100
|
|
if transformedScore < 0 {
|
|
transformedScore = 0
|
|
}
|
|
if transformedScore > 100 {
|
|
transformedScore = 100
|
|
}
|
|
|
|
allScores = append(allScores, ConstitutionScore{
|
|
Type: cType,
|
|
Name: model.ConstitutionNames[cType],
|
|
Score: transformedScore,
|
|
Description: model.ConstitutionDescriptions[cType],
|
|
})
|
|
}
|
|
|
|
// 按分数排序
|
|
sort.Slice(allScores, func(i, j int) bool {
|
|
return allScores[i].Score > allScores[j].Score
|
|
})
|
|
|
|
// 判定主要体质和次要体质
|
|
var primary ConstitutionScore
|
|
var secondary []ConstitutionScore
|
|
|
|
// 平和质特殊判定
|
|
pingheScore := float64(0)
|
|
otherMax := float64(0)
|
|
for _, score := range allScores {
|
|
if score.Type == model.ConstitutionPinghe {
|
|
pingheScore = score.Score
|
|
} else if score.Score > otherMax {
|
|
otherMax = score.Score
|
|
}
|
|
}
|
|
|
|
if pingheScore >= 60 && otherMax < 30 {
|
|
// 判定为平和质
|
|
for _, score := range allScores {
|
|
if score.Type == model.ConstitutionPinghe {
|
|
primary = score
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// 判定为偏颇体质
|
|
for _, score := range allScores {
|
|
if score.Type == model.ConstitutionPinghe {
|
|
continue
|
|
}
|
|
if primary.Type == "" && score.Score >= 40 {
|
|
primary = score
|
|
} else if score.Score >= 30 {
|
|
secondary = append(secondary, score)
|
|
}
|
|
}
|
|
// 如果没有≥40的,取最高分(排除平和质)
|
|
if primary.Type == "" {
|
|
for _, score := range allScores {
|
|
if score.Type != model.ConstitutionPinghe {
|
|
primary = score
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 获取调养建议
|
|
recommendations := make(map[string]map[string]string)
|
|
if primary.Type != "" {
|
|
recommendations[primary.Type] = model.ConstitutionRecommendations[primary.Type]
|
|
}
|
|
for _, sec := range secondary {
|
|
recommendations[sec.Type] = model.ConstitutionRecommendations[sec.Type]
|
|
}
|
|
|
|
// 保存评估结果
|
|
scoresJSON, _ := json.Marshal(allScores)
|
|
secondaryJSON, _ := json.Marshal(secondary)
|
|
recsJSON, _ := json.Marshal(recommendations)
|
|
|
|
assessment := &model.ConstitutionAssessment{
|
|
UserID: userID,
|
|
AssessedAt: time.Now(),
|
|
Scores: string(scoresJSON),
|
|
PrimaryConstitution: primary.Type,
|
|
SecondaryConstitutions: string(secondaryJSON),
|
|
Recommendations: string(recsJSON),
|
|
}
|
|
if err := s.repo.CreateAssessment(assessment); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// 保存答案记录
|
|
answers := make([]model.AssessmentAnswer, len(req.Answers))
|
|
for i, a := range req.Answers {
|
|
answers[i] = model.AssessmentAnswer{
|
|
AssessmentID: assessment.ID,
|
|
QuestionID: a.QuestionID,
|
|
Score: a.Score,
|
|
}
|
|
}
|
|
if err := s.repo.CreateAnswers(answers); err != nil {
|
|
// 答案保存失败不影响结果返回
|
|
}
|
|
|
|
return &AssessmentResult{
|
|
ID: assessment.ID,
|
|
PrimaryConstitution: primary,
|
|
SecondaryConstitutions: secondary,
|
|
AllScores: allScores,
|
|
Recommendations: recommendations,
|
|
AssessedAt: assessment.AssessedAt,
|
|
}, nil
|
|
}
|
|
|
|
// GetLatestResult 获取用户最新的测评结果
|
|
func (s *ConstitutionService) GetLatestResult(userID uint) (*AssessmentResult, error) {
|
|
assessment, err := s.repo.GetLatestAssessment(userID)
|
|
if err != nil {
|
|
return nil, errors.New("暂无体质测评记录")
|
|
}
|
|
|
|
return s.parseAssessment(assessment)
|
|
}
|
|
|
|
// GetHistory 获取用户的测评历史
|
|
func (s *ConstitutionService) GetHistory(userID uint, limit int) ([]AssessmentResult, error) {
|
|
assessments, err := s.repo.GetAssessmentHistory(userID, limit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
results := make([]AssessmentResult, 0, len(assessments))
|
|
for _, a := range assessments {
|
|
result, err := s.parseAssessment(&a)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
results = append(results, *result)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// GetRecommendations 获取体质调养建议
|
|
func (s *ConstitutionService) GetRecommendations(userID uint) (map[string]map[string]string, error) {
|
|
result, err := s.GetLatestResult(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return result.Recommendations, nil
|
|
}
|
|
|
|
// parseAssessment 解析测评记录为结果
|
|
func (s *ConstitutionService) parseAssessment(assessment *model.ConstitutionAssessment) (*AssessmentResult, error) {
|
|
var allScores []ConstitutionScore
|
|
var secondary []ConstitutionScore
|
|
var recommendations map[string]map[string]string
|
|
|
|
json.Unmarshal([]byte(assessment.Scores), &allScores)
|
|
json.Unmarshal([]byte(assessment.SecondaryConstitutions), &secondary)
|
|
json.Unmarshal([]byte(assessment.Recommendations), &recommendations)
|
|
|
|
var primary ConstitutionScore
|
|
for _, score := range allScores {
|
|
if score.Type == assessment.PrimaryConstitution {
|
|
primary = score
|
|
break
|
|
}
|
|
}
|
|
|
|
return &AssessmentResult{
|
|
ID: assessment.ID,
|
|
PrimaryConstitution: primary,
|
|
SecondaryConstitutions: secondary,
|
|
AllScores: allScores,
|
|
Recommendations: recommendations,
|
|
AssessedAt: assessment.AssessedAt,
|
|
}, nil
|
|
}
|
|
|