From a4e3c9d77f4130535b9f8dfe3d6c7da069fd84ca Mon Sep 17 00:00:00 2001 From: laoboli <1293528695@qq.com> Date: Mon, 20 Jul 2026 09:05:31 +0800 Subject: [PATCH] refactor: ai analyze --- controllers/ai.go | 92 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 24 deletions(-) diff --git a/controllers/ai.go b/controllers/ai.go index ed02abc..b1a8640 100644 --- a/controllers/ai.go +++ b/controllers/ai.go @@ -28,11 +28,48 @@ import ( const ( analysisTypeHeartRateOnly = "heart_rate_only" analysisTypeHeartRateWithSteps = "heart_rate_with_steps" + audienceTypeKindergarten = "kindergarten" + audienceTypePrimarySchool = "primary_school" + audienceTypeMiddleSchool = "middle_school" + audienceTypeUniversity = "university" + audienceTypeElderly = "elderly" sourceUpload = "upload" sourceCloud = "cloud" sourceWechat = "wechat" ) +func resolveAudienceLabel(audienceType string) (string, bool) { + switch strings.ToLower(strings.TrimSpace(audienceType)) { + case "", audienceTypeKindergarten: + return "幼儿园", true + case audienceTypePrimarySchool: + return "小学", true + case audienceTypeMiddleSchool: + return "中学", true + case audienceTypeUniversity: + return "大学", true + case audienceTypeElderly: + return "老年人", true + default: + return "", false + } +} + +func resolveAudienceDescription(audienceType, audienceDetail string) (string, bool) { + audienceLabel, ok := resolveAudienceLabel(audienceType) + if !ok { + return "", false + } + detail := strings.TrimSpace(audienceDetail) + if detail == "" || strings.Contains(detail, audienceLabel) { + if detail != "" { + return detail, true + } + return audienceLabel, true + } + return audienceLabel + detail, true +} + func readDocxContent(fileHeader *multipart.FileHeader) (string, error) { tempFile, err := os.CreateTemp("", "upload_*.docx") if err != nil { @@ -113,7 +150,7 @@ func readCSVContent(fileHeader *multipart.FileHeader) (string, error) { return resultContent, nil } -func buildAnalysisPrompt(teachingPlanContent, heartRateContent, analysisType, stepContent string) string { +func buildAnalysisPrompt(teachingPlanContent, heartRateContent, analysisType, stepContent, audienceLabel string) string { if analysisType == analysisTypeHeartRateWithSteps { return fmt.Sprintf(`请根据以下体育课堂的教案、心率监测数据和训练结束步数汇总,生成一份详细的课堂分析报告: @@ -126,7 +163,7 @@ func buildAnalysisPrompt(teachingPlanContent, heartRateContent, analysisType, st ## 训练结束步数汇总: %s -这是一份幼儿园体育课的教案、课程心率监测数据和训练结束步数汇总。请结合三类信息分析课程教学效果、运动量和运动负荷情况是否科学,并提出课程设计的优化方案。 +这是一份面向%s人群的体育课教案、课程心率监测数据和训练结束步数汇总。请结合该人群的年龄、生理特点和运动安全要求,分析课程教学效果、运动量和运动负荷情况是否科学,并提出课程设计的优化方案。 分析要求: 1. 步数只作为移动量、活动密度和参与度的辅助参考,不能替代心率负荷判断。 @@ -135,12 +172,12 @@ func buildAnalysisPrompt(teachingPlanContent, heartRateContent, analysisType, st 4. 在教学建议中明确说明本节课是否适合继续使用步数作为辅助分析指标。 优化方案参考如下格式,教学过程需要详细一些: -# 幼儿体育教案(华侨大学版本) +# 体育教案优化方案 | 项目 | 内容 | | ------------ | -------------------------------- | | **课程名** | | -| **年段** | 小 中 大 | +| **适用人群/年级** | | | **教师姓名** | | | **时间** | 年 月 日 | | **地点** | | @@ -161,15 +198,14 @@ func buildAnalysisPrompt(teachingPlanContent, heartRateContent, analysisType, st | 阶段 | 阶段 | 项目名称 | 引导语及教学方法 | 队形/站位/留意点 | 目标心率区间 | 时间(分) | | ---------- | -------- | ----------------------------- | ------------------------ | --------------------- | ------------ | ---------- | -| **准备部分** | 热身 | | | | | 3 | -| | 注意力游戏 | | | | | 3 | -| **正课部分** | 基本素质练习及常规意识培养环节 | | | | | 5 | -| | 复习环节 | | | | | 5 | -| | 新授环节 | | | | | 8 | -| **结束部分** | 社会性及情感目标游戏 | | | | | 4 | -| | 整理放松 | | | | | 2 | +| **准备部分** | 热身与状态评估 | | | | | | +| **正课部分** | 技能学习 | | | | | | +| | 体能练习 | | | | | | +| | 综合应用 | | | | | | +| **结束部分** | 整理放松 | | | | | | +| | 课堂总结 | | | | | | -请以专业体育教师的视角,提供详细的数据分析和教学建议。请直接输出报告内容,不要包含"好的"、"收到"、"作为一名..."等任何开场白或客套话。`, teachingPlanContent, heartRateContent, stepContent) +请以专业体育教师的视角,提供详细的数据分析和教学建议。请直接输出报告内容,不要包含"好的"、"收到"、"作为一名..."等任何开场白或客套话。`, teachingPlanContent, heartRateContent, stepContent, audienceLabel) } return fmt.Sprintf(`请根据以下体育课堂的教案和心率监测数据,生成一份详细的课堂分析报告: @@ -180,15 +216,15 @@ func buildAnalysisPrompt(teachingPlanContent, heartRateContent, analysisType, st ## 心率监测数据: %s -这是一份幼儿园体育课的教案和课程心率监测数据,请帮对照分析课程教学效果,运动量和运动负荷情况是否科学,并提出课程设计的优化方案。 +这是一份面向%s人群的体育课教案和课程心率监测数据。请结合该人群的年龄、生理特点和运动安全要求,对照分析课程教学效果、运动量和运动负荷情况是否科学,并提出课程设计的优化方案。 优化方案参考如下格式,教学过程需要详细一些: -# 幼儿体育教案(华侨大学版本) +# 体育教案优化方案 | 项目 | 内容 | | ------------ | -------------------------------- | | **课程名** | | -| **年段** | 小 中 大 | +| **适用人群/年级** | | | **教师姓名** | | | **时间** | 年 月 日 | | **地点** | | @@ -209,15 +245,14 @@ func buildAnalysisPrompt(teachingPlanContent, heartRateContent, analysisType, st | 阶段 | 阶段 | 项目名称 | 引导语及教学方法 | 队形/站位/留意点 | 目标心率区间 | 时间(分) | | ---------- | -------- | ----------------------------- | ------------------------ | --------------------- | ------------ | ---------- | -| **准备部分** | 热身 | | | | | 3 | -| | 注意力游戏 | | | | | 3 | -| **正课部分** | 基本素质练习及常规意识培养环节 | | | | | 5 | -| | 复习环节 | | | | | 5 | -| | 新授环节 | | | | | 8 | -| **结束部分** | 社会性及情感目标游戏 | | | | | 4 | -| | 整理放松 | | | | | 2 | +| **准备部分** | 热身与状态评估 | | | | | | +| **正课部分** | 技能学习 | | | | | | +| | 体能练习 | | | | | | +| | 综合应用 | | | | | | +| **结束部分** | 整理放松 | | | | | | +| | 课堂总结 | | | | | | -请以专业体育教师的视角,提供详细的数据分析和教学建议。请直接输出报告内容,不要包含"好的"、"收到"、"作为一名..."等任何开场白或客套话。`, teachingPlanContent, heartRateContent) +请以专业体育教师的视角,提供详细的数据分析和教学建议。请直接输出报告内容,不要包含"好的"、"收到"、"作为一名..."等任何开场白或客套话。`, teachingPlanContent, heartRateContent, audienceLabel) } type aiAnalysisResult struct { @@ -292,6 +327,8 @@ func callAIForAnalysis(prompt string) (*aiAnalysisResult, error) { // @Param step_data formData file false "步数数据CSV文件(analysis_type为heart_rate_with_steps时必填)" // @Param teaching_plan formData file false "教案DOCX文件(teaching_plan_source为upload/wechat时必填)" // @Param analysis_type formData string false "分析类型: heart_rate_only(默认) | heart_rate_with_steps" +// @Param audience_type formData string false "适用人群: kindergarten(默认) | primary_school | middle_school | university | elderly" +// @Param audience_detail formData string false "精确班级或年级,例如: 小班、中班、大班、小学一年级" // @Param teaching_plan_source formData string false "教案来源: upload(默认) | cloud | wechat" // @Param regionid formData string false "区域ID" // @Param trainid formData string false "训练ID" @@ -311,6 +348,8 @@ func (tc *TrainingController) AnalyzeByAI(c *gin.Context) { csvFiles := form.File["heart_rate_data"] stepFiles := form.File["step_data"] analysisType := c.PostForm("analysis_type") + audienceType := c.PostForm("audience_type") + audienceDetail := c.PostForm("audience_detail") teachingPlanSource := c.PostForm("teaching_plan_source") regionIDStr := c.PostForm("regionid") trainID := c.PostForm("trainid") @@ -322,6 +361,11 @@ func (tc *TrainingController) AnalyzeByAI(c *gin.Context) { if teachingPlanSource == "" { teachingPlanSource = sourceUpload } + audienceLabel, ok := resolveAudienceDescription(audienceType, audienceDetail) + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid audience_type, expected kindergarten, primary_school, middle_school, university or elderly"}) + return + } if len(csvFiles) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "Missing required file: heart_rate_data (.csv)"}) @@ -369,7 +413,7 @@ func (tc *TrainingController) AnalyzeByAI(c *gin.Context) { originalFileSize := heartRateFileHeader.Size + teachingPlanSize + stepFileSize compressedContentSize := int64(len(heartRateContent)) + int64(len(teachingPlanContent)) + int64(len(stepContent)) - prompt := buildAnalysisPrompt(teachingPlanContent, heartRateContent, analysisType, stepContent) + prompt := buildAnalysisPrompt(teachingPlanContent, heartRateContent, analysisType, stepContent, audienceLabel) startTime := time.Now()