Răsfoiți Sursa

Merge remote-tracking branch 'origin/eta/2.6.4' into debug

# Conflicts:
#	utils/constants.go
Roc 19 ore în urmă
părinte
comite
bfa00f80cc

+ 367 - 0
controllers/ai_predict_model/index.go

@@ -0,0 +1,367 @@
+package ai_predict_model
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_index_lib/controllers"
+	aiPredictModelLogic "eta/eta_index_lib/logic/ai_predict_model"
+	"eta/eta_index_lib/models"
+	"eta/eta_index_lib/models/ai_predict_model"
+	"eta/eta_index_lib/models/ai_predict_model/request"
+	"eta/eta_index_lib/models/ai_predict_model/response"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"github.com/go-redis/redis/v8"
+	"strconv"
+)
+
+// AiPredictModelIndexController AI预测模型标的
+type AiPredictModelIndexController struct {
+	controllers.BaseAuthController
+}
+
+type IndexTaskRecordOp struct {
+	IndexTaskRecordId int
+	TaskType          string
+}
+
+// OpToDo
+// @Title 获取待操作的标的
+// @Description 获取待操作的标的
+// @Success 200 {object} response.AiPredictModelIndexConfigResp
+// @router /op_todo [post]
+func (this *AiPredictModelIndexController) OpToDo() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	resp := response.AiPredictModelIndexConfigResp{}
+
+	val, err := utils.Rc.BrpopVal(utils.CACHE_INDEX_TASK)
+	if err != nil {
+		if errors.Is(err, redis.Nil) {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "获取成功"
+			return
+		}
+
+		br.Msg = "获取失败"
+		br.ErrMsg = `从redis中获取数据失败,Err:` + err.Error()
+
+		return
+	}
+
+	indexTaskRecordOp := new(IndexTaskRecordOp)
+	if err = json.Unmarshal([]byte(val), &indexTaskRecordOp); err != nil {
+		fmt.Println("json unmarshal wrong!")
+		return
+	}
+
+	indexTaskRecordObj := new(models.IndexTaskRecord)
+	indexTaskRecordInfo, err := indexTaskRecordObj.GetByID(indexTaskRecordOp.IndexTaskRecordId)
+	if err != nil {
+		fmt.Println("get index task record info wrong!")
+		br.Msg = "获取失败"
+		return
+	}
+	if indexTaskRecordInfo.Status != `待处理` {
+		fmt.Println("任务状态不是待处理!")
+		br.Msg = "任务状态不是待处理"
+		return
+	}
+
+	var indexConfigItem *ai_predict_model.AiPredictModelIndexConfig
+	var indexItem *ai_predict_model.AiPredictModelIndex
+
+	resp.IndexTaskRecordId = indexTaskRecordInfo.IndexTaskRecordID
+
+	defer func() {
+		// 获取完成任务后,需要更新任务状态
+		if resp.AiPredictModelIndexId <= 0 {
+			// 如果获取失败了,那么就标记失败
+			go aiPredictModelLogic.HandleTaskRecordFailByTaskRecord(indexTaskRecordOp.TaskType, indexTaskRecordInfo, indexConfigItem, indexItem, br.Msg)
+		} else {
+			// 如果获取成功了,那么就标记进行中
+			go aiPredictModelLogic.HandleTaskRecordProcessingByTaskRecord(indexTaskRecordOp.TaskType, indexTaskRecordInfo, indexConfigItem, indexItem)
+		}
+	}()
+
+	indexConfigObj := new(ai_predict_model.AiPredictModelIndexConfig)
+	indexOb := new(ai_predict_model.AiPredictModelIndex)
+
+	switch indexTaskRecordOp.TaskType {
+	case utils.INDEX_TASK_TYPE_AI_MODEL_TRAIN:
+		// 训练模型
+		indexConfigId, err := strconv.Atoi(indexTaskRecordInfo.Parameters) // 模型配置ID
+		if err != nil {
+			fmt.Println("模型配置ID转换错误!")
+			br.Msg = "模型配置ID转换错误"
+			br.ErrMsg = "模型配置ID转换错误,err:" + err.Error()
+			return
+		}
+
+		// 查找配置
+		indexConfigItem, err = indexConfigObj.GetById(indexConfigId)
+		if err != nil {
+			br.Msg = "获取模型配置失败"
+			br.ErrMsg = "获取模型配置失败,查找配置失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "配置不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+
+		// 查询标的情况
+		indexItem, err = indexOb.GetItemById(indexConfigItem.AiPredictModelIndexId)
+		if err != nil {
+			br.Msg = "训练失败,查找标的失败"
+			br.ErrMsg = fmt.Sprintf("训练失败,查找标的失败, %v", err)
+			if utils.IsErrNoRow(err) {
+				br.Msg = "标的不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+
+	case utils.INDEX_TASK_TYPE_AI_MODEL_RUN:
+		// 运行模型
+
+		// 标的id转换
+		indexId, err := strconv.Atoi(indexTaskRecordInfo.Parameters)
+		if err != nil {
+			fmt.Println("标的ID转换错误!")
+			br.Msg = "标的ID转换错误"
+			br.ErrMsg = "标的ID转换错误,err:" + err.Error()
+			return
+		}
+
+		// 查询标的情况
+		indexItem, err = indexOb.GetItemById(indexId)
+		if err != nil {
+			br.Msg = "训练失败,查找标的失败"
+			br.ErrMsg = fmt.Sprintf("训练失败,查找标的失败, %v", err)
+			if utils.IsErrNoRow(err) {
+				br.Msg = "标的不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+
+		// 查找配置
+		indexConfigItem, err = indexConfigObj.GetById(indexItem.AiPredictModelIndexConfigId)
+		if err != nil {
+			br.Msg = "获取模型配置失败"
+			br.ErrMsg = "获取模型配置失败,查找配置失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "配置不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+	default:
+		br.Msg = "异常的任务类型"
+		br.ErrMsg = "异常的任务类型,Err:" + indexTaskRecordOp.TaskType
+		return
+	}
+
+	if indexItem.ScriptPath == `` {
+		br.Msg = `没有配置脚本路径`
+		br.ErrMsg = `没有配置脚本路径`
+		return
+	}
+
+	var configParams response.ConfigParams
+	if e := json.Unmarshal([]byte(indexConfigItem.Params), &configParams); e != nil {
+		br.Msg = "获取模型配置失败"
+		br.ErrMsg = "获取模型配置失败,解析配置失败,Err:" + e.Error()
+		return
+	}
+
+	resp.AiPredictModelIndexId = indexConfigItem.AiPredictModelIndexId
+	resp.AiPredictModelIndexConfigId = indexConfigItem.AiPredictModelIndexConfigId
+	resp.ConfigParams = configParams
+	resp.ExecType = indexTaskRecordOp.TaskType
+	resp.ScriptPath = indexItem.ScriptPath
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// HandleTaskRecordFailByTaskRecord
+// @Title 任务操作失败
+// @Description 任务操作失败
+// @Success 200 {object} response.HandleTaskRecordFailReq
+// @router /handle/fail [post]
+func (this *AiPredictModelIndexController) HandleTaskRecordFailByTaskRecord() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.HandleTaskRecordFailReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	indexTaskRecordObj := new(models.IndexTaskRecord)
+	indexTaskRecordInfo, err := indexTaskRecordObj.GetByID(req.IndexTaskRecordId)
+	if err != nil {
+		fmt.Println("get index task record info wrong!")
+		br.Msg = "获取失败"
+		return
+	}
+	if indexTaskRecordInfo.Status != `处理中` {
+		fmt.Println("任务状态不是处理中!")
+		br.Msg = "任务状态不是处理中"
+		return
+	}
+
+	indexTaskObj := models.IndexTask{}
+	indexTaskInfo, tmpErr := indexTaskObj.GetByID(indexTaskRecordInfo.IndexTaskID)
+	if tmpErr != nil {
+		err = fmt.Errorf("查找任务失败, err: %s", tmpErr.Error())
+		return
+	}
+
+	var indexConfigItem *ai_predict_model.AiPredictModelIndexConfig
+	var indexItem *ai_predict_model.AiPredictModelIndex
+	indexConfigObj := new(ai_predict_model.AiPredictModelIndexConfig)
+	indexOb := new(ai_predict_model.AiPredictModelIndex)
+
+	switch indexTaskInfo.TaskType {
+	case utils.INDEX_TASK_TYPE_AI_MODEL_TRAIN:
+		// 训练模型
+		indexConfigId, err := strconv.Atoi(indexTaskRecordInfo.Parameters) // 模型配置ID
+		if err != nil {
+			fmt.Println("模型配置ID转换错误!")
+			br.Msg = "模型配置ID转换错误"
+			br.ErrMsg = "模型配置ID转换错误,err:" + err.Error()
+			return
+		}
+
+		// 查找配置
+		indexConfigItem, err = indexConfigObj.GetById(indexConfigId)
+		if err != nil {
+			br.Msg = "获取模型配置失败"
+			br.ErrMsg = "获取模型配置失败,查找配置失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "配置不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+
+		// 查询标的情况
+		indexItem, err = indexOb.GetItemById(indexConfigItem.AiPredictModelIndexId)
+		if err != nil {
+			br.Msg = "训练失败,查找标的失败"
+			br.ErrMsg = fmt.Sprintf("训练失败,查找标的失败, %v", err)
+			if utils.IsErrNoRow(err) {
+				br.Msg = "标的不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+
+	case utils.INDEX_TASK_TYPE_AI_MODEL_RUN:
+		// 运行模型
+
+		// 标的id转换
+		indexId, err := strconv.Atoi(indexTaskRecordInfo.Parameters)
+		if err != nil {
+			fmt.Println("标的ID转换错误!")
+			br.Msg = "标的ID转换错误"
+			br.ErrMsg = "标的ID转换错误,err:" + err.Error()
+			return
+		}
+
+		// 查询标的情况
+		indexItem, err = indexOb.GetItemById(indexId)
+		if err != nil {
+			br.Msg = "训练失败,查找标的失败"
+			br.ErrMsg = fmt.Sprintf("训练失败,查找标的失败, %v", err)
+			if utils.IsErrNoRow(err) {
+				br.Msg = "标的不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+
+		// 查找配置
+		indexConfigItem, err = indexConfigObj.GetById(indexItem.AiPredictModelIndexConfigId)
+		if err != nil {
+			br.Msg = "获取模型配置失败"
+			br.ErrMsg = "获取模型配置失败,查找配置失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "配置不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+	}
+
+	// 标记处理任务失败
+	aiPredictModelLogic.HandleTaskRecordFailByTaskRecord(indexTaskInfo.TaskType, indexTaskRecordInfo, indexConfigItem, indexItem, req.FailMsg)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}
+
+// HandleTaskRecordSuccessByTaskRecord
+// @Title 任务操作成功
+// @Description 任务操作成功
+// @Success 200 {object} response.HandleTaskRecordFailReq
+// @router /handle/success [post]
+func (this *AiPredictModelIndexController) HandleTaskRecordSuccessByTaskRecord() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req request.HandleTaskRecordSuccessReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	indexTaskRecordObj := new(models.IndexTaskRecord)
+	indexTaskRecordInfo, err := indexTaskRecordObj.GetByID(req.IndexTaskRecordId)
+	if err != nil {
+		fmt.Println("get index task record info wrong!")
+		br.Msg = "获取失败"
+		return
+	}
+	if indexTaskRecordInfo.Status != `处理中` {
+		fmt.Println("任务状态不是处理中!")
+		br.Msg = "任务状态不是处理中"
+		return
+	}
+
+	indexTaskObj := models.IndexTask{}
+	indexTaskInfo, tmpErr := indexTaskObj.GetByID(indexTaskRecordInfo.IndexTaskID)
+	if tmpErr != nil {
+		err = fmt.Errorf("查找任务失败, err: %s", tmpErr.Error())
+		return
+	}
+
+	// 标记处理任务成功
+	aiPredictModelLogic.HandleTaskRecordSuccessByTaskRecord(indexTaskInfo.TaskType, indexTaskRecordInfo, req.Data)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "处理成功"
+}

+ 490 - 0
logic/ai_predict_model/index.go

@@ -0,0 +1,490 @@
+package ai_predict_model
+
+import (
+	"encoding/json"
+	"eta/eta_index_lib/models"
+	aiPredictModel "eta/eta_index_lib/models/ai_predict_model"
+	"eta/eta_index_lib/models/ai_predict_model/request"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"strconv"
+	"time"
+)
+
+// HandleTaskRecordFailByTaskRecord
+// @Description: 任务标记失败
+// @author: Roc
+// @datetime 2025-05-09 16:24:48
+// @param taskType string
+// @param indexTaskRecordInfo *models.IndexTaskRecord
+// @param indexConfigItem *ai_predict_model.AiPredictModelIndexConfig
+// @param indexItem *ai_predict_model.AiPredictModelIndex
+// @param errMsg string
+func HandleTaskRecordFailByTaskRecord(taskType string, indexTaskRecordInfo *models.IndexTaskRecord, indexConfigItem *aiPredictModel.AiPredictModelIndexConfig, indexItem *aiPredictModel.AiPredictModelIndex, errMsg string) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprintf(`HandleTaskRecordFailByTaskRecord err:%v`, err))
+		}
+	}()
+
+	// 修改子任务状态
+	indexTaskRecordInfo.Status = `处理失败`
+	indexTaskRecordInfo.Remark = errMsg
+	indexTaskRecordInfo.ModifyTime = time.Now()
+	err = indexTaskRecordInfo.Update([]string{"status", "remark", "modify_time"})
+	if err != nil {
+		fmt.Println("修改子任务状态失败!")
+		return
+	}
+
+	// 处理完成后标记任务状态
+	defer func() {
+		obj := models.IndexTaskRecord{}
+		// 修改任务状态
+		todoCount, tmpErr := obj.GetCountByCondition(fmt.Sprintf(` AND %s = ? AND %s = ? `, models.IndexTaskRecordColumns.IndexTaskID, models.IndexTaskRecordColumns.Status), []interface{}{indexTaskRecordInfo.IndexTaskID, `待处理`})
+		if tmpErr != nil {
+			err = fmt.Errorf("查找剩余任务数量失败, err: %s", tmpErr.Error())
+			return
+		}
+		if todoCount <= 0 {
+			indexTaskObj := models.IndexTask{}
+			indexTaskInfo, tmpErr := indexTaskObj.GetByID(indexTaskRecordInfo.IndexTaskID)
+			if tmpErr != nil {
+				err = fmt.Errorf("查找任务失败, err: %s", tmpErr.Error())
+				return
+			}
+			tmpUpdateCols := []string{`end_time`, "status", "update_time"}
+			indexTaskInfo.EndTime = time.Now()
+			indexTaskInfo.Status = `处理成功`
+			indexTaskInfo.UpdateTime = time.Now()
+
+			if indexTaskInfo.StartTime.IsZero() {
+				indexTaskInfo.StartTime = time.Now()
+				tmpUpdateCols = append(tmpUpdateCols, "start_time")
+			}
+
+			tmpErr = indexTaskInfo.Update(tmpUpdateCols)
+			if tmpErr != nil {
+				utils.FileLog.Error("标记任务状态失败, err: %s", tmpErr.Error())
+			}
+		}
+
+		return
+	}()
+
+	// 修改模型状态
+	switch taskType {
+	case utils.INDEX_TASK_TYPE_AI_MODEL_TRAIN: // 训练模型
+		// 修改模型状态信息
+		if indexItem != nil {
+			indexItem.TrainStatus = aiPredictModel.TrainStatusFailed
+			indexItem.ModifyTime = time.Now()
+			tmpErr := indexItem.Update([]string{"train_status", "modify_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("%d,修改模型训练状态失败, err: %s", indexItem.AiPredictModelIndexId, tmpErr.Error())
+			}
+		}
+
+		// 修改模型配置状态信息
+		if indexConfigItem != nil {
+			indexConfigItem.TrainStatus = aiPredictModel.TrainStatusFailed
+			indexConfigItem.Remark = errMsg
+			indexConfigItem.ModifyTime = time.Now()
+			tmpErr := indexConfigItem.Update([]string{"train_status", `remark`, "modify_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("%d,修改模型训练状态失败, err: %s", indexItem.AiPredictModelIndexId, tmpErr.Error())
+			}
+		}
+
+	case utils.INDEX_TASK_TYPE_AI_MODEL_RUN: // 运行模型
+		if indexItem != nil {
+			indexItem.RunStatus = aiPredictModel.RunStatusFailed
+			indexItem.ModifyTime = time.Now()
+			tmpErr := indexItem.Update([]string{"run_status", "modify_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("%d,修改模型运行状态失败, err: %s", indexItem.AiPredictModelIndexId, tmpErr.Error())
+			}
+		}
+
+	default:
+
+		return
+	}
+
+	return
+}
+
+// HandleTaskRecordProcessingByTaskRecord
+// @Description: 任务标记处理中
+// @author: Roc
+// @datetime 2025-05-09 16:24:38
+// @param taskType string
+// @param indexTaskRecordInfo *models.IndexTaskRecord
+// @param indexConfigItem *ai_predict_model.AiPredictModelIndexConfig
+// @param indexItem *ai_predict_model.AiPredictModelIndex
+func HandleTaskRecordProcessingByTaskRecord(taskType string, indexTaskRecordInfo *models.IndexTaskRecord, indexConfigItem *aiPredictModel.AiPredictModelIndexConfig, indexItem *aiPredictModel.AiPredictModelIndex) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprintf(`HandleTaskRecordFailByTaskRecord err:%v`, err))
+		}
+	}()
+
+	// 修改子任务状态
+	indexTaskRecordInfo.Status = `处理中`
+	indexTaskRecordInfo.ModifyTime = time.Now()
+	err = indexTaskRecordInfo.Update([]string{"status", "modify_time"})
+	if err != nil {
+		fmt.Println("修改子任务状态失败!")
+		return
+	}
+
+	// 处理完成后标记任务状态
+	defer func() {
+		obj := models.IndexTaskRecord{}
+		// 修改任务状态
+		todoCount, tmpErr := obj.GetCountByCondition(fmt.Sprintf(` AND %s = ? AND %s = ? `, models.IndexTaskRecordColumns.IndexTaskID, models.IndexTaskRecordColumns.Status), []interface{}{indexTaskRecordInfo.IndexTaskID, `待处理`})
+		if tmpErr != nil {
+			err = fmt.Errorf("查找剩余任务数量失败, err: %s", tmpErr.Error())
+			return
+		}
+		if todoCount <= 0 {
+			indexTaskObj := models.IndexTask{}
+			indexTaskInfo, tmpErr := indexTaskObj.GetByID(indexTaskRecordInfo.IndexTaskID)
+			if tmpErr != nil {
+				err = fmt.Errorf("查找任务失败, err: %s", tmpErr.Error())
+				return
+			}
+			tmpUpdateCols := []string{`end_time`, "status", "update_time"}
+			indexTaskInfo.EndTime = time.Now()
+			indexTaskInfo.Status = `处理中`
+			indexTaskInfo.UpdateTime = time.Now()
+
+			if indexTaskInfo.StartTime.IsZero() {
+				indexTaskInfo.StartTime = time.Now()
+				tmpUpdateCols = append(tmpUpdateCols, "start_time")
+			}
+
+			tmpErr = indexTaskInfo.Update(tmpUpdateCols)
+			if tmpErr != nil {
+				utils.FileLog.Error("标记任务状态失败, err: %s", tmpErr.Error())
+			}
+		}
+
+		return
+	}()
+
+	// 修改模型状态
+	switch taskType {
+	case utils.INDEX_TASK_TYPE_AI_MODEL_TRAIN: // 训练模型
+		// 修改模型状态信息
+		if indexItem != nil {
+			indexItem.TrainStatus = aiPredictModel.TrainStatusTraining
+			indexItem.ModifyTime = time.Now()
+			tmpErr := indexItem.Update([]string{"train_status", "modify_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("%d,修改模型训练状态失败, err: %s", indexItem.AiPredictModelIndexId, tmpErr.Error())
+			}
+		}
+
+		// 修改模型配置状态信息
+		if indexConfigItem != nil {
+			indexConfigItem.TrainStatus = aiPredictModel.TrainStatusTraining
+			indexConfigItem.ModifyTime = time.Now()
+			tmpErr := indexConfigItem.Update([]string{"train_status", "modify_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("%d,修改模型训练状态失败, err: %s", indexItem.AiPredictModelIndexId, tmpErr.Error())
+			}
+		}
+
+	case utils.INDEX_TASK_TYPE_AI_MODEL_RUN: // 运行模型
+		// 修改模型状态信息
+		if indexItem != nil {
+			indexItem.RunStatus = aiPredictModel.RunStatusRunning
+			indexItem.ModifyTime = time.Now()
+			tmpErr := indexItem.Update([]string{"run_status", "modify_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("%d,修改模型运行状态失败, err: %s", indexItem.AiPredictModelIndexId, tmpErr.Error())
+			}
+		}
+
+	default:
+
+		return
+	}
+
+	return
+}
+
+// HandleTaskRecordSuccessByTaskRecord
+// @Description: 标记处理完成
+// @author: Roc
+// @datetime 2025-05-14 16:00:26
+// @param taskType string
+// @param indexTaskRecordInfo *models.IndexTaskRecord
+// @param aiPredictModelImportData request.AiPredictModelImportData
+func HandleTaskRecordSuccessByTaskRecord(taskType string, indexTaskRecordInfo *models.IndexTaskRecord, aiPredictModelImportData request.AiPredictModelImportData) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprintf(`HandleTaskRecordFailByTaskRecord err:%v`, err))
+		}
+	}()
+
+	// 修改子任务状态
+	indexTaskRecordInfo.Status = `处理成功`
+	//indexTaskRecordInfo.Remark = errMsg
+	indexTaskRecordInfo.ModifyTime = time.Now()
+	err = indexTaskRecordInfo.Update([]string{"status", "modify_time"})
+	if err != nil {
+		fmt.Println("修改子任务状态失败!")
+		return
+	}
+
+	// 处理完成后标记任务状态
+	defer func() {
+		obj := models.IndexTaskRecord{}
+		// 修改任务状态
+		todoCount, tmpErr := obj.GetCountByCondition(fmt.Sprintf(` AND %s = ? AND %s = ? `, models.IndexTaskRecordColumns.IndexTaskID, models.IndexTaskRecordColumns.Status), []interface{}{indexTaskRecordInfo.IndexTaskID, `待处理`})
+		if tmpErr != nil {
+			err = fmt.Errorf("查找剩余任务数量失败, err: %s", tmpErr.Error())
+			return
+		}
+		if todoCount <= 0 {
+			indexTaskObj := models.IndexTask{}
+			indexTaskInfo, tmpErr := indexTaskObj.GetByID(indexTaskRecordInfo.IndexTaskID)
+			if tmpErr != nil {
+				err = fmt.Errorf("查找任务失败, err: %s", tmpErr.Error())
+				return
+			}
+			tmpUpdateCols := []string{`end_time`, "status", "update_time"}
+			indexTaskInfo.EndTime = time.Now()
+			indexTaskInfo.Status = `处理成功`
+			indexTaskInfo.UpdateTime = time.Now()
+
+			if indexTaskInfo.StartTime.IsZero() {
+				indexTaskInfo.StartTime = time.Now()
+				tmpUpdateCols = append(tmpUpdateCols, "start_time")
+			}
+
+			tmpErr = indexTaskInfo.Update(tmpUpdateCols)
+			if tmpErr != nil {
+				utils.FileLog.Error("标记任务状态失败, err: %s", tmpErr.Error())
+			}
+		}
+
+		return
+	}()
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+
+	// 修改模型状态
+	switch taskType {
+	case utils.INDEX_TASK_TYPE_AI_MODEL_TRAIN: // 训练模型
+		// 训练模型
+		indexConfigId, tmpErr := strconv.Atoi(indexTaskRecordInfo.Parameters) // 模型配置ID
+		if tmpErr != nil {
+			err = fmt.Errorf("模型配置ID转换错误, err: %s", tmpErr.Error())
+			return
+		}
+
+		indexConfigObj := new(aiPredictModel.AiPredictModelIndexConfig)
+		// 查找配置
+		indexConfigItem, tmpErr := indexConfigObj.GetById(indexConfigId)
+		if tmpErr != nil {
+			err = fmt.Errorf("获取模型配置失败, err: %s", tmpErr.Error())
+			return
+		}
+
+		// 查询标的情况
+		indexItem, tmpErr := indexOb.GetItemById(indexConfigItem.AiPredictModelIndexId)
+		if err != nil {
+			err = fmt.Errorf("获取标的失败, err: %s", tmpErr.Error())
+			return
+		}
+
+		handleTaskRecordSuccessByTrain(aiPredictModelImportData, indexConfigItem, indexItem)
+
+	case utils.INDEX_TASK_TYPE_AI_MODEL_RUN: // 运行模型
+
+		// 标的id转换
+		indexId, tmpErr := strconv.Atoi(indexTaskRecordInfo.Parameters)
+		if err != nil {
+			err = fmt.Errorf("标的ID转换错误, err: %s", tmpErr.Error())
+			return
+		}
+
+		// 查询标的情况
+		indexItem, tmpErr := indexOb.GetItemById(indexId)
+		if tmpErr != nil {
+			err = fmt.Errorf("训练失败,查找标的失败, err: %s", tmpErr.Error())
+			return
+		}
+
+		tmpErr = handleTaskRecordSuccessByRun(aiPredictModelImportData, indexItem)
+		if tmpErr != nil {
+			utils.FileLog.Error("%d,修改模型运行状态失败, err: %s", indexItem.AiPredictModelIndexId, tmpErr.Error())
+		}
+
+	default:
+
+		return
+	}
+
+	return
+}
+
+// handleTaskRecordSuccessByTrain
+// @Description: 处理模型训练成功后的操作
+// @author: Roc
+// @datetime 2025-05-14 18:25:12
+// @param aiPredictModelImportData request.AiPredictModelImportData
+// @param indexConfigItem *aiPredictModel.AiPredictModelIndexConfig
+// @param indexItem *aiPredictModel.AiPredictModelIndex
+// @return err error
+func handleTaskRecordSuccessByTrain(aiPredictModelImportData request.AiPredictModelImportData, indexConfigItem *aiPredictModel.AiPredictModelIndexConfig, indexItem *aiPredictModel.AiPredictModelIndex) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error(fmt.Sprintf(`handleTaskRecordSuccessByTrain err:%v`, err))
+		}
+	}()
+
+	// 标的状态修改
+	updateIndexCols := []string{"train_status", "modify_time"}
+	indexItem.TrainStatus = aiPredictModel.TrainStatusSuccess
+	indexItem.ModifyTime = time.Now()
+
+	updateIndexConfigCols := []string{"train_status", `remark`, "modify_time", `train_mse`, `train_r2`, `test_mse`, `test_r2`}
+	// 配置状态修改
+	{
+		// 训练参数
+		trainData := aiPredictModelImportData.TrainData
+		indexConfigItem.TrainStatus = aiPredictModel.TrainStatusSuccess
+		indexConfigItem.Remark = `成功`
+		indexConfigItem.TrainMse = fmt.Sprint(trainData.TrainMse)
+		indexConfigItem.TrainR2 = fmt.Sprint(trainData.TrainR2)
+		indexConfigItem.TestMse = fmt.Sprint(trainData.TestMse)
+		indexConfigItem.TestR2 = fmt.Sprint(trainData.TestR2)
+		indexConfigItem.ModifyTime = time.Now()
+	}
+
+	indexConfigOb := new(aiPredictModel.AiPredictModelIndexConfig)
+
+	dataList := make([]*aiPredictModel.AiPredictModelIndexConfigTrainData, 0)
+	for _, tmpData := range aiPredictModelImportData.Data {
+		tmpDate, e := time.ParseInLocation(utils.FormatDate, tmpData.DataTime, time.Local)
+		if e != nil {
+			err = fmt.Errorf("数据日期解析失败, %v", e)
+			return
+		}
+
+		dataList = append(dataList, &aiPredictModel.AiPredictModelIndexConfigTrainData{
+			//AiPredictModelDataId:  0,
+			AiPredictModelIndexConfigId: indexConfigItem.AiPredictModelIndexConfigId,
+			AiPredictModelIndexId:       indexItem.AiPredictModelIndexId,
+			IndexCode:                   indexItem.IndexCode,
+			DataTime:                    tmpDate,
+			Value:                       tmpData.Value,
+			PredictValue:                tmpData.PredictValue,
+			Direction:                   tmpData.Direction,
+			DeviationRate:               tmpData.DeviationRate,
+			CreateTime:                  time.Now(),
+			ModifyTime:                  time.Now(),
+			DataTimestamp:               tmpData.DataTimestamp,
+			Source:                      tmpData.Source,
+		})
+	}
+
+	// 更新指标和数据
+	err = indexConfigOb.UpdateIndexAndData(indexItem, indexConfigItem, dataList, updateIndexCols, updateIndexConfigCols)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// handleTaskRecordSuccessByRun
+// @Description: 运行中的数据处理
+// @author: Roc
+// @datetime 2025-05-14 14:28:11
+// @param aiPredictModelImportData request.AiPredictModelImportData
+// @param indexItem *aiPredictModel.AiPredictModelIndex
+// @return err error
+func handleTaskRecordSuccessByRun(aiPredictModelImportData request.AiPredictModelImportData, indexItem *aiPredictModel.AiPredictModelIndex) (err error) {
+	defer func() {
+		defer func() {
+			if err != nil {
+				utils.FileLog.Error(fmt.Sprintf(`handleTaskRecordSuccessByRun err:%v`, err))
+			}
+		}()
+	}()
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+
+	updateCols := []string{indexOb.Cols().RunStatus, indexOb.Cols().PredictValue, indexOb.Cols().DirectionAccuracy, indexOb.Cols().AbsoluteDeviation, indexOb.Cols().ExtraConfig, indexOb.Cols().ModifyTime}
+
+	// 预测日期,理论上是需要改的,可是产品说不需要改,所以暂时不改
+	updateCols = append(updateCols, indexOb.Cols().PredictDate)
+	indexItem.RunStatus = aiPredictModel.RunStatusSuccess
+	indexItem.PredictValue = aiPredictModelImportData.Index.PredictValue
+	indexItem.DirectionAccuracy = aiPredictModelImportData.Index.DirectionAccuracy
+	indexItem.AbsoluteDeviation = aiPredictModelImportData.Index.AbsoluteDeviation
+	indexItem.ModifyTime = time.Now()
+
+	predictDate, e := time.ParseInLocation(utils.FormatDate, aiPredictModelImportData.Index.PredictDate, time.Local)
+	if e != nil {
+		err = fmt.Errorf("预测日期解析失败, %v", e)
+		return
+	}
+	indexItem.PredictDate = predictDate
+
+	// 图例信息
+	if indexItem.ExtraConfig != "" && aiPredictModelImportData.Index.ExtraConfig != "" {
+		var oldConfig, newConfig aiPredictModel.AiPredictModelIndexExtraConfig
+		if e := json.Unmarshal([]byte(indexItem.ExtraConfig), &oldConfig); e != nil {
+			err = fmt.Errorf("标的原配置解析失败, Config: %s, Err: %v", indexItem.ExtraConfig, e)
+			return
+		}
+		if e := json.Unmarshal([]byte(aiPredictModelImportData.Index.ExtraConfig), &newConfig); e != nil {
+			err = fmt.Errorf("标的新配置解析失败, Config: %s, Err: %v", aiPredictModelImportData.Index.ExtraConfig, e)
+			return
+		}
+		oldConfig.DailyChart.PredictLegendName = newConfig.DailyChart.PredictLegendName
+		b, _ := json.Marshal(oldConfig)
+		indexItem.ExtraConfig = string(b)
+	}
+
+	dataList := make([]*aiPredictModel.AiPredictModelData, 0)
+	for _, tmpData := range aiPredictModelImportData.Data {
+		tmpDate, e := time.ParseInLocation(utils.FormatDate, tmpData.DataTime, time.Local)
+		if e != nil {
+			err = fmt.Errorf("数据日期解析失败, %v", e)
+			return
+		}
+
+		dataList = append(dataList, &aiPredictModel.AiPredictModelData{
+			//AiPredictModelDataId:  0,
+			AiPredictModelIndexId: indexItem.AiPredictModelIndexId,
+			IndexCode:             indexItem.IndexCode,
+			DataTime:              tmpDate,
+			Value:                 tmpData.Value,
+			PredictValue:          tmpData.PredictValue,
+			Direction:             tmpData.Direction,
+			DeviationRate:         tmpData.DeviationRate,
+			CreateTime:            time.Now(),
+			ModifyTime:            time.Now(),
+			DataTimestamp:         tmpData.DataTimestamp,
+			Source:                tmpData.Source,
+		})
+	}
+
+	// 更新指标和数据
+	err = indexOb.UpdateIndexAndData(indexItem, dataList, updateCols)
+	if err != nil {
+		return
+	}
+
+	return
+}

+ 209 - 0
models/ai_predict_model/ai_predict_model_data.go

@@ -0,0 +1,209 @@
+package ai_predict_model
+
+import (
+	"database/sql"
+	"eta/eta_index_lib/global"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"strings"
+	"time"
+)
+
+const (
+	ModelDataSourceMonthly = 1 // 月度预测数据
+	ModelDataSourceDaily   = 2 // 日度预测数据
+)
+
+// AiPredictModelData AI预测模型标的数据
+type AiPredictModelData struct {
+	AiPredictModelDataId  int             `orm:"column(ai_predict_model_data_id);pk" gorm:"primaryKey"`
+	AiPredictModelIndexId int             `description:"标的ID"`
+	IndexCode             string          `description:"标的编码"`
+	DataTime              time.Time       `description:"数据日期"`
+	Value                 sql.NullFloat64 `description:"实际值"`
+	PredictValue          sql.NullFloat64 `description:"预测值"`
+	Direction             string          `description:"方向"`
+	DeviationRate         string          `description:"偏差率"`
+	CreateTime            time.Time       `description:"创建时间"`
+	ModifyTime            time.Time       `description:"修改时间"`
+	DataTimestamp         int64           `description:"数据日期时间戳"`
+	Source                int             `description:"来源:1-月度预测(默认);2-日度预测"`
+}
+
+func (m *AiPredictModelData) TableName() string {
+	return "ai_predict_model_data"
+}
+
+type AiPredictModelDataCols struct {
+	PrimaryId             string
+	AiPredictModelIndexId string
+	IndexCode             string
+	DataTime              string
+	Value                 string
+	PredictValue          string
+	Direction             string
+	DeviationRate         string
+	CreateTime            string
+	ModifyTime            string
+	DataTimestamp         string
+	Source                string
+}
+
+func (m *AiPredictModelData) Cols() AiPredictModelDataCols {
+	return AiPredictModelDataCols{
+		PrimaryId:             "ai_predict_model_data_id",
+		AiPredictModelIndexId: "ai_predict_model_index_id",
+		IndexCode:             "index_code",
+		DataTime:              "data_time",
+		Value:                 "value",
+		PredictValue:          "predict_value",
+		Direction:             "direction",
+		DeviationRate:         "deviation_rate",
+		CreateTime:            "create_time",
+		ModifyTime:            "modify_time",
+		DataTimestamp:         "data_timestamp",
+		Source:                "source",
+	}
+}
+
+func (m *AiPredictModelData) Create() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Create(m).Error
+	return
+}
+
+func (m *AiPredictModelData) CreateMulti(items []*AiPredictModelData) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.CreateInBatches(items, utils.MultiAddNum).Error
+	return
+}
+
+func (m *AiPredictModelData) Update(cols []string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Select(cols).Updates(m).Error
+	return
+}
+
+func (m *AiPredictModelData) Remove() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sqlRun := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Exec(sqlRun, m.AiPredictModelDataId).Error
+	return
+}
+
+func (m *AiPredictModelData) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sqlRun := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	err = o.Exec(sqlRun, ids).Error
+	return
+}
+
+func (m *AiPredictModelData) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sqlRun := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	err = o.Exec(sqlRun, pars...).Error
+	return
+}
+
+func (m *AiPredictModelData) GetItemById(id int) (item *AiPredictModelData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sqlRun := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sqlRun, id).First(&item).Error
+	return
+}
+
+func (m *AiPredictModelData) GetItemByModelIndexId(aiPredictModelIndexId int) (items []*AiPredictModelData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sqlRun := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().AiPredictModelIndexId)
+	err = o.Raw(sqlRun, aiPredictModelIndexId).Find(&items).Error
+	return
+}
+
+func (m *AiPredictModelData) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sqlRun := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sqlRun, pars...).First(&item).Error
+	return
+}
+
+func (m *AiPredictModelData) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sqlRun := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sqlRun, pars...).Scan(&count).Error
+	return
+}
+
+func (m *AiPredictModelData) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sqlRun := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	err = o.Raw(sqlRun, pars...).Find(&items).Error
+	return
+}
+
+func (m *AiPredictModelData) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sqlRun := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	pars = append(pars, startSize, pageSize)
+	err = o.Raw(sqlRun, pars...).Find(&items).Error
+	return
+}
+
+// AiPredictModelDataItem AI预测模型标的数据
+type AiPredictModelDataItem struct {
+	DataId        int      `description:"ID"`
+	IndexId       int      `description:"标的ID"`
+	IndexCode     string   `description:"标的编码"`
+	DataTime      string   `description:"数据日期"`
+	Value         *float64 `description:"实际值"`
+	PredictValue  *float64 `description:"预测值"`
+	Direction     string   `description:"方向"`
+	DeviationRate string   `description:"偏差率"`
+	DataTimestamp int64    `description:"数据日期时间戳" gorm:"column:data_timestamp"`
+}
+
+func (m *AiPredictModelData) Format2Item() (item *AiPredictModelDataItem) {
+	item = new(AiPredictModelDataItem)
+	item.DataId = m.AiPredictModelDataId
+	item.IndexId = m.AiPredictModelIndexId
+	item.IndexCode = m.IndexCode
+	item.DataTime = utils.TimeTransferString(utils.FormatDate, m.DataTime)
+	if m.Value.Valid {
+		item.Value = &m.Value.Float64
+	}
+	if m.PredictValue.Valid {
+		item.PredictValue = &m.PredictValue.Float64
+	}
+	item.Direction = m.Direction
+	item.DeviationRate = m.DeviationRate
+	item.DataTimestamp = m.DataTimestamp
+	return
+}

+ 684 - 0
models/ai_predict_model/ai_predict_model_index.go

@@ -0,0 +1,684 @@
+package ai_predict_model
+
+import (
+	"eta/eta_index_lib/global"
+	"eta/eta_index_lib/models"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// 训练状态
+const (
+	RunStatusWaiting = "待运行"
+	RunStatusRunning = "运行中"
+	RunStatusSuccess = "运行成功"
+	RunStatusFailed  = "运行失败"
+)
+
+// AiPredictModelIndex AI预测模型标的
+type AiPredictModelIndex struct {
+	AiPredictModelIndexId       int       `orm:"column(ai_predict_model_index_id);pk" gorm:"primaryKey"`
+	IndexName                   string    `description:"标的名称"`
+	IndexCode                   string    `description:"自生成的指标编码"`
+	ClassifyId                  int       `description:"分类ID"`
+	ModelFramework              string    `description:"模型框架"`
+	PredictDate                 time.Time `description:"预测日期"`
+	PredictValue                float64   `description:"预测值"`
+	PredictFrequency            string    `description:"预测频度"`
+	DirectionAccuracy           string    `description:"方向准确度"`
+	AbsoluteDeviation           string    `description:"绝对偏差"`
+	ExtraConfig                 string    `description:"模型参数"`
+	Sort                        int       `description:"排序"`
+	SysUserId                   int       `description:"创建人ID"`
+	SysUserRealName             string    `description:"创建人姓名"`
+	LeftMin                     string    `description:"图表左侧最小值"`
+	LeftMax                     string    `description:"图表左侧最大值"`
+	CreateTime                  time.Time `description:"创建时间"`
+	ModifyTime                  time.Time `description:"修改时间"`
+	AiPredictModelIndexConfigId int       `gorm:"column:ai_predict_model_index_config_id" description:"标的当前的配置id"`
+	ScriptPath                  string    `gorm:"column:script_path" description:"脚本的路径"`
+	TrainStatus                 string    `gorm:"column:train_status" description:"训练状态,枚举值:待训练,训练中,训练成功,训练失败"`
+	RunStatus                   string    `gorm:"column:run_status" description:"运行状态,枚举值:待运行,运行中,运行成功,运行失败"`
+}
+
+func (m *AiPredictModelIndex) TableName() string {
+	return "ai_predict_model_index"
+}
+
+type AiPredictModelIndexCols struct {
+	PrimaryId                   string
+	IndexName                   string
+	IndexCode                   string
+	ClassifyId                  string
+	ModelFramework              string
+	PredictDate                 string
+	PredictValue                string
+	DirectionAccuracy           string
+	AbsoluteDeviation           string
+	ExtraConfig                 string
+	Sort                        string
+	SysUserId                   string
+	SysUserRealName             string
+	LeftMin                     string
+	LeftMax                     string
+	CreateTime                  string
+	ModifyTime                  string
+	AiPredictModelIndexConfigId string
+	ScriptPath                  string
+	TrainStatus                 string
+	RunStatus                   string
+}
+
+func (m *AiPredictModelIndex) Cols() AiPredictModelIndexCols {
+	return AiPredictModelIndexCols{
+		PrimaryId:                   "ai_predict_model_index_id",
+		IndexName:                   "index_name",
+		IndexCode:                   "index_code",
+		ClassifyId:                  "classify_id",
+		ModelFramework:              "model_framework",
+		PredictDate:                 "predict_date",
+		PredictValue:                "predict_value",
+		DirectionAccuracy:           "direction_accuracy",
+		AbsoluteDeviation:           "absolute_deviation",
+		ExtraConfig:                 "extra_config",
+		Sort:                        "sort",
+		SysUserId:                   "sys_user_id",
+		SysUserRealName:             "sys_user_real_name",
+		LeftMin:                     "left_min",
+		LeftMax:                     "left_max",
+		CreateTime:                  "create_time",
+		ModifyTime:                  "modify_time",
+		AiPredictModelIndexConfigId: "ai_predict_model_index_config_id",
+		ScriptPath:                  "script_path",
+		TrainStatus:                 "train_status",
+		RunStatus:                   "run_status",
+	}
+}
+
+func (m *AiPredictModelIndex) Create() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Create(m).Error
+	return
+}
+
+func (m *AiPredictModelIndex) CreateMulti(items []*AiPredictModelIndex) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.CreateInBatches(items, utils.MultiAddNum).Error
+	return
+}
+
+func (m *AiPredictModelIndex) Update(cols []string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Select(cols).Updates(m).Error
+	return
+}
+
+func (m *AiPredictModelIndex) Remove() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Exec(sql, m.AiPredictModelIndexId).Error
+	return
+}
+
+func (m *AiPredictModelIndex) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	err = o.Exec(sql, ids).Error
+	return
+}
+
+func (m *AiPredictModelIndex) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	err = o.Exec(sql, pars...).Error
+	return
+}
+
+func (m *AiPredictModelIndex) GetItemById(id int) (item *AiPredictModelIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).First(&item).Error
+	return
+}
+
+// GetItemByConfigId
+// @Description: 根据配置id获取标的信息
+// @author: Roc
+// @receiver m
+// @datetime 2025-05-06 13:31:24
+// @param configId int
+// @return item *AiPredictModelIndex
+// @return err error
+func (m *AiPredictModelIndex) GetItemByConfigId(configId int) (item *AiPredictModelIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().AiPredictModelIndexConfigId)
+	err = o.Raw(sql, configId).First(&item).Error
+	return
+}
+
+func (m *AiPredictModelIndex) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars...).First(&item).Error
+	return
+}
+
+func (m *AiPredictModelIndex) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars...).Scan(&count).Error
+	return
+}
+
+func (m *AiPredictModelIndex) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	err = o.Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+func (m *AiPredictModelIndex) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	pars = append(pars, startSize, pageSize)
+	err = o.Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+// AiPredictModelIndexItem AI预测模型标的信息
+type AiPredictModelIndexItem struct {
+	IndexId                     int     `description:"标的ID"`
+	IndexName                   string  `description:"标的名称"`
+	IndexCode                   string  `description:"自生成的指标编码"`
+	ClassifyId                  int     `description:"分类ID"`
+	ClassifyName                string  `description:"分类名称"`
+	ModelFramework              string  `description:"模型框架"`
+	PredictDate                 string  `description:"预测日期"`
+	PredictValue                float64 `description:"预测值"`
+	PredictFrequency            string  `description:"预测频度"`
+	DirectionAccuracy           string  `description:"方向准确度"`
+	AbsoluteDeviation           string  `description:"绝对偏差"`
+	ExtraConfig                 string  `description:"模型参数"`
+	SysUserId                   int     `description:"创建人ID"`
+	SysUserRealName             string  `description:"创建人姓名"`
+	CreateTime                  string  `description:"创建时间"`
+	ModifyTime                  string  `description:"修改时间"`
+	SearchText                  string  `description:"搜索结果(含高亮)"`
+	AiPredictModelIndexConfigId int     `gorm:"column:ai_predict_model_index_config_id" description:"标的当前的配置id"`
+	ScriptPath                  string  `gorm:"column:script_path" description:"脚本的路径"`
+	TrainStatus                 string  `gorm:"column:train_status" description:"训练状态,枚举值:待训练,训练中,训练成功,训练失败"`
+	RunStatus                   string  `gorm:"column:run_status" description:"运行状态,枚举值:待运行,运行中,运行成功,运行失败"`
+}
+
+func (m *AiPredictModelIndex) Format2Item() (item *AiPredictModelIndexItem) {
+	item = new(AiPredictModelIndexItem)
+	item.IndexId = m.AiPredictModelIndexId
+	item.IndexName = m.IndexName
+	item.IndexCode = m.IndexCode
+	item.ClassifyId = m.ClassifyId
+	item.ModelFramework = m.ModelFramework
+	item.PredictDate = utils.TimeTransferString(utils.FormatDate, m.PredictDate)
+	item.PredictValue = m.PredictValue
+	item.PredictFrequency = m.PredictFrequency
+	item.DirectionAccuracy = m.DirectionAccuracy
+	item.AbsoluteDeviation = m.AbsoluteDeviation
+	item.ExtraConfig = m.ExtraConfig
+	item.SysUserId = m.SysUserId
+	item.SysUserRealName = m.SysUserRealName
+	item.AiPredictModelIndexConfigId = m.AiPredictModelIndexConfigId
+	item.ScriptPath = m.ScriptPath
+	item.TrainStatus = m.TrainStatus
+	item.RunStatus = m.RunStatus
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}
+
+type AiPredictModelIndexPageListResp struct {
+	Paging *paging.PagingItem
+	List   []*AiPredictModelIndexItem `description:"列表"`
+}
+
+// RemoveIndexAndData 删除标的及数据
+func (m *AiPredictModelIndex) RemoveIndexAndData(indexId int, chartIds []int) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	tx := o.Begin()
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	sql := `DELETE FROM ai_predict_model_index WHERE ai_predict_model_index_id = ? LIMIT 1`
+	e := tx.Exec(sql, indexId).Error
+	if e != nil {
+		err = fmt.Errorf("remove index err: %v", e)
+		return
+	}
+	sql = ` DELETE FROM ai_predict_model_data WHERE ai_predict_model_index_id = ?`
+	e = tx.Exec(sql, indexId).Error
+	if e != nil {
+		err = fmt.Errorf("remove index data err: %v", e)
+		return
+	}
+
+	// 删除图表
+	if len(chartIds) == 0 {
+		return
+	}
+	sql = ` DELETE FROM chart_info WHERE chart_info_id IN ?`
+	if e = tx.Exec(sql, chartIds).Error; e != nil {
+		err = fmt.Errorf("remove charts err: %v", e)
+		return
+	}
+	sql = ` DELETE FROM chart_edb_mapping WHERE chart_info_id IN ?`
+	if e = tx.Exec(sql, chartIds).Error; e != nil {
+		err = fmt.Errorf("remove chart mappings err: %v", e)
+		return
+	}
+	return
+}
+
+// UpdateAiPredictModelIndexSortByClassifyId 根据分类id更新排序
+func UpdateAiPredictModelIndexSortByClassifyId(classifyId, nowSort int, prevEdbInfoId int, updateSort string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := ` UPDATE ai_predict_model_index SET sort = ` + updateSort + ` WHERE classify_id = ?`
+	if prevEdbInfoId > 0 {
+		sql += ` AND ( sort > ? or ( ai_predict_model_index_id > ` + fmt.Sprint(prevEdbInfoId) + ` and sort=` + fmt.Sprint(nowSort) + ` )) `
+	} else {
+		sql += ` AND ( sort > ? )`
+	}
+	err = o.Exec(sql, classifyId, nowSort).Error
+	return
+}
+
+// GetFirstAiPredictModelIndexByClassifyId 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstAiPredictModelIndexByClassifyId(classifyId int) (item *AiPredictModelIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := ` SELECT * FROM ai_predict_model_index WHERE classify_id = ? order by sort asc,ai_predict_model_index_id asc limit 1`
+	err = o.Raw(sql, classifyId).First(&item).Error
+	return
+}
+
+type AiPredictModelImportData struct {
+	Index  *AiPredictModelIndex
+	Data   []*AiPredictModelData
+	Charts []*AiPredictModelImportCharts
+}
+
+type AiPredictModelImportCharts struct {
+	ChartInfo   *models.ChartInfo
+	EdbMappings []*models.ChartEdbMapping
+}
+
+// ImportIndexAndData 导入数据
+func (m *AiPredictModelIndex) ImportIndexAndData(createIndexes, updateIndexes []*AiPredictModelImportData, updateCols []string) (chartIds []int, err error) {
+	if len(createIndexes) == 0 && len(updateIndexes) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	tx := o.Begin()
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	if len(updateIndexes) > 0 {
+		for _, v := range updateIndexes {
+			// 更新指标
+			e := tx.Select(updateCols).Updates(v.Index).Error
+			if e != nil {
+				err = fmt.Errorf("update index err: %v", e)
+				return
+			}
+			var hasDaily, hasMonthly bool
+			for _, d := range v.Data {
+				d.AiPredictModelIndexId = v.Index.AiPredictModelIndexId
+				d.IndexCode = v.Index.IndexCode
+				d.DataTimestamp = d.DataTime.UnixNano() / 1e6
+				if d.Source == ModelDataSourceDaily {
+					hasDaily = true
+				}
+				if d.Source == ModelDataSourceMonthly {
+					hasMonthly = true
+				}
+			}
+			// 哪个有数据就先清空然后重新写入,没数据就保留旧数据, 都没就忽略
+			if !hasDaily && !hasMonthly {
+				continue
+			}
+			removeCond := ``
+			removePars := make([]interface{}, 0)
+			removePars = append(removePars, v.Index.AiPredictModelIndexId)
+			if hasDaily && !hasMonthly {
+				removeCond += ` AND source = ?`
+				removePars = append(removePars, ModelDataSourceDaily)
+			}
+			if !hasDaily && hasMonthly {
+				removeCond += ` AND source = ?`
+				removePars = append(removePars, ModelDataSourceMonthly)
+			}
+
+			// 清空指标并新增
+			sql := fmt.Sprintf(`DELETE FROM ai_predict_model_data WHERE ai_predict_model_index_id = ? %s`, removeCond)
+			e = tx.Exec(sql, removePars...).Error
+			if e != nil {
+				err = fmt.Errorf("clear index data err: %v", e)
+				return
+			}
+			e = tx.CreateInBatches(v.Data, utils.MultiAddNum).Error
+			if e != nil {
+				err = fmt.Errorf("insert index data err: %v", e)
+				return
+			}
+		}
+	}
+
+	if len(createIndexes) > 0 {
+		for _, v := range createIndexes {
+			if e := tx.Create(v.Index).Error; e != nil {
+				err = fmt.Errorf("insert index err: %v", e)
+				return
+			}
+
+			indexId := v.Index.AiPredictModelIndexId
+			for _, d := range v.Data {
+				d.AiPredictModelIndexId = indexId
+				d.IndexCode = v.Index.IndexCode
+				d.DataTimestamp = d.DataTime.UnixNano() / 1e6
+			}
+			if e := tx.CreateInBatches(v.Data, utils.MultiAddNum).Error; e != nil {
+				err = fmt.Errorf("insert index data err: %v", e)
+				return
+			}
+
+			// 图表
+			if len(v.Charts) == 0 {
+				continue
+			}
+			for _, ct := range v.Charts {
+				if e := tx.Create(ct.ChartInfo).Error; e != nil {
+					err = fmt.Errorf("insert chart err: %v", e)
+					return
+				}
+				for _, cm := range ct.EdbMappings {
+					cm.ChartInfoId = ct.ChartInfo.ChartInfoId
+					cm.EdbInfoId = indexId
+					time.Sleep(time.Microsecond)
+					cm.UniqueCode = utils.MD5(fmt.Sprint(utils.CHART_PREFIX, "_", indexId, "_", strconv.FormatInt(time.Now().UnixNano(), 10)))
+				}
+				if e := tx.CreateInBatches(ct.EdbMappings, utils.MultiAddNum).Error; e != nil {
+					err = fmt.Errorf("insert chart mapping err: %v", e)
+					return
+				}
+				chartIds = append(chartIds, ct.ChartInfo.ChartInfoId)
+			}
+		}
+	}
+	return
+}
+
+type AiPredictModelIndexSaveReq struct {
+	IndexId      int                           `description:"指标ID"`
+	MonthlyChart *AiPredictModelIndexSaveChart `description:"月度图表信息"`
+	DailyChart   *AiPredictModelIndexSaveChart `description:"日度图表信息"`
+}
+
+type AiPredictModelIndexSaveChart struct {
+	LeftMin string `description:"图表左侧最小值"`
+	LeftMax string `description:"图表左侧最大值"`
+	Unit    string `description:"单位"`
+}
+
+type AiPredictModelIndexExtraConfig struct {
+	MonthlyChart MonthlyChartConfig
+	DailyChart   DailyChartConfig
+}
+type MonthlyChartConfig struct {
+	LeftMin string `description:"图表左侧最小值"`
+	LeftMax string `description:"图表左侧最大值"`
+	Unit    string `description:"单位"`
+}
+
+type DailyChartConfig struct {
+	LeftMin           string `description:"图表左侧最小值"`
+	LeftMax           string `description:"图表左侧最大值"`
+	Unit              string `description:"单位"`
+	PredictLegendName string `description:"预测图例的名称(通常为Predicted)"`
+}
+
+func (m *AiPredictModelIndex) GetSortMax() (sort int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := `SELECT COALESCE(MAX(sort), 0) AS sort FROM ai_predict_model_index`
+	err = o.Raw(sql).Scan(&sort).Error
+	if err != nil {
+		return
+	}
+	// 查询分类的最大排序
+	sql = `SELECT COALESCE(MAX(sort), 0) AS sort FROM ai_predict_model_classify`
+	var classifySort int
+	err = o.Raw(sql).Scan(&classifySort).Error
+	if err != nil {
+		return
+	}
+	if classifySort > sort {
+		sort = classifySort
+	}
+	return
+}
+
+// UpdateRunStatusByIdList
+// @Description: 通过标的ID列表更新运行状态
+// @author: Roc
+// @receiver m
+// @datetime 2025-05-08 14:44:15
+// @param indexIdList []int
+// @return err error
+func (m *AiPredictModelIndex) UpdateRunStatusByIdList(indexIdList []int) (err error) {
+	if len(indexIdList) <= 0 {
+		return
+	}
+	sql := ` UPDATE ai_predict_model_index SET run_status = ? WHERE ai_predict_model_index_id in (?)`
+	err = global.DbMap[utils.DbNameIndex].Exec(sql, RunStatusWaiting, indexIdList).Error
+
+	return
+}
+
+// UpdateIndexAndData 导入数据
+func (m *AiPredictModelIndex) UpdateIndexAndData(modelIndexItem *AiPredictModelIndex, dataList []*AiPredictModelData, updateCols []string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	tx := o.Begin()
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	// 更新指标
+	e := tx.Select(updateCols).Updates(modelIndexItem).Error
+	if e != nil {
+		err = fmt.Errorf("update index err: %v", e)
+		return
+	}
+
+	var existDataList []*AiPredictModelData
+	// 查询标的的所有数据
+	sqlRun := `SELECT * FROM ai_predict_model_data WHERE ai_predict_model_index_id = ? ORDER BY ai_predict_model_data_id DESC`
+	err = tx.Raw(sqlRun, modelIndexItem.AiPredictModelIndexId).Find(&existDataList).Error
+	if err != nil {
+		err = fmt.Errorf("find index data err: %v", e)
+		return
+	}
+	existDailyMap := make(map[string]*AiPredictModelData)
+	existMonthlyMap := make(map[string]*AiPredictModelData)
+	removeDailyDateMap := make(map[string]bool)
+	removeMonthlyDateMap := make(map[string]bool)
+
+	for _, d := range existDataList {
+		tmpDate := d.DataTime.Format(utils.FormatDate)
+		if d.Source == ModelDataSourceDaily {
+			existDailyMap[tmpDate] = d
+			removeDailyDateMap[tmpDate] = true
+		}
+		if d.Source == ModelDataSourceMonthly {
+			existMonthlyMap[tmpDate] = d
+			removeMonthlyDateMap[tmpDate] = true
+		}
+	}
+
+	addDataList := make([]*AiPredictModelData, 0)
+	for _, tmpData := range dataList {
+		tmpData.AiPredictModelIndexId = modelIndexItem.AiPredictModelIndexId
+		tmpData.IndexCode = modelIndexItem.IndexCode
+		tmpData.DataTimestamp = tmpData.DataTime.UnixNano() / 1e6
+
+		// 档期日期
+		tmpDate := tmpData.DataTime.Format(utils.FormatDate)
+
+		if tmpData.Source == ModelDataSourceDaily {
+			delete(removeDailyDateMap, tmpDate)
+			if existData, ok := existDailyMap[tmpDate]; ok {
+				// 修改
+				dataUpdateCols := make([]string, 0)
+				if existData.Value != tmpData.Value {
+					existData.Value = tmpData.Value
+					dataUpdateCols = append(dataUpdateCols, "Value")
+				}
+				if existData.PredictValue != tmpData.PredictValue {
+					existData.PredictValue = tmpData.PredictValue
+					dataUpdateCols = append(dataUpdateCols, "PredictValue")
+				}
+				if existData.Direction != tmpData.Direction {
+					existData.Direction = tmpData.Direction
+					dataUpdateCols = append(dataUpdateCols, "Direction")
+				}
+				if existData.DeviationRate != tmpData.DeviationRate {
+					existData.DeviationRate = tmpData.DeviationRate
+					dataUpdateCols = append(dataUpdateCols, "DeviationRate")
+				}
+
+				if len(dataUpdateCols) > 0 {
+					existData.ModifyTime = time.Now()
+					dataUpdateCols = append(dataUpdateCols, "ModifyTime")
+					tmpErr := tx.Select(dataUpdateCols).Updates(existData).Error
+					if tmpErr != nil {
+						utils.FileLog.Error("update index data err: %v", tmpErr)
+					}
+				}
+
+			} else {
+				addDataList = append(addDataList, tmpData)
+			}
+		}
+
+		if tmpData.Source == ModelDataSourceMonthly {
+			delete(removeMonthlyDateMap, tmpDate)
+			if existData, ok := existMonthlyMap[tmpDate]; ok {
+				// 修改
+				dataUpdateCols := make([]string, 0)
+				if existData.Value != tmpData.Value {
+					existData.Value = tmpData.Value
+					dataUpdateCols = append(dataUpdateCols, "Value")
+				}
+				if existData.PredictValue.Float64 != tmpData.PredictValue.Float64 {
+					existData.PredictValue = tmpData.PredictValue
+					dataUpdateCols = append(dataUpdateCols, "PredictValue")
+				}
+				if existData.Direction != tmpData.Direction {
+					existData.Direction = tmpData.Direction
+					dataUpdateCols = append(dataUpdateCols, "Direction")
+				}
+				if existData.DeviationRate != tmpData.DeviationRate {
+					existData.DeviationRate = tmpData.DeviationRate
+					dataUpdateCols = append(dataUpdateCols, "DeviationRate")
+				}
+
+				if len(dataUpdateCols) > 0 {
+					existData.ModifyTime = time.Now()
+					dataUpdateCols = append(dataUpdateCols, "ModifyTime")
+					tmpErr := tx.Select(dataUpdateCols).Updates(existData).Error
+					if tmpErr != nil {
+						utils.FileLog.Error("update index data err: %v", tmpErr)
+					}
+				}
+			} else {
+				addDataList = append(addDataList, tmpData)
+			}
+		}
+	}
+
+	// 清除不要了的日度指标
+	if len(removeDailyDateMap) > 0 {
+		removeDateList := make([]string, 0)
+		for date := range removeDailyDateMap {
+			removeDateList = append(removeDateList, date)
+		}
+		sql := `DELETE FROM ai_predict_model_data WHERE ai_predict_model_index_id = ? AND source = ? AND data_time IN (?)`
+		e = tx.Exec(sql, modelIndexItem.AiPredictModelIndexId, ModelDataSourceDaily, removeDateList).Error
+		if e != nil {
+			err = fmt.Errorf("clear index daily data err: %v", e)
+			return
+		}
+	}
+
+	// 清除不要了的月度指标
+	if len(removeMonthlyDateMap) > 0 {
+		removeDateList := make([]string, 0)
+		for date := range removeMonthlyDateMap {
+			removeDateList = append(removeDateList, date)
+		}
+		sql := `DELETE FROM ai_predict_model_data WHERE ai_predict_model_index_id = ? AND source = ? AND data_time IN (?)`
+		e = tx.Exec(sql, modelIndexItem.AiPredictModelIndexId, ModelDataSourceMonthly, removeDateList).Error
+		if e != nil {
+			err = fmt.Errorf("clear index monthly data err: %v", e)
+			return
+		}
+	}
+	e = tx.CreateInBatches(addDataList, utils.MultiAddNum).Error
+	if e != nil {
+		err = fmt.Errorf("insert index data err: %v", e)
+		return
+	}
+
+	return
+}

+ 215 - 0
models/ai_predict_model/ai_predict_model_index_config.go

@@ -0,0 +1,215 @@
+package ai_predict_model
+
+import (
+	"database/sql"
+	"eta/eta_index_lib/global"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"time"
+)
+
+// 训练状态
+const (
+	TrainStatusWaiting  = "待训练"
+	TrainStatusTraining = "训练中"
+	TrainStatusSuccess  = "训练成功"
+	TrainStatusFailed   = "训练失败"
+)
+
+// AiPredictModelIndexConfig ai预测模型训练配置
+type AiPredictModelIndexConfig struct {
+	AiPredictModelIndexConfigId int       `gorm:"primaryKey;column:ai_predict_model_index_config_id" description:"-"`
+	AiPredictModelIndexId       int       `gorm:"column:ai_predict_model_index_id" description:"ai预测模型id"`
+	TrainStatus                 string    `gorm:"column:train_status" description:"训练状态,枚举值:待训练,训练中,训练成功,训练失败"`
+	Params                      string    `gorm:"column:params" description:"训练参数,json字符串存储,便于以后参数扩展"`
+	TrainMse                    string    `gorm:"column:train_mse" description:"训练集mse"`
+	TrainR2                     string    `gorm:"column:train_r2" description:"训练集r2"`
+	TestMse                     string    `gorm:"column:test_mse" description:"测试集mse"`
+	TestR2                      string    `gorm:"column:test_r2" description:"测试集r2"`
+	Remark                      string    `gorm:"column:remark" description:"训练结果说明"`
+	IsDeleted                   int8      `gorm:"column:is_deleted" description:"是否删除,0:未删除,1:已删除"`
+	LeftMin                     string    `gorm:"column:left_min" description:"图表左侧下限"`
+	LeftMax                     string    `gorm:"column:left_max" description:"图表左侧下限"`
+	ModifyTime                  time.Time `gorm:"column:modify_time" description:"修改时间"`
+	CreateTime                  time.Time `gorm:"column:create_time" description:"创建时间"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *AiPredictModelIndexConfig) TableName() string {
+	return "ai_predict_model_index_config"
+}
+
+// AiPredictModelIndexConfigColumns get sql column name.获取数据库列名
+var AiPredictModelIndexConfigColumns = struct {
+	AiPredictModelIndexConfigId string
+	AiPredictModelIndexId       string
+	TrainStatus                 string
+	Params                      string
+	TrainMse                    string
+	TrainR2                     string
+	TestMse                     string
+	TestR2                      string
+	Remark                      string
+	IsDeleted                   string
+	ModifyTime                  string
+	CreateTime                  string
+	LeftMin                     string
+	LeftMax                     string
+}{
+	AiPredictModelIndexConfigId: "ai_predict_model_index_config_id",
+	AiPredictModelIndexId:       "ai_predict_model_index_id",
+	TrainStatus:                 "train_status",
+	Params:                      "params",
+	TrainMse:                    "train_mse",
+	TrainR2:                     "train_r2",
+	TestMse:                     "test_mse",
+	TestR2:                      "test_r2",
+	Remark:                      "remark",
+	LeftMin:                     "left_min",
+	LeftMax:                     "left_max",
+	IsDeleted:                   "is_deleted",
+	ModifyTime:                  "modify_time",
+	CreateTime:                  "create_time",
+}
+
+// AiPredictModelIndexConfigView ai预测模型训练配置
+type AiPredictModelIndexConfigView struct {
+	AiPredictModelIndexConfigId int    `gorm:"primaryKey;column:ai_predict_model_index_config_id" description:"-"`
+	AiPredictModelIndexId       int    `gorm:"column:ai_predict_model_index_id" description:"ai预测模型id"`
+	TrainStatus                 string `gorm:"column:train_status" description:"训练状态,枚举值:待训练,训练中,训练成功,训练失败"`
+	Params                      string `gorm:"column:params" description:"训练参数,json字符串存储,便于以后参数扩展"`
+	TrainMse                    string `gorm:"column:train_mse" description:"训练集mse"`
+	TrainR2                     string `gorm:"column:train_r2" description:"训练集r2"`
+	TestMse                     string `gorm:"column:test_mse" description:"测试集mse"`
+	TestR2                      string `gorm:"column:test_r2" description:"测试集r2"`
+	Remark                      string `gorm:"column:remark" description:"训练结果说明"`
+	LeftMin                     string `gorm:"column:left_min" description:"图表左侧下限"`
+	LeftMax                     string `gorm:"column:left_max" description:"图表左侧下限"`
+	IsCurr                      int8   `gorm:"column:is_curr" description:"是否当前版本,0:否,1:是"`
+	ModifyTime                  string `gorm:"column:modify_time" description:"修改时间"`
+	CreateTime                  string `gorm:"column:create_time" description:"创建时间"`
+}
+
+func (m *AiPredictModelIndexConfig) ToView() AiPredictModelIndexConfigView {
+	var modifyTime, createTime string
+
+	if !m.CreateTime.IsZero() {
+		createTime = m.CreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.ModifyTime.IsZero() {
+		modifyTime = m.ModifyTime.Format(utils.FormatDateTime)
+	}
+	return AiPredictModelIndexConfigView{
+		AiPredictModelIndexConfigId: m.AiPredictModelIndexConfigId,
+		AiPredictModelIndexId:       m.AiPredictModelIndexId,
+		TrainStatus:                 m.TrainStatus,
+		Params:                      m.Params,
+		TrainMse:                    m.TrainMse,
+		TrainR2:                     m.TrainR2,
+		TestMse:                     m.TestMse,
+		TestR2:                      m.TestR2,
+		Remark:                      m.Remark,
+		LeftMin:                     m.LeftMin,
+		LeftMax:                     m.LeftMax,
+		ModifyTime:                  modifyTime,
+		CreateTime:                  createTime,
+	}
+}
+
+func (m *AiPredictModelIndexConfig) ListToViewList(list []*AiPredictModelIndexConfig) (AiPredictModelIndexConfigViewList []AiPredictModelIndexConfigView) {
+	AiPredictModelIndexConfigViewList = make([]AiPredictModelIndexConfigView, 0)
+
+	for _, v := range list {
+		AiPredictModelIndexConfigViewList = append(AiPredictModelIndexConfigViewList, v.ToView())
+	}
+	return
+}
+
+func (m *AiPredictModelIndexConfig) Create() (err error) {
+	err = global.DbMap[utils.DbNameIndex].Create(&m).Error
+
+	return
+}
+
+func (m *AiPredictModelIndexConfig) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameIndex].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *AiPredictModelIndexConfig) GetById(id int) (item *AiPredictModelIndexConfig, err error) {
+	err = global.DbMap[utils.DbNameIndex].Where(fmt.Sprintf("%s = ?", AiPredictModelIndexConfigColumns.AiPredictModelIndexConfigId), id).First(&item).Error
+
+	return
+}
+
+func (m *AiPredictModelIndexConfig) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*AiPredictModelIndexConfig, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE is_deleted=0 %s order by ai_predict_model_index_config_id desc LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameIndex].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *AiPredictModelIndexConfig) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE is_deleted=0 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameIndex].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *AiPredictModelIndexConfig) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*AiPredictModelIndexConfig, err error) {
+
+	total, err = m.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByCondition(``, condition, pars, startSize, pageSize)
+	}
+
+	return
+}
+
+// UpdateIndexAndData 导入数据
+func (m *AiPredictModelIndexConfig) UpdateIndexAndData(modelIndexItem *AiPredictModelIndex, modelIndexConfigItem *AiPredictModelIndexConfig, dataList []*AiPredictModelIndexConfigTrainData, updateIndexCols, updateIndexConfigCols []string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	tx := o.Begin()
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	// 更新标的
+	e := tx.Select(updateIndexCols).Updates(modelIndexItem).Error
+	if e != nil {
+		err = fmt.Errorf("update index err: %v", e)
+		return
+	}
+
+	// 更新模型配置
+	e = tx.Select(updateIndexConfigCols).Updates(modelIndexConfigItem).Error
+	if e != nil {
+		err = fmt.Errorf("update index err: %v", e)
+		return
+	}
+
+	// 添加训练数据
+	e = tx.CreateInBatches(dataList, utils.MultiAddNum).Error
+	if e != nil {
+		err = fmt.Errorf("insert index data err: %v", e)
+		return
+	}
+
+	return
+}

+ 132 - 0
models/ai_predict_model/ai_predict_model_index_config_train_data.go

@@ -0,0 +1,132 @@
+package ai_predict_model
+
+import (
+	"database/sql"
+	"eta/eta_index_lib/global"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"strings"
+	"time"
+)
+
+// AiPredictModelIndexConfigTrainData AI预测模型标的训练数据
+type AiPredictModelIndexConfigTrainData struct {
+	AiPredictModelIndexConfigTrainDataId int             `gorm:"primaryKey;column:ai_predict_model_index_config_train_data_id" description:"-"`
+	AiPredictModelIndexConfigId          int             `gorm:"column:ai_predict_model_index_config_id" description:"标的配置Id"`
+	AiPredictModelIndexId                int             `gorm:"column:ai_predict_model_index_id" description:"标的Id"`
+	IndexCode                            string          `gorm:"column:index_code" description:"指标编码"`
+	DataTime                             time.Time       `gorm:"column:data_time" description:"数据日期"`
+	Value                                sql.NullFloat64 `gorm:"column:value" description:"实际值"`
+	PredictValue                         sql.NullFloat64 `gorm:"column:predict_value" description:"预测值"`
+	Direction                            string          `gorm:"column:direction" description:"方向"`
+	DeviationRate                        string          `gorm:"column:deviation_rate" description:"偏差率"`
+	CreateTime                           time.Time       `gorm:"column:create_time" description:"创建时间"`
+	ModifyTime                           time.Time       `gorm:"column:modify_time" description:"修改时间"`
+	DataTimestamp                        int64           `gorm:"column:data_timestamp" description:"数据日期时间戳"`
+	Source                               int             `gorm:"column:source" description:"来源:1-月度预测;2-日度预测"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *AiPredictModelIndexConfigTrainData) TableName() string {
+	return "ai_predict_model_index_config_train_data"
+}
+
+// AiPredictModelIndexConfigTrainDataColumns get sql column name.获取数据库列名
+var AiPredictModelIndexConfigTrainDataColumns = struct {
+	AiPredictModelIndexConfigTrainDataId string
+	AiPredictModelIndexConfigId          string
+	AiPredictModelIndexId                string
+	IndexCode                            string
+	DataTime                             string
+	Value                                string
+	PredictValue                         string
+	Direction                            string
+	DeviationRate                        string
+	CreateTime                           string
+	ModifyTime                           string
+	DataTimestamp                        string
+	Source                               string
+}{
+	AiPredictModelIndexConfigTrainDataId: "ai_predict_model_index_config_train_data_id",
+	AiPredictModelIndexConfigId:          "ai_predict_model_index_config_id",
+	AiPredictModelIndexId:                "ai_predict_model_index_id",
+	IndexCode:                            "index_code",
+	DataTime:                             "data_time",
+	Value:                                "value",
+	PredictValue:                         "predict_value",
+	Direction:                            "direction",
+	DeviationRate:                        "deviation_rate",
+	CreateTime:                           "create_time",
+	ModifyTime:                           "modify_time",
+	DataTimestamp:                        "data_timestamp",
+	Source:                               "source",
+}
+
+func (m *AiPredictModelIndexConfigTrainData) Create() (err error) {
+	err = global.DbMap[utils.DbNameIndex].Create(&m).Error
+
+	return
+}
+
+func (m *AiPredictModelIndexConfigTrainData) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameIndex].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *AiPredictModelIndexConfigTrainData) GetById(id int) (item *AiPredictModelIndexConfigTrainData, err error) {
+	err = global.DbMap[utils.DbNameIndex].Where(fmt.Sprintf("%s = ?", AiPredictModelIndexConfigTrainDataColumns.AiPredictModelIndexConfigTrainDataId), id).First(&item).Error
+
+	return
+}
+
+func (m *AiPredictModelIndexConfigTrainData) GetAllListByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelIndexConfigTrainData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, AiPredictModelIndexConfigTrainDataColumns.AiPredictModelIndexConfigTrainDataId)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sqlRun := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	err = o.Raw(sqlRun, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *AiPredictModelIndexConfigTrainData) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*AiPredictModelIndexConfigTrainData, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by ai_predict_model_index_config_train_data_id desc LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameIndex].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *AiPredictModelIndexConfigTrainData) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameIndex].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *AiPredictModelIndexConfigTrainData) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*AiPredictModelIndexConfigTrainData, err error) {
+
+	total, err = m.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByCondition(``, condition, pars, startSize, pageSize)
+	}
+
+	return
+}

+ 86 - 0
models/ai_predict_model/request/index.go

@@ -0,0 +1,86 @@
+package request
+
+import (
+	"database/sql"
+)
+
+// HandleTaskRecordFailReq
+// @Description: 任务处理失败
+type HandleTaskRecordFailReq struct {
+	IndexTaskRecordId int    `description:"子任务id"`
+	FailMsg           string `description:"失败原因"`
+}
+
+// HandleTaskRecordSuccessReq
+// @Description: 任务处理成功
+type HandleTaskRecordSuccessReq struct {
+	IndexTaskRecordId int                      `gorm:"primaryKey;column:ai_predict_model_index_config_id" description:"子任务记录id"`
+	Data              AiPredictModelImportData `description:"导入的指标"`
+}
+
+// AiPredictModelIndex AI预测模型标的
+type AiPredictModelIndex struct {
+	AiPredictModelIndexId       int     `orm:"column(ai_predict_model_index_id);pk" gorm:"primaryKey"`
+	IndexName                   string  `description:"标的名称"`
+	IndexCode                   string  `description:"自生成的指标编码"`
+	ClassifyId                  int     `description:"分类ID"`
+	ModelFramework              string  `description:"模型框架"`
+	PredictDate                 string  `description:"预测日期"`
+	PredictValue                float64 `description:"预测值"`
+	PredictFrequency            string  `description:"预测频度"`
+	DirectionAccuracy           string  `description:"方向准确度"`
+	AbsoluteDeviation           string  `description:"绝对偏差"`
+	ExtraConfig                 string  `description:"模型参数"`
+	Sort                        int     `description:"排序"`
+	SysUserId                   int     `description:"创建人ID"`
+	SysUserRealName             string  `description:"创建人姓名"`
+	LeftMin                     string  `description:"图表左侧最小值"`
+	LeftMax                     string  `description:"图表左侧最大值"`
+	AiPredictModelIndexConfigId int     `gorm:"column:ai_predict_model_index_config_id" description:"标的当前的配置id"`
+	ScriptPath                  string  `gorm:"column:script_path" description:"脚本的路径"`
+	TrainStatus                 string  `gorm:"column:train_status" description:"训练状态,枚举值:待训练,训练中,训练成功,训练失败"`
+	RunStatus                   string  `gorm:"column:run_status" description:"运行状态,枚举值:待运行,运行中,运行成功,运行失败"`
+}
+
+// AiPredictModelData AI预测模型标的数据
+type AiPredictModelData struct {
+	DataTime      string          `description:"数据日期"`
+	Value         sql.NullFloat64 `description:"实际值"`
+	PredictValue  sql.NullFloat64 `description:"预测值"`
+	Direction     string          `description:"方向"`
+	DeviationRate string          `description:"偏差率"`
+	DataTimestamp int64           `description:"数据日期时间戳"`
+	Source        int             `description:"来源:1-月度预测(默认);2-日度预测"`
+}
+
+type AiPredictModelImportData struct {
+	Index     *AiPredictModelIndex
+	Data      []*AiPredictModelData
+	TrainData TrainData
+}
+
+// TrainData
+// @Description: 训练结果
+type TrainData struct {
+	TrainMse float64
+	TrainR2  float64
+	TestMse  float64
+	TestR2   float64
+}
+
+type AiPredictModelIndexExtraConfig struct {
+	MonthlyChart MonthlyChartConfig
+	DailyChart   DailyChartConfig
+}
+type MonthlyChartConfig struct {
+	LeftMin string `description:"图表左侧最小值"`
+	LeftMax string `description:"图表左侧最大值"`
+	Unit    string `description:"单位"`
+}
+
+type DailyChartConfig struct {
+	LeftMin           string `description:"图表左侧最小值"`
+	LeftMax           string `description:"图表左侧最大值"`
+	Unit              string `description:"单位"`
+	PredictLegendName string `description:"预测图例的名称(通常为Predicted)"`
+}

+ 30 - 0
models/ai_predict_model/response/index.go

@@ -0,0 +1,30 @@
+package response
+
+type AiPredictModelIndexConfigResp struct {
+	IndexTaskRecordId           int          `gorm:"primaryKey;column:ai_predict_model_index_config_id" description:"子任务记录id"`
+	AiPredictModelIndexId       int          `gorm:"column:ai_predict_model_index_id" description:"ai预测模型id"`
+	AiPredictModelIndexConfigId int          `gorm:"primaryKey;column:ai_predict_model_index_config_id" description:"ai预测模型配置ID"`
+	ConfigParams                ConfigParams `gorm:"column:train_status" description:"运行/训练参数"`
+	ExecType                    string       `description:"枚举值:ai_predict_model_train(训练)、ai_predict_model_run(运行)"`
+	ScriptPath                  string       `description:"脚本路径"`
+}
+
+// ConfigParams
+// @Description: 训练参数
+type ConfigParams struct {
+	Seed            float64 `json:"seed" description:"随机种子,如:42"`
+	Objective       string  `json:"objective" description:"目标(回归任务),枚举值,squarederror、multi、sofamax;默认值:squarederror" `
+	EvalMetric      string  `json:"eval_metric" description:"评估指标,枚举值,rmse、auc logloss、merror;默认值:rmse"`
+	MaxDeltaStep    int     `json:"max_delta_step" description:"最大步长,如:5;正整数,必须大于0"`
+	TreeMethod      string  `json:"tree_method" description:"树构建方法,枚举值:auto、exact、approx、hist;默认值:auto"`
+	NumBoostRound   int     `json:"num_boost_round" description:"迭代次数;正整数,必须大于0"`
+	LearningRate    float64 `json:"learning_rate" description:"学习率,如:0.0881"`
+	MaxDepth        int     `json:"max_depth" description:"最大深度(控制树的深度,防止过拟合),如:4;正整数,必须大于0"`
+	MinChildWeight  float64 `json:"min_child_weight" description:"最小子节点权重(防止过拟合),如:6.0601"`
+	Subsample       float64 `json:"subsample" description:"随机采样(防止过拟合),如:0.9627"`
+	ColsampleBytree float64 `json:"colsample_bytree" description:"特征随机采样(防止过拟合),如:0.7046"`
+	Gamma           float64 `json:"gamma" description:"控制分裂,如:0.4100"`
+	RegAlpha        float64 `json:"reg_alpha" description:"L1正则化系数,如:0.3738"`
+	ReqLambda       float64 `json:"reg_lambda" description:"L2正则化系数,如:1.4775"`
+	Booster         string  `json:"booster" description:"使用哪种补充包。可以是,或;使用基于树的模型,同时使用线性函数。枚举值:gbtree、gblinear、dart、gbtree、dart、gblinear"`
+}

+ 155 - 0
models/index_task.go

@@ -0,0 +1,155 @@
+package models
+
+import (
+	"database/sql"
+	"eta/eta_index_lib/global"
+	"eta/eta_index_lib/utils"
+	"fmt"
+	"time"
+)
+
+// IndexTask index这边的任务表
+type IndexTask struct {
+	IndexTaskID     int       `gorm:"primaryKey;column:index_task_id" description:"-"`
+	TaskName        string    `gorm:"column:task_name" description:"任务名称"`
+	TaskType        string    `gorm:"column:task_type" description:"任务类型"`
+	Status          string    `gorm:"column:status" description:"任务状态,枚举值:待处理,处理成功,处理失败,暂停处理"`
+	StartTime       time.Time `gorm:"column:start_time" description:"开始时间"`
+	EndTime         time.Time `gorm:"column:end_time" description:"结束时间"`
+	CreateTime      time.Time `gorm:"column:create_time" description:"创建时间"`
+	UpdateTime      time.Time `gorm:"column:update_time" description:"更新时间"`
+	Logs            string    `gorm:"column:logs" description:"日志"`
+	Errormessage    string    `gorm:"column:ErrorMessage" description:"错误信息"`
+	Priority        int       `gorm:"column:priority" description:"优先级"`
+	RetryCount      int       `gorm:"column:retry_count" description:"重试次数"`
+	Remark          string    `gorm:"column:remark" description:"备注"`
+	SysUserID       int       `gorm:"column:sys_user_id" description:"任务创建人id"`
+	SysUserRealName string    `gorm:"column:sys_user_real_name" description:"任务创建人名称"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *IndexTask) TableName() string {
+	return "index_task"
+}
+
+// IndexTaskColumns get sql column name.获取数据库列名
+var IndexTaskColumns = struct {
+	IndexTaskID     string
+	TaskName        string
+	TaskType        string
+	Status          string
+	StartTime       string
+	EndTime         string
+	CreateTime      string
+	UpdateTime      string
+	Logs            string
+	Errormessage    string
+	Priority        string
+	RetryCount      string
+	Remark          string
+	SysUserID       string
+	SysUserRealName string
+}{
+	IndexTaskID:     "index_task_id",
+	TaskName:        "task_name",
+	TaskType:        "task_type",
+	Status:          "status",
+	StartTime:       "start_time",
+	EndTime:         "end_time",
+	CreateTime:      "create_time",
+	UpdateTime:      "update_time",
+	Logs:            "logs",
+	Errormessage:    "ErrorMessage",
+	Priority:        "priority",
+	RetryCount:      "retry_count",
+	Remark:          "remark",
+	SysUserID:       "sys_user_id",
+	SysUserRealName: "sys_user_real_name",
+}
+
+func (m *IndexTask) Create() (err error) {
+	err = global.DEFAULT_DB.Create(&m).Error
+
+	return
+}
+
+func (m *IndexTask) Update(updateCols []string) (err error) {
+	err = global.DEFAULT_DB.Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *IndexTask) Del() (err error) {
+	err = global.DEFAULT_DB.Delete(&m).Error
+
+	return
+}
+
+func (m *IndexTask) GetByID(id int) (item *IndexTask, err error) {
+	err = global.DEFAULT_DB.Where(fmt.Sprintf("%s = ?", IndexTaskColumns.IndexTaskID), id).First(&item).Error
+
+	return
+}
+
+func (m *IndexTask) GetByCondition(condition string, pars []interface{}) (item *IndexTask, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DEFAULT_DB.Raw(sqlStr, pars...).First(&item).Error
+
+	return
+}
+
+func (m *IndexTask) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*IndexTask, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by index_task_id desc LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DEFAULT_DB.Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *IndexTask) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DEFAULT_DB.Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+// AddIndexTask
+// @Description: 添加Index模块的任务
+// @author: Roc
+// @datetime 2025-04-16 16:55:36
+// @param indexTask *IndexTask
+// @param indexRecordList []*IndexTaskRecord
+// @return err error
+func AddIndexTask(indexTask *IndexTask, indexRecordList []*IndexTaskRecord) (err error) {
+	to := global.DEFAULT_DB.Begin()
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	err = to.Create(indexTask).Error
+	if err != nil {
+		return
+	}
+
+	for _, indexTaskRecord := range indexRecordList {
+		indexTaskRecord.IndexTaskID = indexTask.IndexTaskID
+	}
+
+	err = to.CreateInBatches(indexRecordList, utils.MultiAddNum).Error
+	if err != nil {
+		return
+	}
+
+	return
+}

+ 114 - 0
models/index_task_record.go

@@ -0,0 +1,114 @@
+package models
+
+import (
+	"database/sql"
+	"eta/eta_index_lib/global"
+	"fmt"
+	"time"
+)
+
+// IndexTaskRecord AI任务的子记录
+type IndexTaskRecord struct {
+	IndexTaskRecordID int       `gorm:"primaryKey;column:index_task_record_id" description:"任务记录id"`
+	IndexTaskID       int       `gorm:"column:index_task_id" description:"任务id"`
+	Parameters        string    `gorm:"column:parameters" description:"子任务参数"`
+	Status            string    `gorm:"column:status" description:"状态,枚举值:待处理,处理成功,处理失败,暂停处理"`
+	Remark            string    `gorm:"column:remark" description:"备注"`
+	ModifyTime        time.Time `gorm:"column:modify_time" description:"最后一次修改时间"`
+	CreateTime        time.Time `gorm:"column:create_time" description:"任务创建时间"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *IndexTaskRecord) TableName() string {
+	return "index_task_record"
+}
+
+// IndexTaskRecordColumns get sql column name.获取数据库列名
+var IndexTaskRecordColumns = struct {
+	IndexTaskRecordID string
+	IndexTaskID       string
+	Parameters        string
+	Status            string
+	Remark            string
+	ModifyTime        string
+	CreateTime        string
+}{
+	IndexTaskRecordID: "index_task_record_id",
+	IndexTaskID:       "index_task_id",
+	Parameters:        "parameters",
+	Status:            "status",
+	Remark:            "remark",
+	ModifyTime:        "modify_time",
+	CreateTime:        "create_time",
+}
+
+func (m *IndexTaskRecord) Create() (err error) {
+	err = global.DEFAULT_DB.Create(&m).Error
+
+	return
+}
+
+func (m *IndexTaskRecord) Update(updateCols []string) (err error) {
+	err = global.DEFAULT_DB.Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *IndexTaskRecord) Del() (err error) {
+	err = global.DEFAULT_DB.Delete(&m).Error
+
+	return
+}
+
+func (m *IndexTaskRecord) GetByID(id int) (item *IndexTaskRecord, err error) {
+	err = global.DEFAULT_DB.Where(fmt.Sprintf("%s = ?", IndexTaskRecordColumns.IndexTaskRecordID), id).First(&item).Error
+
+	return
+}
+
+func (m *IndexTaskRecord) GetByCondition(condition string, pars []interface{}) (item *IndexTaskRecord, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DEFAULT_DB.Raw(sqlStr, pars...).First(&item).Error
+
+	return
+}
+
+func (m *IndexTaskRecord) GetAllListByCondition(field, condition string, pars []interface{}) (items []*IndexTaskRecord, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by index_task_record_id desc `, field, m.TableName(), condition)
+	err = global.DEFAULT_DB.Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *IndexTaskRecord) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*IndexTaskRecord, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by index_task_record_id desc LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DEFAULT_DB.Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *IndexTaskRecord) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DEFAULT_DB.Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+// QuestionGenerateAbstractParam
+// @Description:
+type QuestionGenerateAbstractParam struct {
+	QuestionId  int    `json:"questionId"`
+	ArticleType string `json:"articleType"`
+	ArticleId   int    `json:"articleId"`
+}

+ 27 - 0
routers/commentsRouter.go

@@ -7,6 +7,33 @@ import (
 
 func init() {
 
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "HandleTaskRecordFailByTaskRecord",
+            Router: `/handle/fail`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "HandleTaskRecordSuccessByTaskRecord",
+            Router: `/handle/success`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_index_lib/controllers/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "OpToDo",
+            Router: `/op_todo`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_index_lib/controllers/factor_edb_series:FactorEdbSeriesController"] = append(beego.GlobalControllerRouter["eta/eta_index_lib/controllers/factor_edb_series:FactorEdbSeriesController"],
         beego.ControllerComments{
             Method: "ChartRecalculate",

+ 6 - 0
routers/router.go

@@ -9,6 +9,7 @@ package routers
 
 import (
 	"eta/eta_index_lib/controllers"
+	"eta/eta_index_lib/controllers/ai_predict_model"
 	"eta/eta_index_lib/controllers/factor_edb_series"
 	"eta/eta_index_lib/controllers/fix"
 	"eta/eta_index_lib/controllers/future_good"
@@ -357,6 +358,11 @@ func init() {
 				&controllers.RadishResearchController{},
 			),
 		),
+		beego.NSNamespace("/ai_predict_model",
+			beego.NSInclude(
+				&ai_predict_model.AiPredictModelIndexController{},
+			),
+		),
 	)
 	beego.AddNamespace(ns)
 }

+ 6 - 0
utils/constants.go

@@ -259,6 +259,7 @@ const (
 	CACHE_BASE_EDB_ADD               = "CACHE_BASE_EDB_ADD_"              // 添加至数据源缓存
 	CACHE_BASE_EDB_REFRESH           = "CACHE_BASE_EDB_REFRESH_"          // 刷新数据源缓存
 	CACHE_EXCEL_REFRESH              = "CACHE_EXCEL_REFRESH"              // 表格刷新
+	CACHE_INDEX_TASK                 = "eta:index:task:op:"               // 指标库的任务调度缓存
 )
 
 // 图表类型
@@ -391,6 +392,11 @@ const (
 	StlTypeNonTrend = 4 // 非趋势性指标
 )
 
+const (
+	INDEX_TASK_TYPE_AI_MODEL_TRAIN = `ai_predict_model_train`
+	INDEX_TASK_TYPE_AI_MODEL_RUN   = `ai_predict_model_run`
+)
+
 // 日期序列
 const (
 	WindWsdDaysDefault      = "Trading"   // 日期选项:交易日参数值

+ 1 - 0
utils/redis.go

@@ -16,6 +16,7 @@ type RedisClient interface {
 	IsExist(key string) bool
 	LPush(key string, val interface{}) error
 	Brpop(key string, callback func([]byte))
+	BrpopVal(key string) (val string, err error)
 	GetRedisTTL(key string) time.Duration
 	Incrby(key string, num int) (interface{}, error)
 	Do(commandName string, args ...interface{}) (reply interface{}, err error)

+ 23 - 0
utils/redis/cluster_redis.go

@@ -224,6 +224,29 @@ func (rc *ClusterRedisClient) Brpop(key string, callback func([]byte)) {
 
 }
 
+// BrpopVal
+// @Description: 从list中读取一个值(只取值,不做其他处理)
+// @author: Roc
+// @receiver rc
+// @datetime 2025-04-10 10:12:22
+// @param key string
+// @return val string
+// @return err error
+func (rc *ClusterRedisClient) BrpopVal(key string) (val string, err error) {
+	values, err := rc.redisClient.BRPop(context.TODO(), 1*time.Second, key).Result()
+	if err != nil {
+		return
+	}
+	if len(values) < 2 {
+		fmt.Println("assert is wrong")
+		return
+	}
+
+	val = values[1]
+
+	return
+}
+
 // GetRedisTTL
 // @Description: 获取key的过期时间
 // @receiver rc

+ 23 - 0
utils/redis/standalone_redis.go

@@ -216,6 +216,29 @@ func (rc *StandaloneRedisClient) Brpop(key string, callback func([]byte)) {
 
 }
 
+// BrpopVal
+// @Description: 从list中读取一个值(只取值,不做其他处理)
+// @author: Roc
+// @receiver rc
+// @datetime 2025-04-10 10:12:22
+// @param key string
+// @return val string
+// @return err error
+func (rc *StandaloneRedisClient) BrpopVal(key string) (val string, err error) {
+	values, err := rc.redisClient.BRPop(context.TODO(), 1*time.Second, key).Result()
+	if err != nil {
+		return
+	}
+	if len(values) < 2 {
+		fmt.Println("assert is wrong")
+		return
+	}
+
+	val = values[1]
+
+	return
+}
+
 // GetRedisTTL
 // @Description: 获取key的过期时间
 // @receiver rc