浏览代码

AI预测模型接口

hsun 3 月之前
父节点
当前提交
0f58556f23

+ 507 - 0
controllers/data_manage/ai_predict_model/classify.go

@@ -0,0 +1,507 @@
+package ai_predict_model
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// AiPredictModelClassifyController AI预测模型-分类
+type AiPredictModelClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 分类列表
+// @Description 分类列表
+// @Param   ParentId   query   bool   false   "父级ID"
+// @Success 200 {object} data_manage.ChartClassifyListResp
+// @router /classify/list [get]
+func (this *AiPredictModelClassifyController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	parentId, _ := this.GetInt("ParentId")
+	resp := new(aiPredictModel.AiPredictModelClassifyListResp)
+
+	// (懒加载)仅查询直属分类
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId)
+		list, e := classifyOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", classifyOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取子分类失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			resp.AllNodes = append(resp.AllNodes, &aiPredictModel.AiPredictModelClassifyListItem{
+				NodeName:     v.ClassifyName,
+				ClassifyId:   v.AiPredictModelClassifyId,
+				ClassifyName: v.ClassifyName,
+				ParentId:     v.ParentId,
+				Level:        v.Level,
+				Sort:         v.Sort,
+				UniqueCode:   v.UniqueCode,
+			})
+		}
+	}
+
+	// 非顶级目录查询指标
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	if parentId > 0 {
+		parentClassify, e := classifyOb.GetItemById(parentId)
+		if e != nil {
+			br.Msg = "父级分类不存在, 请刷新页面"
+			return
+		}
+
+		cond := fmt.Sprintf(" AND %s = ?", indexOb.Cols().ClassifyId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, parentId)
+		list, e := indexOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", indexOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取分类下指标失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			resp.AllNodes = append(resp.AllNodes, &aiPredictModel.AiPredictModelClassifyListItem{
+				NodeType:     1,
+				NodeName:     v.IndexName,
+				ClassifyId:   parentClassify.AiPredictModelClassifyId,
+				ClassifyName: parentClassify.ClassifyName,
+				IndexId:      v.AiPredictModelIndexId,
+				IndexCode:    v.IndexCode,
+				IndexName:    v.IndexName,
+				ParentId:     parentId,
+				Sort:         v.Sort,
+				UniqueCode:   v.IndexCode,
+			})
+		}
+	}
+	sort.Slice(resp.AllNodes, func(i, j int) bool {
+		return resp.AllNodes[i].Sort < resp.AllNodes[j].Sort
+	})
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Add
+// @Title 新增分类
+// @Description 新增分类
+// @Param	request	body aiPredictModel.AiPredictModelClassifyAddReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /classify/add [post]
+func (this *AiPredictModelClassifyController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req aiPredictModel.AiPredictModelClassifyAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "请选择上级分类"
+		return
+	}
+	if req.Level > 5 {
+		br.Msg = "目前只支持6级目录"
+		return
+	}
+
+	// 校验分类名称
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ParentId)
+		if this.Lang == utils.EnLangVersion {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyNameEn)
+		} else {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyName)
+		}
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ParentId, req.ClassifyName)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类名称重复数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在"
+			return
+		}
+	}
+
+	// 层级路径
+	var levelPath string
+	var rootId int
+	if req.ParentId > 0 {
+		parent, e := classifyOb.GetItemById(req.ParentId)
+		if e != nil {
+			br.Msg = "上级分类有误"
+			br.ErrMsg = fmt.Sprintf("获取上级分类失败, %v", e)
+			return
+		}
+		levelPath = parent.LevelPath
+		rootId = parent.RootId
+	}
+
+	sortMax, e := classifyOb.GetSortMax(req.ParentId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类最大排序失败, %v", e)
+		return
+	}
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	classifyOb.ParentId = req.ParentId
+	classifyOb.ClassifyName = req.ClassifyName
+	classifyOb.ClassifyNameEn = req.ClassifyName
+	classifyOb.Level = req.Level + 1
+	classifyOb.Sort = sortMax + 1
+	classifyOb.SysUserId = sysUser.AdminId
+	classifyOb.SysUserRealName = sysUser.RealName
+	classifyOb.UniqueCode = utils.MD5(classifyOb.TableName() + "_" + timestamp)
+	classifyOb.CreateTime = time.Now().Local()
+	classifyOb.ModifyTime = time.Now().Local()
+	if e = classifyOb.Create(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("新增分类失败, %v", e)
+		return
+	}
+	if req.ParentId > 0 {
+		// 用英文逗号拼接方便查询
+		classifyOb.LevelPath = fmt.Sprintf("%s,%d", levelPath, classifyOb.AiPredictModelClassifyId)
+		classifyOb.RootId = rootId
+	} else {
+		classifyOb.LevelPath = fmt.Sprint(classifyOb.AiPredictModelClassifyId)
+		classifyOb.RootId = classifyOb.AiPredictModelClassifyId
+	}
+	if e = classifyOb.Update([]string{classifyOb.Cols().LevelPath, classifyOb.Cols().RootId}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新分类失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "操作成功"
+	br.Success = true
+}
+
+// Edit
+// @Title 修改分类
+// @Description 修改分类
+// @Param	request	body aiPredictModel.AiPredictModelClassifyEditReq true "type json string"
+// @Success 200 Ret=200 修改成功
+// @router /classify/edit [post]
+func (this *AiPredictModelClassifyController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req aiPredictModel.AiPredictModelClassifyEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	classifyItem, e := classifyOb.GetItemById(req.ClassifyId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "分类不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+		return
+	}
+
+	// 校验分类名称
+	{
+		cond := fmt.Sprintf(" AND %s <> ?", classifyOb.Cols().PrimaryId)
+		if this.Lang == utils.EnLangVersion {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyNameEn)
+		} else {
+			cond += fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ClassifyName)
+		}
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ClassifyId, req.ClassifyName)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类名称重复数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在"
+			return
+		}
+	}
+	classifyItem.ClassifyName = req.ClassifyName
+	classifyItem.ClassifyNameEn = req.ClassifyName
+	classifyItem.ModifyTime = time.Now().Local()
+	updateCols := []string{classifyOb.Cols().ClassifyName, classifyOb.Cols().ClassifyNameEn, classifyOb.Cols().ModifyTime}
+	if e = classifyItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新分类失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "操作成功"
+	br.Success = true
+}
+
+// RemoveCheck
+// @Title 删除校验
+// @Description 删除校验
+// @Param	request	body aiPredictModel.AiPredictModelClassifyRemoveReq true "type json string"
+// @Success 200 Ret=200 检测成功
+// @router /classify/remove_check [post]
+func (this *AiPredictModelClassifyController) RemoveCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req aiPredictModel.AiPredictModelClassifyRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 && req.IndexId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	var deleteStatus int
+	var tipsMsg string
+	// 删除分类
+	if req.ClassifyId > 0 && req.IndexId == 0 {
+		count, err := aiPredictModel.GetAiPredictModelIndexCountByClassifyId(req.ClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有图表失败,Err:" + err.Error()
+			return
+		}
+
+		if count > 0 {
+			deleteStatus = 1
+			tipsMsg = "该分类下关联图表不可删除"
+		}
+	}
+
+	if deleteStatus != 1 && req.IndexId == 0 {
+		classifyCount, err := aiPredictModel.GetAiPredictModelClassifyCountByClassifyId(req.ClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "分类下是否含有图表失败,Err:" + err.Error()
+			return
+		}
+		if classifyCount > 0 {
+			deleteStatus = 2
+			tipsMsg = "确认删除当前目录及包含的子目录吗"
+		}
+	}
+	if deleteStatus == 0 {
+		tipsMsg = "可删除,进行删除操作"
+	}
+
+	resp := new(data_manage.ChartClassifyDeleteCheckResp)
+	resp.DeleteStatus = deleteStatus
+	resp.TipsMsg = tipsMsg
+	br.Ret = 200
+	br.Msg = "检测成功"
+	br.Success = true
+	br.Data = resp
+}
+
+// Remove
+// @Title 删除分类/标的
+// @Description 删除分类/标的
+// @Param	request	body aiPredictModel.AiPredictModelClassifyRemoveReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /classify/remove [post]
+func (this *AiPredictModelClassifyController) Remove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	var req aiPredictModel.AiPredictModelClassifyRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 && req.IndexId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	// 删除分类
+	if req.ClassifyId > 0 && req.IndexId == 0 {
+		count, err := aiPredictModel.GetAiPredictModelIndexCountByClassifyId(req.ClassifyId)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "删除失败"
+			br.ErrMsg = "判断名称是否已存在失败,Err:" + err.Error()
+			return
+		}
+		if count > 0 {
+			br.Msg = "该目录下存在关联指标,不可删除"
+			br.IsSendEmail = false
+			return
+		}
+		err = aiPredictModel.RemoveAiPredictModelClassify(req.ClassifyId)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	// 删除标的
+	if req.IndexId > 0 {
+		indexOb := new(aiPredictModel.AiPredictModelIndex)
+		_, e := indexOb.GetItemById(req.IndexId)
+		if e != nil {
+			if e.Error() == utils.ErrNoRow() {
+				br.Ret = 200
+				br.Msg = "删除成功"
+				br.Success = true
+				return
+			}
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取标的信息失败, %v", e)
+			return
+		}
+
+		// 删除标的及数据
+		if e = indexOb.RemoveIndexAndData(req.IndexId); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("删除标的及数据失败, %v", e)
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Msg = "删除成功"
+	br.Success = true
+	br.IsAddLog = true
+}
+
+// Move
+// @Title 移动
+// @Description 移动
+// @Success 200 {object} aiPredictModel.AiPredictModelClassifyMoveReq
+// @router /classify/move [post]
+func (this *AiPredictModelClassifyController) Move() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req aiPredictModel.AiPredictModelClassifyMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId <= 0 && req.ItemId <= 0 {
+		br.Msg = "请选择分类或指标"
+		return
+	}
+
+	err, errMsg := services.AiPredictModelMoveClassify(req, sysUser)
+	if errMsg != `` {
+		br.Msg = errMsg
+		br.ErrMsg = errMsg
+		if err != nil {
+			br.ErrMsg = err.Error()
+		} else {
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "移动成功"
+}

+ 766 - 0
controllers/data_manage/ai_predict_model/index.go

@@ -0,0 +1,766 @@
+package ai_predict_model
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// AiPredictModelIndexController AI预测模型标的
+type AiPredictModelIndexController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 标的列表
+// @Description 标的列表
+// @Param   PageSize   query   int   true   "每页数据条数"
+// @Param   CurrentIndex   query   int   true   "当前页页码,从1开始"
+// @Param   ClassifyId   query   int   false   "分类id"
+// @Param   Keyword   query   string   false   "搜索关键词"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /index/list [get]
+func (this *AiPredictModelIndexController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	classifyId, _ := this.GetInt("ClassifyId")
+	keyword := this.GetString("KeyWord")
+	keyword = strings.TrimSpace(keyword)
+	resp := new(aiPredictModel.AiPredictModelIndexPageListResp)
+
+	// 分页
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	// 分类
+	classifyIdName := make(map[int]string)
+	{
+		classifyOb := new(aiPredictModel.AiPredictModelClassify)
+		list, e := classifyOb.GetItemsByCondition("", make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			classifyIdName[v.AiPredictModelClassifyId] = v.ClassifyName
+		}
+	}
+
+	// 筛选条件
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	var cond string
+	var pars []interface{}
+	{
+		if classifyId > 0 {
+			cond += fmt.Sprintf(" AND %s = ?", indexOb.Cols().ClassifyId)
+			pars = append(pars, classifyId)
+		}
+		if keyword != "" {
+			cond += fmt.Sprintf(" AND %s LIKE ?", indexOb.Cols().IndexName)
+			pars = append(pars, fmt.Sprint("%", keyword, "%"))
+		}
+	}
+
+	// 获取列表
+	total, e := indexOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取标的总数失败, %v", e)
+		return
+	}
+	list, e := indexOb.GetPageItemsByCondition(cond, pars, []string{}, "", startSize, pageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取分页列表失败, %v", e)
+		return
+	}
+	pageList := make([]*aiPredictModel.AiPredictModelIndexItem, 0)
+	for _, v := range list {
+		t := v.Format2Item()
+		t.ClassifyName = classifyIdName[v.ClassifyId]
+		pageList = append(pageList, t)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp.Paging = page
+	resp.List = pageList
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Import
+// @Title 导入标的和数据
+// @Description 导入标的和数据
+// @Param   IndexFile   query   file   true   "标的文件"
+// @Success 200 Ret=200 录入成功
+// @router /index/import [post]
+func (this *AiPredictModelIndexController) Import() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	file, _, e := this.GetFile("IndexFile")
+	if e != nil {
+		br.Msg = "导入失败"
+		br.ErrMsg = fmt.Sprintf("获取文件失败, %v", e)
+		return
+	}
+	path := "./static/ai_predict_model_temp_" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	defer func() {
+		_ = file.Close()
+		_ = os.Remove(path)
+	}()
+	if e = this.SaveToFile("IndexFile", path); e != nil {
+		br.Msg = "导入失败"
+		br.ErrMsg = fmt.Sprintf("保存文件失败, %v", e)
+		return
+	}
+	xlFile, e := xlsx.OpenFile(path)
+	if e != nil {
+		br.Msg = "导入失败"
+		br.ErrMsg = fmt.Sprintf("打开excel文件失败, %v", e)
+		return
+	}
+
+	// 获取分类和用户,遍历时校验
+	classifyNameId := make(map[string]int)
+	adminNameId := make(map[string]int)
+	{
+		classifyOb := new(aiPredictModel.AiPredictModelClassify)
+		classifyCond := fmt.Sprintf(` AND %s = ?`, classifyOb.Cols().ParentId)
+		classifyPars := make([]interface{}, 0)
+		classifyPars = append(classifyPars, 0) // 只取一级分类(临时过渡方案,业务端只会加一级)
+		classifies, e := classifyOb.GetItemsByCondition(classifyCond, classifyPars, []string{}, "")
+		if e != nil {
+			br.Msg = "导入失败"
+			br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+			return
+		}
+		for _, v := range classifies {
+			classifyNameId[v.ClassifyName] = v.AiPredictModelClassifyId
+		}
+
+		admins, e := system.GetSysAdminList(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			br.Msg = "导入失败"
+			br.ErrMsg = fmt.Sprintf("获取用户失败, %v", e)
+			return
+		}
+		for _, v := range admins {
+			adminNameId[v.RealName] = v.AdminId
+		}
+	}
+
+	// 遍历sheet页
+	// 列表页:预测标的|分类|模型框架|创建人|预测日期|预测值|预测频度|方向准确率|绝对偏差
+	type ImportDataColKey struct {
+		IndexName string
+		ColKey    int
+		DataDate  time.Time
+	}
+	imports := make(map[string]*aiPredictModel.AiPredictModelImportData)
+	importsData := make(map[string]map[time.Time]*aiPredictModel.AiPredictModelData)
+	for sheetKey, sheet := range xlFile.Sheets {
+		maxRow := sheet.MaxRow
+
+		// 列表页
+		if sheetKey == 0 {
+			for i := 0; i < maxRow; i++ {
+				// 忽略首行标题
+				if i < 1 {
+					continue
+				}
+				row := sheet.Row(i)
+				cells := row.Cells
+				if len(cells) < 9 {
+					continue
+				}
+
+				// 标的名称
+				indexName := strings.TrimSpace(cells[0].String())
+				if indexName == "" {
+					continue
+				}
+				if imports[indexName] == nil {
+					imports[indexName] = new(aiPredictModel.AiPredictModelImportData)
+					imports[indexName].Index = new(aiPredictModel.AiPredictModelIndex)
+					imports[indexName].Data = make([]*aiPredictModel.AiPredictModelData, 0)
+				}
+				imports[indexName].Index.IndexName = indexName
+				imports[indexName].Index.CreateTime = time.Now()
+				imports[indexName].Index.ModifyTime = time.Now()
+
+				// 分类
+				classifyName := strings.TrimSpace(cells[1].String())
+				if classifyNameId[classifyName] <= 0 {
+					br.Msg = fmt.Sprintf("分类:%s不存在", classifyName)
+					return
+				}
+				imports[indexName].Index.ClassifyId = classifyNameId[classifyName]
+
+				// 创建人
+				adminName := strings.TrimSpace(cells[3].String())
+				if adminNameId[adminName] <= 0 {
+					br.Msg = fmt.Sprintf("创建人:%s不存在", adminName)
+					return
+				}
+				imports[indexName].Index.SysUserId = adminNameId[adminName]
+				imports[indexName].Index.SysUserRealName = adminName
+
+				// 其余信息
+				imports[indexName].Index.ModelFramework = strings.TrimSpace(cells[2].String())
+				strDate := strings.TrimSpace(cells[4].String())
+				predictDate, _ := time.Parse("2006/01/02", strDate)
+				if predictDate.IsZero() {
+					predictDate, _ = time.Parse("01-02-06", strDate)
+					if predictDate.IsZero() {
+						predictDate, _ = time.Parse("2006/1/2", strDate)
+					}
+				}
+				imports[indexName].Index.PredictDate = predictDate
+				predictVal, _ := cells[5].Float()
+				imports[indexName].Index.PredictValue = predictVal
+				imports[indexName].Index.PredictFrequency = strings.TrimSpace(cells[6].String())
+				imports[indexName].Index.DirectionAccuracy = strings.TrimSpace(cells[7].String())
+				imports[indexName].Index.AbsoluteDeviation = strings.TrimSpace(cells[8].String())
+			}
+		}
+
+		// 数据页
+		if sheetKey == 1 {
+			// 每五列为一个指标的数据
+			colKeys := make(map[int]*ImportDataColKey) // 每一列对应的指标名称以及对应的字段序号
+			for i := 0; i < maxRow; i++ {
+				// 首行为指标名称
+				if i == 0 {
+					nameCol := 0
+					row := sheet.Row(i)
+					for ck, cell := range row.Cells {
+						nameCol += 1
+						if nameCol > 5 {
+							nameCol = 1
+						}
+						if nameCol == 1 {
+							// nameCol=1时为指标/数据行则为日期
+							indexName := strings.TrimSpace(cell.String())
+							if indexName == "" {
+								continue
+							}
+							importsData[indexName] = make(map[time.Time]*aiPredictModel.AiPredictModelData)
+
+							colKeys[ck] = &ImportDataColKey{
+								ColKey:    1,
+								IndexName: indexName,
+							}
+
+							// 后面四列分别对应: 实际值|预测值|方向|偏差率, 这里直接加无须考虑是否会越界
+							colKeys[ck+1] = &ImportDataColKey{
+								ColKey:    2,
+								IndexName: indexName,
+							}
+							colKeys[ck+2] = &ImportDataColKey{
+								ColKey:    3,
+								IndexName: indexName,
+							}
+							colKeys[ck+3] = &ImportDataColKey{
+								ColKey:    4,
+								IndexName: indexName,
+							}
+							colKeys[ck+4] = &ImportDataColKey{
+								ColKey:    5,
+								IndexName: indexName,
+							}
+							continue
+						}
+					}
+					continue
+				}
+
+				// 第二行为标题,跳过
+				if i == 1 {
+					continue
+				}
+
+				// 剩余为数据行
+				row := sheet.Row(i)
+				for ck, cell := range row.Cells {
+					if colKeys[ck] == nil {
+						continue
+					}
+					if colKeys[ck].IndexName == "" {
+						continue
+					}
+					switch colKeys[ck].ColKey {
+					case 1:
+						// 日期列
+						strDate := strings.TrimSpace(cell.String())
+						dataDate, _ := time.Parse("2006/01/02", strDate)
+						if dataDate.IsZero() {
+							dataDate, _ = time.Parse("01-02-06", strDate)
+							if dataDate.IsZero() {
+								dataDate, _ = time.Parse("2006/1/2", strDate)
+								if dataDate.IsZero() {
+									continue
+								}
+							}
+						}
+						colKeys[ck].DataDate = dataDate
+						colKeys[ck+1].DataDate = dataDate
+						colKeys[ck+2].DataDate = dataDate
+						colKeys[ck+3].DataDate = dataDate
+						colKeys[ck+4].DataDate = dataDate
+						importRow := imports[colKeys[ck].IndexName]
+						if importRow == nil {
+							continue
+						}
+						// 新增当前日期数据
+						importsData[colKeys[ck].IndexName][dataDate] = new(aiPredictModel.AiPredictModelData)
+						importsData[colKeys[ck].IndexName][dataDate].DataTime = dataDate
+						importsData[colKeys[ck].IndexName][dataDate].CreateTime = time.Now()
+						importsData[colKeys[ck].IndexName][dataDate].ModifyTime = time.Now()
+					case 2, 3:
+						// 实际值和预测值, 可能为空
+						dataDate := colKeys[ck].DataDate
+						if importsData[colKeys[ck].IndexName][dataDate] == nil {
+							continue
+						}
+						strVal := strings.TrimSpace(cell.String())
+						if strVal == "" {
+							continue
+						}
+						val, _ := strconv.ParseFloat(strVal, 64)
+						if colKeys[ck].ColKey == 2 {
+							importsData[colKeys[ck].IndexName][dataDate].Value.Valid = true
+							importsData[colKeys[ck].IndexName][dataDate].Value.Float64 = val
+						} else {
+							importsData[colKeys[ck].IndexName][dataDate].PredictValue.Valid = true
+							importsData[colKeys[ck].IndexName][dataDate].PredictValue.Float64 = val
+						}
+					case 4, 5:
+						// 方向/偏差率
+						dataDate := colKeys[ck].DataDate
+						if importsData[colKeys[ck].IndexName][dataDate] == nil {
+							continue
+						}
+						str := strings.TrimSpace(cell.String())
+						if str == "" {
+							continue
+						}
+						if colKeys[ck].ColKey == 4 {
+							importsData[colKeys[ck].IndexName][dataDate].Direction = str
+						} else {
+							importsData[colKeys[ck].IndexName][dataDate].DeviationRate = str
+						}
+					default:
+						continue
+					}
+				}
+			}
+		}
+	}
+
+	for indexName, v := range importsData {
+		if imports[indexName] == nil {
+			continue
+		}
+		for _, dateData := range v {
+			imports[indexName].Data = append(imports[indexName].Data, dateData)
+		}
+	}
+	importIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
+	for _, v := range imports {
+		importIndexes = append(importIndexes, v)
+	}
+
+	// 导入指标
+	if e = services.ImportAiPredictModelIndexAndData(importIndexes); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("导入指标数据失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// Detail
+// @Title 标的详情
+// @Description 标的详情
+// @Param   IndexId   query   int   true   "标的ID"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /index/detail [get]
+func (this *AiPredictModelIndexController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	indexId, _ := this.GetInt("IndexId")
+	if indexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IndexId: %d", indexId)
+		return
+	}
+	resp := new(aiPredictModel.AiPredictModelDetailResp)
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexItem, e := indexOb.GetItemById(indexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标的已被删除,请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取标的失败, %v", e)
+		return
+	}
+
+	// 获取标的数据
+	indexData := make([]*aiPredictModel.AiPredictModelData, 0)
+	{
+		tableData := make([]*aiPredictModel.AiPredictModelDataItem, 0)
+		dataOb := new(aiPredictModel.AiPredictModelData)
+		dataCond := fmt.Sprintf(` AND %s = ?`, dataOb.Cols().AiPredictModelIndexId)
+		dataPars := make([]interface{}, 0)
+		dataPars = append(dataPars, indexId)
+		list, e := dataOb.GetItemsByCondition(dataCond, dataPars, []string{}, fmt.Sprintf("%s DESC", dataOb.Cols().DataTime))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取标的数据失败, %v", e)
+			return
+		}
+		indexData = list
+		for _, v := range list {
+			tableData = append(tableData, v.Format2Item())
+		}
+		resp.TableData = tableData
+	}
+
+	// 获取图表数据
+	chartDetail, e := services.GetAiPredictChartDetailByData(indexItem, indexData)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取图表数据失败, %v", e)
+		return
+	}
+	resp.ChartView = chartDetail
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// Save
+// @Title 保存标的
+// @Description 保存标的
+// @Param	request	body aiPredictModel.AiPredictModelIndexSaveReq true "type json string"
+// @Success 200 Ret=200 保存成功
+// @router /index/save [post]
+func (this *AiPredictModelIndexController) Save() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req aiPredictModel.AiPredictModelIndexSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.IndexId < 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("标的ID有误, IndexId: %d", req.IndexId)
+		return
+	}
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexItem, e := indexOb.GetItemById(req.IndexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标的已被删除,请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取标的失败, %v", e)
+		return
+	}
+
+	var updateCols []string
+	if req.LeftMin != "" {
+		indexItem.LeftMin = req.LeftMin
+		updateCols = append(updateCols, indexOb.Cols().LeftMin)
+	}
+	if req.LeftMax != "" {
+		indexItem.LeftMax = req.LeftMax
+		updateCols = append(updateCols, indexOb.Cols().LeftMax)
+	}
+	indexItem.ModifyTime = time.Now()
+	updateCols = append(updateCols, indexOb.Cols().ModifyTime)
+	if e = indexItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("保存标的失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "操作成功"
+	br.Success = true
+}
+
+// DashboardSave
+// @Title 保存看板
+// @Description 保存看板
+// @Param	request	body aiPredictModel.AiPredictModelDashboardSaveReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /index/dashboard/save [post]
+func (this *AiPredictModelIndexController) DashboardSave() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req aiPredictModel.AiPredictModelDashboardSaveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.IndexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, %d", req.IndexId)
+		return
+	}
+	req.DashboardName = strings.TrimSpace(req.DashboardName)
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	_, e := indexOb.GetItemById(req.IndexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标的已被删除,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取标的失败, %v", e)
+		return
+	}
+	// 仅标的创建人有权限操作看板
+	//if sysUser.AdminId != indexItem.SysUserId {
+	//	br.Msg = "仅创建人可操作"
+	//	return
+	//}
+
+	// 获取看板
+	var isUpdate bool
+	var updateCols []string
+	dashboardItem := new(aiPredictModel.AiPredictModelDashboard)
+	if req.IndexId > 0 {
+		cond := fmt.Sprintf(" AND %s = ?", dashboardItem.Cols().AiPredictModelIndexId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.IndexId)
+		item, e := dashboardItem.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取标的看板失败, %v", e)
+			return
+		}
+		if item != nil {
+			isUpdate = true
+			dashboardItem = item
+			dashboardItem.DashboardName = req.DashboardName
+			dashboardItem.ModifyTime = time.Now()
+			updateCols = append(updateCols, dashboardItem.Cols().DashboardName, dashboardItem.Cols().ModifyTime)
+		}
+	}
+	if !isUpdate {
+		dashboardItem.AiPredictModelIndexId = req.IndexId
+		dashboardItem.DashboardName = req.DashboardName
+		dashboardItem.SysUserId = sysUser.AdminId
+		dashboardItem.SysUserRealName = sysUser.RealName
+		dashboardItem.CreateTime = time.Now()
+		dashboardItem.ModifyTime = time.Now()
+	}
+
+	// 详情
+	dashboardDetails := make([]*aiPredictModel.AiPredictModelDashboardDetail, 0)
+	for i, v := range req.List {
+		t := &aiPredictModel.AiPredictModelDashboardDetail{
+			Type:       v.Type,
+			UniqueCode: v.UniqueCode,
+			Sort:       i + 1,
+			CreateTime: time.Now(),
+			ModifyTime: time.Now(),
+		}
+		dashboardDetails = append(dashboardDetails, t)
+	}
+
+	// 保存
+	if e := dashboardItem.SaveIndexDashboard(dashboardItem, dashboardDetails, isUpdate, updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("保存标的看板失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// DashboardDetail
+// @Title 看板详情
+// @Description 看板详情
+// @Param   IndexId   query   int   true   "标的ID"
+// @Success 200 {object} aiPredictModel.AiPredictModelDashboardDetailResp
+// @router /index/dashboard/detail [get]
+func (this *AiPredictModelIndexController) DashboardDetail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	indexId, _ := this.GetInt("IndexId")
+	resp := new(aiPredictModel.AiPredictModelDashboardDetailResp)
+
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexItem, e := indexOb.GetItemById(indexId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "标的已被删除,请刷新页面"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取标的失败, %v", e)
+		return
+	}
+	resp.CreateUserId = indexItem.SysUserId
+	resp.CreateUserRealName = indexItem.SysUserRealName
+
+	// 获取标的看板
+	dashboardOb := new(aiPredictModel.AiPredictModelDashboard)
+	dashboardItem := new(aiPredictModel.AiPredictModelDashboardItem)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", dashboardOb.Cols().AiPredictModelIndexId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, indexId)
+		item, e := dashboardOb.GetItemByCondition(cond, pars, "")
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取标的看板失败, %v", e)
+			return
+		}
+		if item != nil {
+			dashboardItem = item.Format2Item()
+		}
+	}
+
+	// 获取看板详情
+	dashboardDetails := make([]*aiPredictModel.AiPredictModelDashboardDetailItem, 0)
+	if dashboardItem.DashboardId > 0 {
+		detailOb := new(aiPredictModel.AiPredictModelDashboardDetail)
+		cond := fmt.Sprintf(" AND %s = ?", detailOb.Cols().AiPredictModelDashboardId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, dashboardItem.DashboardId)
+		list, e := detailOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", detailOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取看板详情失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			dashboardDetails = append(dashboardDetails, v.Format2Item())
+		}
+	}
+
+	resp.AiPredictModelDashboardItem = dashboardItem
+	resp.List = dashboardDetails
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 356 - 0
models/ai_predict_model/ai_predict_model_classify.go

@@ -0,0 +1,356 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// AiPredictModelClassify 同花顺高频数据-分类
+type AiPredictModelClassify struct {
+	AiPredictModelClassifyId int       `orm:"column(ai_predict_model_classify_id);pk"`
+	UniqueCode               string    `description:"唯一编码"`
+	ClassifyName             string    `description:"分类名称"`
+	ClassifyNameEn           string    `description:"英文分类名称"`
+	ParentId                 int       `description:"父级ID"`
+	Level                    int       `description:"层级"`
+	LevelPath                string    `description:"层级路径"`
+	Sort                     int       `description:"排序"`
+	RootId                   int       `description:"顶级分类ID"`
+	SysUserId                int       `description:"创建人ID"`
+	SysUserRealName          string    `description:"创建人姓名"`
+	CreateTime               time.Time `description:"创建时间"`
+	ModifyTime               time.Time `description:"修改时间"`
+}
+
+func (m *AiPredictModelClassify) TableName() string {
+	return "ai_predict_model_classify"
+}
+
+type AiPredictModelClassifyCols struct {
+	PrimaryId       string
+	UniqueCode      string
+	ClassifyName    string
+	ClassifyNameEn  string
+	ParentId        string
+	Level           string
+	LevelPath       string
+	Sort            string
+	RootId          string
+	SysUserId       string
+	SysUserRealName string
+	CreateTime      string
+	ModifyTime      string
+}
+
+func (m *AiPredictModelClassify) Cols() AiPredictModelClassifyCols {
+	return AiPredictModelClassifyCols{
+		PrimaryId:       "ai_predict_model_classify_id",
+		UniqueCode:      "unique_code",
+		ClassifyName:    "classify_name",
+		ClassifyNameEn:  "classify_name_en",
+		ParentId:        "parent_id",
+		Level:           "level",
+		LevelPath:       "level_path",
+		Sort:            "sort",
+		RootId:          "root_id",
+		SysUserId:       "sys_user_id",
+		SysUserRealName: "sys_user_real_name",
+		CreateTime:      "create_time",
+		ModifyTime:      "modify_time",
+	}
+}
+
+func (m *AiPredictModelClassify) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelClassifyId = int(id)
+	return
+}
+
+func (m *AiPredictModelClassify) CreateMulti(items []*AiPredictModelClassify) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelClassify) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelClassify) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.AiPredictModelClassifyId).Exec()
+	return
+}
+
+func (m *AiPredictModelClassify) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *AiPredictModelClassify) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}
+
+func (m *AiPredictModelClassify) GetItemById(id int) (item *AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelClassify) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelClassify) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *AiPredictModelClassify) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRows(&items)
+	return
+}
+
+func (m *AiPredictModelClassify) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// AiPredictModelClassifyItem 同花顺高频数据信息
+type AiPredictModelClassifyItem struct {
+	ClassifyId     int                           `description:"分类ID"`
+	ClassifyName   string                        `description:"分类名称"`
+	ClassifyNameEn string                        `description:"英文分类名称"`
+	ParentId       int                           `description:"父级ID"`
+	Level          int                           `description:"层级"`
+	Sort           int                           `description:"排序"`
+	LevelPath      string                        `description:"层级路径"`
+	UniqueCode     string                        `description:"唯一编码"`
+	Children       []*AiPredictModelClassifyItem `description:"子分类"`
+}
+
+func (m *AiPredictModelClassify) Format2Item() (item *AiPredictModelClassifyItem) {
+	item = new(AiPredictModelClassifyItem)
+	item.ClassifyId = m.AiPredictModelClassifyId
+	item.ClassifyName = m.ClassifyName
+	item.ClassifyNameEn = m.ClassifyNameEn
+	item.ParentId = m.ParentId
+	item.Level = m.Level
+	item.Sort = m.Sort
+	item.LevelPath = m.LevelPath
+	item.UniqueCode = m.UniqueCode
+	item.Children = make([]*AiPredictModelClassifyItem, 0)
+	return
+}
+
+type AiPredictModelClassifyAddReq struct {
+	ClassifyName string `description:"分类名称"`
+	ParentId     int    `description:"父级ID, 第一级传0"`
+	Level        int    `description:"层级, 第一级传0, 其余传上一级的层级"`
+}
+
+type AiPredictModelClassifyEditReq struct {
+	ClassifyId   int    `description:"分类ID"`
+	ClassifyName string `description:"分类名称"`
+}
+
+func (m *AiPredictModelClassify) GetSortMax(parentId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT MAX(%s) FROM %s WHERE %s = ?`, m.Cols().Sort, m.TableName(), m.Cols().ParentId)
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+type AiPredictModelClassifyRemoveReq struct {
+	ClassifyId int `description:"分类ID"`
+	IndexId    int `description:"标的ID"`
+}
+
+type AiPredictModelClassifyListResp struct {
+	AllNodes []*AiPredictModelClassifyListItem `description:"分类节点"`
+}
+
+type AiPredictModelClassifyListItem struct {
+	NodeType     int                               `description:"节点类型: 0-分类; 1-指标"`
+	NodeName     string                            `description:"节点名称"`
+	ClassifyId   int                               `description:"分类ID"`
+	ClassifyName string                            `description:"分类名称"`
+	IndexId      int                               `description:"指标ID"`
+	IndexCode    string                            `description:"指标编码"`
+	IndexName    string                            `description:"指标名称"`
+	ParentId     int                               `description:"父级ID"`
+	Level        int                               `description:"层级"`
+	Sort         int                               `description:"排序"`
+	UniqueCode   string                            `description:"唯一编码, 指标为IndexCode"`
+	Children     []*AiPredictModelClassifyListItem `description:"子分类"`
+}
+
+type AiPredictModelClassifyMoveReq struct {
+	ClassifyId       int `description:"分类ID"`
+	ParentClassifyId int `description:"父级分类ID"`
+	PrevClassifyId   int `description:"上一个兄弟节点分类ID"`
+	NextClassifyId   int `description:"下一个兄弟节点分类ID"`
+	ItemId           int `description:"指标ID, 如果指标ID有值,则移动对象为指标,否则认为移动对象为分类"`
+	PrevItemId       int `description:"上一个指标ID"`
+	NextItemId       int `description:"下一个指标ID"`
+}
+
+func GetAiPredictModelClassifyById(classifyId int) (item *AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM ai_predict_model_classify WHERE ai_predict_model_classify_id = ?`
+	err = o.Raw(sql, classifyId).QueryRow(&item)
+	return
+}
+
+func GetAiPredictModelClassifyByRootIdLevel(rootId int, orderStr string) (items []*AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM ai_predict_model_classify WHERE root_id = ? `
+	if orderStr != "" {
+		sql += orderStr
+	} else {
+		sql += ` order by level desc, sort asc, ai_predict_model_classify_id asc`
+	}
+	_, err = o.Raw(sql, rootId).QueryRows(&items)
+	return
+}
+
+// UpdateAiPredictModelClassifySortByParentId 根据父类id更新排序
+func UpdateAiPredictModelClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update ai_predict_model_classify set sort = ` + updateSort + ` WHERE parent_id = ? AND sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( ai_predict_model_classify_id > ` + fmt.Sprint(classifyId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetFirstAiPredictModelClassifyByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func GetFirstAiPredictModelClassifyByParentId(parentId int) (item *AiPredictModelClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM ai_predict_model_classify WHERE parent_id = ? order by sort asc,ai_predict_model_classify_id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}
+
+func UpdateAiPredictModelClassifyChildByParentClassifyId(classifyIds []int, rootId int, levelStep int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	var pars []interface{}
+	pars = append(pars, rootId, levelStep)
+	pars = append(pars, classifyIds)
+	// 更新相关联的二级分类的parentId,和classify_name_second
+	sql := `UPDATE ai_predict_model_classify SET root_id = ?, level = level+? where ai_predict_model_classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
+	_, err = o.Raw(sql, pars).Exec()
+	if err != nil {
+		return
+	}
+	return
+}
+
+// GetAiPredictModelIndexCountByClassifyId 获取目录下(包含子目录)的指标数量
+func GetAiPredictModelIndexCountByClassifyId(classifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(1) AS count FROM ai_predict_model_index AS a
+				WHERE a.classify_id IN(
+				SELECT t.ai_predict_model_classify_id FROM 
+				(
+				SELECT rd.*
+				FROM (SELECT * FROM ai_predict_model_classify WHERE parent_id IS NOT NULL) rd,
+					 (SELECT @pid := ?) pd 
+				WHERE FIND_IN_SET(parent_id, @pid) > 0 
+				  AND @pid := CONCAT(@pid, ',', ai_predict_model_classify_id) 
+				UNION SELECT * FROM ai_predict_model_classify WHERE ai_predict_model_classify_id = @pid
+				)AS t
+			)`
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+// GetAiPredictModelClassifyCountByClassifyId 获取目录下子目录数量
+func GetAiPredictModelClassifyCountByClassifyId(chartClassifyId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(1) AS count FROM (
+			SELECT rd.*
+			FROM (SELECT * FROM ai_predict_model_classify WHERE parent_id IS NOT NULL) rd,
+				 (SELECT @pid := ?) pd 
+			WHERE FIND_IN_SET(parent_id, @pid) > 0 
+			  AND @pid := CONCAT(@pid, ',', ai_predict_model_classify_id) 
+			UNION SELECT * FROM ai_predict_model_classify WHERE ai_predict_model_classify_id = @pid
+			)AS t
+			WHERE t.ai_predict_model_classify_id <> ?`
+	err = o.Raw(sql, chartClassifyId, chartClassifyId).QueryRow(&count)
+	return
+}
+
+// RemoveAiPredictModelClassify 删除分类及子分类
+func RemoveAiPredictModelClassify(classifyId int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM ai_predict_model_classify
+				WHERE ai_predict_model_classify_id IN(
+				SELECT t.ai_predict_model_classify_id FROM
+				(
+				SELECT rd.*
+				FROM (SELECT * FROM ai_predict_model_classify WHERE parent_id IS NOT NULL) rd,
+				(SELECT @pid := ?) pd
+				WHERE FIND_IN_SET(parent_id, @pid) > 0
+				AND @pid := CONCAT(@pid, ',', ai_predict_model_classify_id)
+				UNION SELECT * FROM ai_predict_model_classify WHERE ai_predict_model_classify_id = @pid
+				)AS t
+			)`
+	_, err = o.Raw(sql, classifyId).Exec()
+	return
+}

+ 261 - 0
models/ai_predict_model/ai_predict_model_dashboard.go

@@ -0,0 +1,261 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// AiPredictModelDashboard AI预测模型-BI看板
+type AiPredictModelDashboard struct {
+	AiPredictModelDashboardId int       `orm:"column(ai_predict_model_dashboard_id);pk"`
+	AiPredictModelIndexId     int       `description:"标的ID"`
+	DashboardName             string    `description:"看板名称"`
+	Sort                      int       `description:"排序"`
+	SysUserId                 int       `description:"创建人ID"`
+	SysUserRealName           string    `description:"创建人姓名"`
+	CreateTime                time.Time `description:"创建时间"`
+	ModifyTime                time.Time `description:"修改时间"`
+}
+
+func (m *AiPredictModelDashboard) TableName() string {
+	return "ai_predict_model_dashboard"
+}
+
+type AiPredictModelDashboardCols struct {
+	PrimaryId             string
+	AiPredictModelIndexId string
+	DashboardName         string
+	Sort                  string
+	SysUserId             string
+	SysUserRealName       string
+	CreateTime            string
+	ModifyTime            string
+}
+
+func (m *AiPredictModelDashboard) Cols() AiPredictModelDashboardCols {
+	return AiPredictModelDashboardCols{
+		PrimaryId:             "ai_predict_model_dashboard_id",
+		AiPredictModelIndexId: "ai_predict_model_index_id",
+		DashboardName:         "dashboard_name",
+		Sort:                  "sort",
+		SysUserId:             "sys_user_id",
+		SysUserRealName:       "sys_user_real_name",
+		CreateTime:            "create_time",
+		ModifyTime:            "modify_time",
+	}
+}
+
+func (m *AiPredictModelDashboard) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelDashboardId = int(id)
+	return
+}
+
+func (m *AiPredictModelDashboard) CreateMulti(items []*AiPredictModelDashboard) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelDashboard) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelDashboard) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.AiPredictModelDashboardId).Exec()
+	return
+}
+
+func (m *AiPredictModelDashboard) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *AiPredictModelDashboard) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}
+
+func (m *AiPredictModelDashboard) GetItemById(id int) (item *AiPredictModelDashboard, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelDashboard) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelDashboard, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelDashboard) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *AiPredictModelDashboard) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelDashboard, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRows(&items)
+	return
+}
+
+func (m *AiPredictModelDashboard) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelDashboard, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// AiPredictModelDashboardItem AI预测模型-BI看板
+type AiPredictModelDashboardItem struct {
+	DashboardId     int    `description:"看板ID"`
+	IndexId         int    `description:"标的ID"`
+	DashboardName   string `description:"看板名称"`
+	Sort            int    `description:"排序"`
+	SysUserId       int    `description:"创建人ID"`
+	SysUserRealName string `description:"创建人姓名"`
+	CreateTime      string `description:"创建时间"`
+	ModifyTime      string `description:"修改时间"`
+}
+
+func (m *AiPredictModelDashboard) Format2Item() (item *AiPredictModelDashboardItem) {
+	item = new(AiPredictModelDashboardItem)
+	item.DashboardId = m.AiPredictModelDashboardId
+	item.IndexId = m.AiPredictModelIndexId
+	item.DashboardName = m.DashboardName
+	item.Sort = m.Sort
+	item.SysUserId = m.SysUserId
+	item.SysUserRealName = m.SysUserRealName
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}
+
+// AiPredictModelDashboardDetailResp BI看板详情响应
+type AiPredictModelDashboardDetailResp struct {
+	*AiPredictModelDashboardItem
+	CreateUserId       int    `description:"标的创建人ID"`
+	CreateUserRealName string `description:"标的创建人姓名"`
+	List               []*AiPredictModelDashboardDetailItem
+}
+
+// AiPredictModelDashboardSaveReq BI看板保存请求
+type AiPredictModelDashboardSaveReq struct {
+	IndexId       int    `description:"标的ID"`
+	DashboardName string `description:"看板名称"`
+	List          []*AiPredictModelDashboardDetailReq
+}
+
+type AiPredictModelDashboardDetailReq struct {
+	Type       int
+	UniqueCode string
+	Sort       int
+}
+
+// SaveIndexDashboard 保存标的看板
+func (m *AiPredictModelDashboard) SaveIndexDashboard(dashboardItem *AiPredictModelDashboard, dashboardDetails []*AiPredictModelDashboardDetail, isUpdate bool, updateCols []string) (err error) {
+	if dashboardItem == nil {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("trans begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	// 新增/更新看板
+	var dashboardId int
+	if !isUpdate {
+		newId, e := tx.Insert(dashboardItem)
+		if e != nil {
+			err = fmt.Errorf("insert dashboard err: %v", e)
+			return
+		}
+		dashboardId = int(newId)
+	} else {
+		_, e = tx.Update(dashboardItem, updateCols...)
+		if e != nil {
+			err = fmt.Errorf("update dashboard err: %v", e)
+			return
+		}
+		dashboardId = dashboardItem.AiPredictModelDashboardId
+	}
+
+	// 清空详情并新增
+	if isUpdate {
+		sql := `DELETE FROM ai_predict_model_dashboard_detail WHERE ai_predict_model_dashboard_id = ?`
+		_, e = tx.Raw(sql, dashboardItem.AiPredictModelDashboardId).Exec()
+		if e != nil {
+			err = fmt.Errorf("clear dashboard detail err: %v", e)
+			return
+		}
+	}
+	if len(dashboardDetails) > 0 {
+		for _, v := range dashboardDetails {
+			v.AiPredictModelDashboardId = dashboardId
+		}
+		_, e = tx.InsertMulti(utils.MultiAddNum, dashboardDetails)
+		if e != nil {
+			err = fmt.Errorf("insert dashboard detail err: %v", e)
+			return
+		}
+	}
+	return
+}

+ 176 - 0
models/ai_predict_model/ai_predict_model_dashboard_detail.go

@@ -0,0 +1,176 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// AiPredictModelDashboardDetail AI预测模型-BI看板
+type AiPredictModelDashboardDetail struct {
+	AiPredictModelDashboardDetailId int       `orm:"column(ai_predict_model_dashboard_detail_id);pk"`
+	AiPredictModelDashboardId       int       `description:"看板ID"`
+	Type                            int       `description:"类型:1-图表;2-表格"`
+	UniqueCode                      string    `description:"图表/表格唯一编码"`
+	Sort                            int       `description:"排序"`
+	CreateTime                      time.Time `description:"创建时间"`
+	ModifyTime                      time.Time `description:"修改时间"`
+}
+
+func (m *AiPredictModelDashboardDetail) TableName() string {
+	return "ai_predict_model_dashboard_detail"
+}
+
+type AiPredictModelDashboardDetailCols struct {
+	PrimaryId                 string
+	AiPredictModelDashboardId string
+	Type                      string
+	UniqueCode                string
+	Sort                      string
+	CreateTime                string
+	ModifyTime                string
+}
+
+func (m *AiPredictModelDashboardDetail) Cols() AiPredictModelDashboardDetailCols {
+	return AiPredictModelDashboardDetailCols{
+		PrimaryId:                 "ai_predict_model_dashboard_detail_id",
+		AiPredictModelDashboardId: "ai_predict_model_dashboard_id",
+		Type:                      "type",
+		UniqueCode:                "unique_code",
+		Sort:                      "sort",
+		CreateTime:                "create_time",
+		ModifyTime:                "modify_time",
+	}
+}
+
+func (m *AiPredictModelDashboardDetail) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelDashboardDetailId = int(id)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) CreateMulti(items []*AiPredictModelDashboardDetail) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.AiPredictModelDashboardDetailId).Exec()
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) GetItemById(id int) (item *AiPredictModelDashboardDetail, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelDashboardDetail, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelDashboardDetail, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRows(&items)
+	return
+}
+
+func (m *AiPredictModelDashboardDetail) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelDashboardDetail, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// AiPredictModelDashboardDetailItem AI预测模型-BI看板详情
+type AiPredictModelDashboardDetailItem struct {
+	DashboardDetailId int    `description:"详情ID"`
+	DashboardId       int    `description:"看板ID"`
+	Type              int    `description:"类型:1-图表;2-表格"`
+	UniqueCode        string `description:"图表/表格唯一编码"`
+	Sort              int    `description:"排序"`
+	CreateTime        string `description:"创建时间"`
+	ModifyTime        string `description:"修改时间"`
+}
+
+func (m *AiPredictModelDashboardDetail) Format2Item() (item *AiPredictModelDashboardDetailItem) {
+	item = new(AiPredictModelDashboardDetailItem)
+	item.DashboardDetailId = m.AiPredictModelDashboardDetailId
+	item.DashboardId = m.AiPredictModelDashboardId
+	item.Type = m.Type
+	item.UniqueCode = m.UniqueCode
+	item.Sort = m.Sort
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}

+ 197 - 0
models/ai_predict_model/ai_predict_model_data.go

@@ -0,0 +1,197 @@
+package data_manage
+
+import (
+	"database/sql"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// AiPredictModelData AI预测模型标的数据
+type AiPredictModelData struct {
+	AiPredictModelDataId  int             `orm:"column(ai_predict_model_data_id);pk"`
+	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:"数据日期时间戳"`
+}
+
+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
+}
+
+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",
+	}
+}
+
+func (m *AiPredictModelData) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelDataId = int(id)
+	return
+}
+
+func (m *AiPredictModelData) CreateMulti(items []*AiPredictModelData) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelData) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelData) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sqlRun, m.AiPredictModelDataId).Exec()
+	return
+}
+
+func (m *AiPredictModelData) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sqlRun, ids).Exec()
+	return
+}
+
+func (m *AiPredictModelData) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sqlRun, pars).Exec()
+	return
+}
+
+func (m *AiPredictModelData) GetItemById(id int) (item *AiPredictModelData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sqlRun, id).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelData) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelData) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sqlRun := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sqlRun, pars).QueryRow(&count)
+	return
+}
+
+func (m *AiPredictModelData) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRows(&items)
+	return
+}
+
+func (m *AiPredictModelData) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelData, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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)
+	_, err = o.Raw(sqlRun, pars, startSize, pageSize).QueryRows(&items)
+	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:"数据日期时间戳"`
+}
+
+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
+}

+ 369 - 0
models/ai_predict_model/ai_predict_model_index.go

@@ -0,0 +1,369 @@
+package data_manage
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+// AiPredictModelIndex AI预测模型标的
+type AiPredictModelIndex struct {
+	AiPredictModelIndexId int       `orm:"column(ai_predict_model_index_id);pk"`
+	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:"修改时间"`
+}
+
+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
+}
+
+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",
+	}
+}
+
+func (m *AiPredictModelIndex) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.AiPredictModelIndexId = int(id)
+	return
+}
+
+func (m *AiPredictModelIndex) CreateMulti(items []*AiPredictModelIndex) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *AiPredictModelIndex) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *AiPredictModelIndex) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.AiPredictModelIndexId).Exec()
+	return
+}
+
+func (m *AiPredictModelIndex) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *AiPredictModelIndex) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}
+
+func (m *AiPredictModelIndex) GetItemById(id int) (item *AiPredictModelIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelIndex) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *AiPredictModelIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRow(&item)
+	return
+}
+
+func (m *AiPredictModelIndex) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *AiPredictModelIndex) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRows(&items)
+	return
+}
+
+func (m *AiPredictModelIndex) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*AiPredictModelIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	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:"修改时间"`
+}
+
+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.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) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("trans begin err: %v", e)
+		return
+	}
+	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.Raw(sql, indexId).Exec()
+	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.Raw(sql, indexId).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove index data err: %v", e)
+		return
+	}
+	return
+}
+
+// UpdateAiPredictModelIndexSortByClassifyId 根据分类id更新排序
+func UpdateAiPredictModelIndexSortByClassifyId(classifyId, nowSort int, prevEdbInfoId int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	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.Raw(sql, classifyId, nowSort).Exec()
+	return
+}
+
+// GetFirstAiPredictModelIndexByClassifyId 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstAiPredictModelIndexByClassifyId(classifyId int) (item *AiPredictModelIndex, err error) {
+	o := orm.NewOrmUsingDB("data")
+	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).QueryRow(&item)
+	return
+}
+
+type AiPredictModelImportData struct {
+	Index *AiPredictModelIndex
+	Data  []*AiPredictModelData
+}
+
+// ImportIndexAndData 导入数据
+func (m *AiPredictModelIndex) ImportIndexAndData(createIndexes, updateIndexes []*AiPredictModelImportData, updateCols []string) (err error) {
+	if len(createIndexes) == 0 && len(updateIndexes) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("trans begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	if len(updateIndexes) > 0 {
+		for _, v := range updateIndexes {
+			// 更新指标
+			_, e = tx.Update(v.Index, updateCols...)
+			if e != nil {
+				err = fmt.Errorf("update index err: %v", e)
+				return
+			}
+			for _, d := range v.Data {
+				d.AiPredictModelIndexId = v.Index.AiPredictModelIndexId
+				d.IndexCode = v.Index.IndexCode
+				d.DataTimestamp = d.DataTime.UnixNano() / 1e6
+			}
+
+			// 清空指标并新增
+			sql := `DELETE FROM ai_predict_model_data WHERE ai_predict_model_index_id = ?`
+			_, e = tx.Raw(sql, v.Index.AiPredictModelIndexId).Exec()
+			if e != nil {
+				err = fmt.Errorf("clear index data err: %v", e)
+				return
+			}
+			_, e = tx.InsertMulti(utils.MultiAddNum, v.Data)
+			if e != nil {
+				err = fmt.Errorf("insert index data err: %v", e)
+				return
+			}
+		}
+	}
+
+	if len(createIndexes) > 0 {
+		for _, v := range createIndexes {
+			indexId, e := tx.Insert(v.Index)
+			if e != nil {
+				err = fmt.Errorf("insert index err: %v", e)
+				return
+			}
+			for _, d := range v.Data {
+				d.AiPredictModelIndexId = int(indexId)
+				d.IndexCode = v.Index.IndexCode
+				d.DataTimestamp = d.DataTime.UnixNano() / 1e6
+			}
+			_, e = tx.InsertMulti(utils.MultiAddNum, v.Data)
+			if e != nil {
+				err = fmt.Errorf("insert index data err: %v", e)
+				return
+			}
+		}
+	}
+	return
+}
+
+type AiPredictModelDetailResp struct {
+	TableData []*AiPredictModelDataItem        `description:"表格数据"`
+	ChartView *data_manage.ChartInfoDetailResp `description:"图表信息"`
+}
+
+type AiPredictModelIndexSaveReq struct {
+	IndexId int    `description:"指标ID"`
+	LeftMin string `description:"图表左侧最小值"`
+	LeftMax string `description:"图表左侧最大值"`
+}

+ 15 - 0
models/db.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
 	"eta/eta_api/models/ai_summary"
 	"eta/eta_api/models/aimod"
 	"eta/eta_api/models/bi_dashboard"
@@ -225,6 +226,9 @@ func init() {
 
 	// 智能看板
 	initBiDashBoard()
+
+	// AI预测模型表
+	initAiPredictModel()
 }
 
 // initSystem 系统表 数据表
@@ -688,6 +692,17 @@ func initBiDashBoard() {
 	)
 }
 
+// initAiPredictModel AI预测模型表
+func initAiPredictModel() {
+	orm.RegisterModel(
+		new(aiPredictModel.AiPredictModelClassify),
+		new(aiPredictModel.AiPredictModelIndex),
+		new(aiPredictModel.AiPredictModelData),
+		new(aiPredictModel.AiPredictModelDashboard),
+		new(aiPredictModel.AiPredictModelDashboardDetail),
+	)
+}
+
 // afterInitTable
 // @Description: 初始化表结构的的后置操作
 // @author: Roc

+ 108 - 0
routers/commentsRouter.go

@@ -241,6 +241,114 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "Remove",
+            Router: `/classify/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelClassifyController"],
+        beego.ControllerComments{
+            Method: "RemoveCheck",
+            Router: `/classify/remove_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "DashboardDetail",
+            Router: `/index/dashboard/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "DashboardSave",
+            Router: `/index/dashboard/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/index/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "Import",
+            Router: `/index/import`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/index/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "Save",
+            Router: `/index/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage/correlation:CorrelationChartClassifyController"],
         beego.ControllerComments{
             Method: "AddChartClassify",

+ 7 - 0
routers/router.go

@@ -11,6 +11,7 @@ import (
 	"eta/eta_api/controllers"
 	"eta/eta_api/controllers/ai"
 	"eta/eta_api/controllers/data_manage"
+	"eta/eta_api/controllers/data_manage/ai_predict_model"
 	"eta/eta_api/controllers/data_manage/correlation"
 	"eta/eta_api/controllers/data_manage/cross_variety"
 	"eta/eta_api/controllers/data_manage/data_manage_permission"
@@ -416,6 +417,12 @@ func init() {
 				&controllers.BIDaShboardController{},
 			),
 		),
+		web.NSNamespace("/ai_predict_model",
+			web.NSInclude(
+				&ai_predict_model.AiPredictModelClassifyController{},
+				&ai_predict_model.AiPredictModelIndexController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 513 - 0
services/ai_predict_model_classify.go

@@ -0,0 +1,513 @@
+package services
+
+import (
+	"errors"
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// AiPredictModelMoveClassify 移动分类
+func AiPredictModelMoveClassify(req aiPredictModel.AiPredictModelClassifyMoveReq, sysUser *system.Admin) (err error, errMsg string) {
+	classifyId := req.ClassifyId
+	parentClassifyId := req.ParentClassifyId
+	prevClassifyId := req.PrevClassifyId
+	nextClassifyId := req.NextClassifyId
+
+	itemId := req.ItemId
+	prevItemId := req.PrevItemId
+	nextItemId := req.NextItemId
+
+	//首先确定移动的对象是分类还是指标
+	//判断上一个节点是分类还是指标
+	//判断下一个节点是分类还是指标
+	//同时更新分类目录下的分类sort和指标sort
+	//更新当前移动的分类或者指标sort
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+
+	var parentClassify *aiPredictModel.AiPredictModelClassify
+	if parentClassifyId > 0 {
+		parentClassify, err = classifyOb.GetItemById(parentClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上级分类信息失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		edbClassifyInfo *aiPredictModel.AiPredictModelClassify
+		prevClassify    *aiPredictModel.AiPredictModelClassify
+		nextClassify    *aiPredictModel.AiPredictModelClassify
+
+		edbInfo     *aiPredictModel.AiPredictModelIndex
+		prevEdbInfo *aiPredictModel.AiPredictModelIndex
+		nextEdbInfo *aiPredictModel.AiPredictModelIndex
+		prevSort    int
+		nextSort    int
+	)
+
+	// 移动对象为分类, 判断权限
+	if itemId == 0 {
+		edbClassifyInfo, err = classifyOb.GetItemById(classifyId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前分类不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		if parentClassifyId > 0 && parentClassify.Level == 6 {
+			errMsg = "最高只支持添加6级分类"
+			err = errors.New(errMsg)
+			return
+		}
+		// 如果是移动目录, 那么校验一下父级目录下是否有重名目录
+		exists, e := data_manage.GetEdbClassifyByParentIdAndName(parentClassifyId, edbClassifyInfo.ClassifyName, classifyId)
+		if e != nil && e.Error() != utils.ErrNoRow() {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取父级分类下的同名分类失败, Err: %s", e.Error())
+			return
+		}
+		if exists != nil {
+			errMsg = "移动失败,分类名称已存在"
+			return
+		}
+
+	} else {
+		edbInfo, err = indexOb.GetItemById(req.ItemId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				errMsg = "当前指标不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		if parentClassifyId == 0 {
+			errMsg = "移动失败,指标必须挂在分类下"
+			err = errors.New(errMsg)
+			return
+		}
+
+		//// 移动权限校验
+		//button := GetEdbOpButton(sysUser, edbInfo.SysUserId, edbInfo.EdbType, edbInfo.EdbInfoType, haveOperaAuth)
+		//if !button.MoveButton {
+		//	errMsg = "无操作权限"
+		//	err = errors.New(errMsg)
+		//	return
+		//}
+	}
+
+	if prevClassifyId > 0 {
+		prevClassify, err = classifyOb.GetItemById(prevClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevClassify.Sort
+	} else if prevItemId > 0 {
+		prevEdbInfo, err = indexOb.GetItemById(prevItemId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevEdbInfo.Sort
+	}
+
+	if nextClassifyId > 0 {
+		//下一个兄弟节点
+		nextClassify, err = classifyOb.GetItemById(nextClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextClassify.Sort
+	} else if nextItemId > 0 {
+		//下一个兄弟节点
+		nextEdbInfo, err = indexOb.GetItemById(nextItemId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextEdbInfo.Sort
+	}
+
+	err, errMsg = moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, nextClassify, edbInfo, prevEdbInfo, nextEdbInfo, parentClassifyId, prevSort, nextSort)
+	return
+}
+
+// moveAiPredictModelClassify 移动指标分类
+func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, nextClassify *aiPredictModel.AiPredictModelClassify, edbInfo, prevEdbInfo, nextEdbInfo *aiPredictModel.AiPredictModelIndex, parentClassifyId int, prevSort, nextSort int) (err error, errMsg string) {
+	updateCol := make([]string, 0)
+
+	// 移动对象为分类, 判断分类是否存在
+	if edbClassifyInfo != nil {
+		oldParentId := edbClassifyInfo.ParentId
+		oldLevel := edbClassifyInfo.Level
+		var classifyIds []int
+		if oldParentId != parentClassifyId {
+			//更新子分类对应的level
+			childList, e, m := GetAiPredictModelChildClassifyByClassifyId(edbClassifyInfo.AiPredictModelClassifyId)
+			if e != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询子分类失败,Err:" + e.Error() + m)
+				return
+			}
+
+			if len(childList) > 0 {
+				for _, v := range childList {
+					if v.AiPredictModelClassifyId == edbClassifyInfo.AiPredictModelClassifyId {
+						continue
+					}
+					classifyIds = append(classifyIds, v.AiPredictModelClassifyId)
+				}
+			}
+		}
+		//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+		if edbClassifyInfo.ParentId != parentClassifyId && parentClassifyId != 0 {
+			if edbClassifyInfo.Level != parentClassify.Level+1 { //禁止层级调整
+				errMsg = "移动失败"
+				err = errors.New("不支持目录层级变更")
+				return
+			}
+			edbClassifyInfo.ParentId = parentClassify.AiPredictModelClassifyId
+			edbClassifyInfo.RootId = parentClassify.RootId
+			edbClassifyInfo.Level = parentClassify.Level + 1
+			edbClassifyInfo.ModifyTime = time.Now()
+			// 更改层级路径
+			edbClassifyInfo.LevelPath = fmt.Sprintf("%s,%d", parentClassify.LevelPath, edbClassifyInfo.AiPredictModelClassifyId)
+			updateCol = append(updateCol, "ParentId", "RootId", "Level", "ModifyTime", "LevelPath")
+		} else if edbClassifyInfo.ParentId != parentClassifyId && parentClassifyId == 0 {
+			errMsg = "移动失败"
+			err = errors.New("不支持目录层级变更")
+			return
+		}
+
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == edbClassifyInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, prevClassify.AiPredictModelClassifyId, prevClassify.Sort, updateSortStr)
+					} else {
+						_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+					}
+
+					//变更指标
+					if prevEdbInfo != nil {
+						//变更兄弟节点的排序
+						_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.AiPredictModelIndexId, updateSortStr)
+					} else {
+						_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+
+						//变更分类
+						if prevClassify != nil {
+							_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, prevClassify.AiPredictModelClassifyId, prevSort, updateSortStr)
+						} else {
+							_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+						}
+
+						//变更指标
+						if prevEdbInfo != nil {
+							//变更兄弟节点的排序
+							_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.AiPredictModelIndexId, updateSortStr)
+						} else {
+							_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+						}
+
+					}
+				}
+			}
+
+			edbClassifyInfo.Sort = prevSort + 1
+			edbClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevClassify == nil && nextClassify == nil && prevEdbInfo == nil && nextEdbInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetAiPredictModelClassifyMaxSort(parentClassifyId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			edbClassifyInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			edbClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := aiPredictModel.GetFirstAiPredictModelClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, firstClassify.AiPredictModelClassifyId-1, 0, updateSortStr)
+				//该分类下的所有指标也需要+1
+				_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, 0, updateSortStr)
+			} else {
+				//如果该分类下存在指标,且第一个指标的排序等于0,那么需要调整排序
+				firstEdb, tErr := aiPredictModel.GetFirstAiPredictModelIndexByClassifyId(parentClassifyId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstEdb != nil && firstEdb.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, firstEdb.AiPredictModelIndexId-1, updateSortStr)
+					_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)
+				}
+			}
+
+			edbClassifyInfo.Sort = 0 //那就是排在第一位
+			edbClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = edbClassifyInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+			//更新对应分类的root_id和层级
+			if oldParentId != parentClassifyId {
+				if len(classifyIds) > 0 {
+					levelStep := edbClassifyInfo.Level - oldLevel
+					err = aiPredictModel.UpdateAiPredictModelClassifyChildByParentClassifyId(classifyIds, edbClassifyInfo.RootId, levelStep)
+					if err != nil {
+						errMsg = "移动失败"
+						err = errors.New("更新子分类失败,Err:" + err.Error())
+						return
+					}
+				}
+			}
+		}
+	} else {
+		if edbInfo == nil {
+			errMsg = "当前指标不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		//如果改变了分类,那么移动该指标数据
+		if edbInfo.ClassifyId != parentClassifyId {
+			edbInfo.ClassifyId = parentClassifyId
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "ClassifyId", "ModifyTime")
+		}
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == edbInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, prevClassify.AiPredictModelClassifyId, prevClassify.Sort, updateSortStr)
+					} else {
+						_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+					}
+
+					//变更指标
+					if prevEdbInfo != nil {
+						//变更兄弟节点的排序
+						_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.AiPredictModelIndexId, updateSortStr)
+					} else {
+						_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+						//变更分类
+						if prevClassify != nil {
+							_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, prevClassify.AiPredictModelClassifyId, prevSort, updateSortStr)
+						} else {
+							_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+						}
+
+						//变更指标
+						if prevEdbInfo != nil {
+							//变更兄弟节点的排序
+							_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.AiPredictModelIndexId, updateSortStr)
+						} else {
+							_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+						}
+					}
+				}
+			}
+
+			edbInfo.Sort = prevSort + 1
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevClassify == nil && nextClassify == nil && prevEdbInfo == nil && nextEdbInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			classifyOb := new(aiPredictModel.AiPredictModelClassify)
+			maxSort, err = classifyOb.GetSortMax(parentClassifyId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			edbInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := aiPredictModel.GetFirstAiPredictModelClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && tmpErr.Error() != utils.ErrNoRow() {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, firstClassify.AiPredictModelClassifyId-1, 0, updateSortStr)
+				//该分类下的所有指标也需要+1
+				_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, 0, updateSortStr)
+			} else {
+				//如果该分类下存在指标,且第一个指标的排序等于0,那么需要调整排序
+				firstEdb, tErr := aiPredictModel.GetFirstAiPredictModelIndexByClassifyId(parentClassifyId)
+				if tErr != nil && tErr.Error() != utils.ErrNoRow() {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstEdb != nil && firstEdb.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, firstEdb.AiPredictModelIndexId-1, updateSortStr)
+					_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)
+				}
+			}
+
+			edbInfo.Sort = 0 //那就是排在第一位
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = edbInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+		}
+	}
+	return
+}
+
+func GetAiPredictModelChildClassifyByClassifyId(targetClassifyId int) (targetList []*aiPredictModel.AiPredictModelClassify, err error, errMsg string) {
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	//判断是否是挂在顶级目录下
+	targetClassify, err := classifyOb.GetItemById(targetClassifyId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			errMsg = "当前分类不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		errMsg = "获取失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	orderStr := ` order by level asc, sort asc, ai_predict_model_classify_id asc`
+	tmpList, err := aiPredictModel.GetAiPredictModelClassifyByRootIdLevel(targetClassify.RootId, orderStr)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		errMsg = "获取失败"
+		err = errors.New("获取数据失败,Err:" + err.Error())
+		return
+	}
+	idMap := make(map[int]struct{})
+	if len(tmpList) > 0 {
+		for _, v := range tmpList {
+			if v.AiPredictModelClassifyId == targetClassify.AiPredictModelClassifyId {
+				idMap[v.AiPredictModelClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ParentId]; ok {
+				idMap[v.AiPredictModelClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.AiPredictModelClassifyId]; ok {
+				targetItem := new(aiPredictModel.AiPredictModelClassify)
+				targetItem.AiPredictModelClassifyId = v.AiPredictModelClassifyId
+				targetItem.ParentId = v.ParentId
+				targetItem.RootId = v.RootId
+				//targetItem.UniqueCode = v.UniqueCode
+				targetItem.Level = v.Level
+				targetItem.ClassifyName = v.ClassifyName
+				//targetItem.IsJoinPermission = v.IsJoinPermission
+				targetList = append(targetList, targetItem)
+			}
+		}
+	}
+
+	return
+}
+
+func GetAiPredictModelClassifyMaxSort(parentId int) (maxSort int, err error) {
+	//获取该层级下最大的排序数
+	classifyOb := new(aiPredictModel.AiPredictModelClassify)
+	classifyMaxSort, err := classifyOb.GetSortMax(parentId)
+	if err != nil {
+		return
+	}
+	maxSort = classifyMaxSort
+	edbMaxSort, err := data_manage.GetThsHfIndexMaxSortByClassifyId(parentId)
+	if err != nil {
+		return
+	}
+	if maxSort < edbMaxSort {
+		maxSort = edbMaxSort
+	}
+	return
+}

+ 188 - 0
services/ai_predict_model_index.go

@@ -0,0 +1,188 @@
+package services
+
+import (
+	aiPredictModel "eta/eta_api/models/ai_predict_model"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+func ImportAiPredictModelIndexAndData(imports []*aiPredictModel.AiPredictModelImportData) (err error) {
+	if len(imports) == 0 {
+		return
+	}
+
+	// 查询已存在的标的
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexNameItem := make(map[string]*aiPredictModel.AiPredictModelIndex)
+	{
+		list, e := indexOb.GetItemsByCondition("", make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取标的失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			indexNameItem[v.IndexName] = v
+		}
+	}
+
+	updateCols := []string{indexOb.Cols().ClassifyId, indexOb.Cols().ModelFramework, indexOb.Cols().PredictDate, indexOb.Cols().PredictValue, indexOb.Cols().DirectionAccuracy, indexOb.Cols().AbsoluteDeviation, indexOb.Cols().SysUserId, indexOb.Cols().SysUserRealName, indexOb.Cols().ModifyTime}
+	updateIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
+	createIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
+	for _, v := range imports {
+		exist := indexNameItem[v.Index.IndexName]
+		// 编辑
+		if exist != nil {
+			v.Index.AiPredictModelIndexId = exist.AiPredictModelIndexId
+			v.Index.IndexCode = exist.IndexCode
+			updateIndexes = append(updateIndexes, v)
+			continue
+		}
+
+		// 新增
+		indexCode, e := utils.GenerateEdbCode(1, "IPM")
+		if e != nil {
+			err = fmt.Errorf("生成标的编码失败, %v", e)
+			return
+		}
+		v.Index.IndexCode = indexCode
+		createIndexes = append(createIndexes, v)
+	}
+
+	// 新增/更新指标
+	if e := indexOb.ImportIndexAndData(createIndexes, updateIndexes, updateCols); e != nil {
+		err = fmt.Errorf("导入指标失败, %v", e)
+		return
+	}
+	return
+}
+
+func GetAiPredictChartDetailByData(indexItem *aiPredictModel.AiPredictModelIndex, indexData []*aiPredictModel.AiPredictModelData) (resp *data_manage.ChartInfoDetailResp, err error) {
+	resp = new(data_manage.ChartInfoDetailResp)
+
+	// 获取曲线图主题样式
+	chartView := new(data_manage.ChartInfoView)
+	chartView.ChartType = utils.CHART_SOURCE_DEFAULT
+	chartTheme, e := data.GetChartThemeConfig(0, chartView.ChartType, utils.CHART_TYPE_CURVE)
+	if e != nil {
+		err = fmt.Errorf("获取图表主题样式失败, %v", e)
+		return
+	}
+	chartView.ChartThemeStyle = chartTheme.Config
+	chartView.ChartThemeId = chartTheme.ChartThemeId
+
+	chartView.ChartName = indexItem.IndexName
+	chartView.ChartNameEn = indexItem.IndexName
+	chartView.DateType = 3
+	chartView.Calendar = "公历"
+	chartView.ChartSource = "AI预测模型"
+	chartView.ChartSourceEn = "AI预测模型"
+
+	// EdbList-固定一条为标的实际值、一条为预测值
+	edbList := make([]*data_manage.ChartEdbInfoMapping, 0)
+	edbActual, edbPredict := new(data_manage.ChartEdbInfoMapping), new(data_manage.ChartEdbInfoMapping)
+	edbActual.EdbName = indexItem.IndexName
+	edbActual.EdbNameEn = indexItem.IndexName
+	edbActual.IsAxis = 1
+	edbPredict.EdbName = "预测值"
+	edbPredict.EdbNameEn = "预测值"
+	edbPredict.IsAxis = 1
+	actualData, predictData := make([]*data_manage.EdbDataList, 0), make([]*data_manage.EdbDataList, 0)
+
+	var startDate, endDate time.Time
+	var actualValues, predictValues []float64
+	var actualNewest, predictNewest bool
+	for k, v := range indexData {
+		// 如果实际值和预测值都是null那么该日期无效直接忽略
+		if !v.Value.Valid && !v.PredictValue.Valid {
+			continue
+		}
+
+		// 将有效值加入[]float64,最后取极值
+		if v.Value.Valid {
+			actualValues = append(actualValues, v.Value.Float64)
+		}
+		if v.PredictValue.Valid {
+			predictValues = append(predictValues, v.PredictValue.Float64)
+		}
+
+		// 开始结束时间
+		if k == 0 {
+			startDate = v.DataTime
+			endDate = v.CreateTime
+		}
+		if v.DataTime.Before(startDate) {
+			startDate = v.DataTime
+		}
+		if v.DataTime.After(endDate) {
+			endDate = v.DataTime
+		}
+
+		// 指标数据
+		if v.Value.Valid {
+			if !actualNewest {
+				edbActual.LatestDate = v.DataTime.Format(utils.FormatDate)
+				edbActual.LatestValue = v.Value.Float64
+				actualNewest = true
+			}
+			actualData = append(actualData, &data_manage.EdbDataList{
+				DataTime:      v.DataTime.Format(utils.FormatDate),
+				Value:         v.Value.Float64,
+				DataTimestamp: v.DataTimestamp,
+			})
+		}
+		if v.PredictValue.Valid {
+			if !predictNewest {
+				edbPredict.LatestDate = v.DataTime.Format(utils.FormatDate)
+				edbPredict.LatestValue = v.Value.Float64
+				predictNewest = true
+			}
+			predictData = append(predictData, &data_manage.EdbDataList{
+				DataTime:      v.DataTime.Format(utils.FormatDate),
+				Value:         v.PredictValue.Float64,
+				DataTimestamp: v.DataTimestamp,
+			})
+		}
+	}
+
+	// 极值
+	actualMin, actualMax := utils.FindMinMax(actualValues)
+	predictMin, predictMax := utils.FindMinMax(predictValues)
+	edbActual.MinData = actualMin
+	edbActual.MaxData = actualMax
+	edbPredict.MinData = predictMin
+	edbPredict.MaxData = predictMax
+
+	edbActual.DataList = actualData
+	edbPredict.DataList = predictData
+	edbList = append(edbList, edbActual, edbPredict)
+
+	// 上下限
+	if indexItem.LeftMin != "" {
+		chartView.LeftMin = indexItem.LeftMin
+	} else {
+		leftMin := actualMin
+		if leftMin > predictMin {
+			leftMin = predictMin
+		}
+		chartView.LeftMin = fmt.Sprint(leftMin)
+	}
+	if indexItem.LeftMax != "" {
+		chartView.LeftMax = indexItem.LeftMax
+	} else {
+		leftMax := actualMax
+		if leftMax < predictMax {
+			leftMax = predictMax
+		}
+		chartView.LeftMax = fmt.Sprint(leftMax)
+	}
+
+	chartView.StartDate = startDate.Format(utils.FormatDate)
+	chartView.EndDate = endDate.Format(utils.FormatDate)
+
+	resp.ChartInfo = chartView
+	resp.EdbInfoList = edbList
+	return
+}

+ 20 - 0
utils/common.go

@@ -2769,3 +2769,23 @@ func RoundNumber(num string, decimalPlaces int, hasPercent bool) string {
 	}
 	return numStr
 }
+
+// FindMinMax 取出数组中的最小值和最大值
+func FindMinMax(numbers []float64) (min float64, max float64) {
+	if len(numbers) == 0 {
+		return 0, 0 // 如果切片为空,返回0, 0
+	}
+
+	min, max = numbers[0], numbers[0] // 初始化 min 和 max 为切片的第一个元素
+
+	for _, num := range numbers {
+		if num < min {
+			min = num
+		}
+		if num > max {
+			max = num
+		}
+	}
+
+	return min, max
+}