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 }