healthapp
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

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
}