Bläddra i källkod

Merge branch 'feature/eta_2.5.2'

hsun 3 veckor sedan
förälder
incheckning
cdcee70f62

+ 81 - 0
controllers/data_manage/ai_predict_model/framework.go

@@ -0,0 +1,81 @@
+package ai_predict_model
+
+import (
+	"eta/eta_mobile/controllers"
+	"eta/eta_mobile/models"
+	aiPredictModel "eta/eta_mobile/models/ai_predict_model"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"strings"
+)
+
+// AiPredictModelFrameworkController 模型框架
+type AiPredictModelFrameworkController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 列表
+// @Description 列表
+// @Param   AdminId		query	int		false	"创建人ID"
+// @Param   Visibility	query	int		false	"范围: 0-所有; 1-私有; 2-公开"
+// @Param   Keyword		query	string	false	"关键词"
+// @Success 200 Ret=200 获取成功
+// @router /framework/list [get]
+func (c *AiPredictModelFrameworkController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	adminId, _ := c.GetInt("AdminId")
+	keyword := c.GetString("Keyword")
+	keyword = strings.TrimSpace(keyword)
+
+	frameworkOb := new(aiPredictModel.AiPredictModelFramework)
+
+	cond := ``
+	pars := make([]interface{}, 0)
+	if adminId > 0 {
+		cond += fmt.Sprintf(` AND %s = ?`, aiPredictModel.AiPredictModelFrameworkColumns.AdminId)
+		pars = append(pars, adminId)
+	}
+	if keyword != "" {
+		cond += fmt.Sprintf(` AND %s LIKE ?`, aiPredictModel.AiPredictModelFrameworkColumns.FrameworkName)
+		pars = append(pars, "%"+keyword+"%")
+	}
+
+	orderRule := `sort ASC, create_time DESC`
+	list, e := frameworkOb.GetItemsByCondition(cond, pars, []string{}, orderRule)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取框架列表失败, Err: " + e.Error()
+		return
+	}
+	resp := make([]*aiPredictModel.AiPredictModelFrameworkItem, 0)
+	for _, v := range list {
+		t := aiPredictModel.FormatAiPredictModelFramework2Item(v, make([]*aiPredictModel.AiPredictModelFrameworkNodeItem, 0))
+		if t.AdminId == sysUser.AdminId || utils.IsAdminRole(sysUser.RoleTypeCode) {
+			t.Button.OpButton = true
+			t.Button.DeleteButton = true
+			t.Button.MoveButton = true
+		}
+		resp = append(resp, t)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

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

@@ -0,0 +1,162 @@
+package ai_predict_model
+
+import (
+	"eta/eta_mobile/controllers"
+	"eta/eta_mobile/models"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/utils"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// AiPredictModelIndexController AI预测模型标的
+type AiPredictModelIndexController struct {
+	controllers.BaseAuthController
+}
+
+// SearchByEs
+// @Title 图表模糊搜索(从es获取)
+// @Description  图表模糊搜索(从es获取)
+// @Param   Keyword   query   string  true       "图表名称"
+// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   Source   query   int  true       "来源,14:日度预测,15:月度预测,默认0:全部14+15"
+// @Success 200 {object} data_manage.ChartInfo
+// @router /chart/search_by_es [get]
+func (this *AiPredictModelIndexController) SearchByEs() {
+	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
+	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	keyword := this.GetString("Keyword")
+	keyword = strings.TrimSpace(keyword)
+	if keyword == "" {
+		keyword = this.GetString("KeyWord")
+		keyword = strings.TrimSpace(keyword)
+	}
+
+	//只看我的
+	isShowMe, _ := this.GetBool("IsShowMe")
+	showSysId := 0
+	if isShowMe {
+		showSysId = sysUser.AdminId
+	}
+
+	source, _ := this.GetInt("Source")
+	sourceList := make([]int, 0)
+	if source <= 0 {
+		sourceList = append(sourceList, utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY, utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY)
+	} else {
+		sourceList = append(sourceList, source)
+	}
+
+	var searchList []*data_manage.ChartInfoMore
+	var total int64
+	var err error
+
+	// 获取当前账号的不可见指标(AI预测的指标为标的均可见)
+	noPermissionChartIdList := make([]int, 0)
+	//{
+	//	obj := data_manage.EdbInfoNoPermissionAdmin{}
+	//	confList, err := obj.GetAllChartListByAdminId(this.SysUser.AdminId)
+	//	if err != nil && !utils.IsErrNoRow(err) {
+	//		br.Msg = "获取失败"
+	//		br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
+	//		return
+	//	}
+	//	for _, v := range confList {
+	//		noPermissionChartIdList = append(noPermissionChartIdList, v.ChartInfoId)
+	//	}
+	//}
+
+	if keyword != "" {
+		searchList, total, err = data.EsSearchChartInfo(keyword, showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+	} else {
+		total, searchList, err = data_manage.ChartInfoSearchByEmptyKeyWord(showSysId, sourceList, noPermissionChartIdList, startSize, pageSize)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	finalList := make([]*data_manage.ChartInfoMore, 0)
+	if len(searchList) > 0 {
+		chartInfoIds := ""
+		chartEdbMap := make(map[int][]*data_manage.ChartEdbInfoMapping)
+		for _, v := range searchList {
+			chartInfoIds += strconv.Itoa(v.ChartInfoId) + ","
+		}
+		if chartInfoIds != "" {
+			chartInfoIds = strings.Trim(chartInfoIds, ",")
+			//判断是否需要展示英文标识
+			edbList, e := data_manage.GetChartEdbMappingListByChartInfoIds(chartInfoIds)
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + e.Error()
+				return
+			}
+			for _, v := range edbList {
+				chartEdbMap[v.ChartInfoId] = append(chartEdbMap[v.ChartInfoId], v)
+			}
+		}
+
+		for _, v := range searchList {
+			tmp := new(data_manage.ChartInfoMore)
+			tmp.ChartInfo = v.ChartInfo
+			// 图表数据权限
+			tmp.HaveOperaAuth = true
+			//判断是否需要展示英文标识
+			if edbTmpList, ok := chartEdbMap[v.ChartInfoId]; ok {
+				tmp.IsEnChart = data.CheckIsEnChart(v.ChartNameEn, edbTmpList, v.Source, v.ChartType)
+			}
+			tmp.SearchText = v.SearchText
+			if tmp.SearchText == "" {
+				tmp.SearchText = v.ChartName
+			}
+			finalList = append(finalList, tmp)
+		}
+	}
+	//新增搜索词记录
+	{
+		searchKeyword := new(data_manage.SearchKeyword)
+		searchKeyword.KeyWord = keyword
+		searchKeyword.CreateTime = time.Now()
+		go data_manage.AddSearchKeyword(searchKeyword)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, int(total))
+	resp := data_manage.ChartInfoListByEsResp{
+		Paging: page,
+		List:   finalList,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}

+ 133 - 0
controllers/data_manage/chart_common.go

@@ -9,6 +9,7 @@ import (
 	"eta/eta_mobile/controllers/data_manage/line_feature"
 	"eta/eta_mobile/controllers/data_manage/range_analysis"
 	"eta/eta_mobile/models"
+	aiPredictModel "eta/eta_mobile/models/ai_predict_model"
 	"eta/eta_mobile/models/data_manage"
 	"eta/eta_mobile/models/system"
 	"eta/eta_mobile/services"
@@ -16,6 +17,7 @@ import (
 	"eta/eta_mobile/services/data/excel"
 	"eta/eta_mobile/utils"
 	"fmt"
+	"strings"
 	"time"
 )
 
@@ -180,6 +182,29 @@ func (this *ChartInfoController) CommonChartInfoDetailFromUniqueCode() {
 		br.Success = true
 		br.Msg = "获取成功"
 		br.Data = resp
+	case utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY, utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY:
+		resp, isOk, msg, errMsg := GetAiPredictChartInfoDetailFromUniqueCode(chartInfo, isCache)
+		if !isOk {
+			if strings.Contains(errMsg, utils.ErrNoRow()) {
+				endInfoList := make([]*data_manage.ChartEdbInfoMapping, 0)
+				resp.EdbInfoList = endInfoList
+				resp.ChartInfo = chartInfo
+				resp.Status = false
+
+				br.Data = resp
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				return
+			}
+			br.Msg = msg
+			br.ErrMsg = errMsg
+			return
+		}
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		br.Data = resp
 	default:
 		br.Msg = "错误的图表"
 		br.ErrMsg = "错误的图表"
@@ -285,6 +310,114 @@ func (this *ChartInfoController) GeneralChartToken() {
 	br.Success = true
 	br.Msg = "获取成功"
 	br.Data = token
+	return
+}
 
+// GetAiPredictChartInfoDetailFromUniqueCode 根据编码获取AI预测模型图表详情
+func GetAiPredictChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoView, isCache bool) (resp *data_manage.ChartInfoDetailFromUniqueCodeResp, isOk bool, msg, errMsg string) {
+	var err error
+	msg = "获取成功"
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("UniqueCode获取图表详情失败, %v", err)
+			msg = "获取失败"
+			errMsg = fmt.Sprintf(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	if chartInfo == nil {
+		err = fmt.Errorf("图表信息不存在")
+		return
+	}
+	if chartInfo.Source != utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY && chartInfo.Source != utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY {
+		err = fmt.Errorf("图表来源有误, Source: %d", chartInfo.Source)
+		return
+	}
+	resp = new(data_manage.ChartInfoDetailFromUniqueCodeResp)
+
+	// 获取图表标的
+	edbMappings, e := data_manage.GetChartEdbMappingsByChartInfoId(chartInfo.ChartInfoId)
+	if e != nil {
+		err = fmt.Errorf("获取图表指标关联失败, %v", e)
+		return
+	}
+	if len(edbMappings) == 0 {
+		err = fmt.Errorf("图表指标关联不存在, %v", e)
+		return
+	}
+	indexId := edbMappings[0].EdbInfoId
+	if indexId <= 0 {
+		err = fmt.Errorf("图表标的有误")
+		return
+	}
+	indexOb := new(aiPredictModel.AiPredictModelIndex)
+	indexItem, e := indexOb.GetItemById(indexId)
+	if e != nil {
+		err = fmt.Errorf("获取图表标的失败, %v", e)
+		return
+	}
+	if indexItem != nil && indexItem.AiPredictModelIndexId <= 0 {
+		err = fmt.Errorf("图表标的不存在, IndexId: %d", indexId)
+		return
+	}
+
+	// 获取标的数据
+	indexData := make([]*aiPredictModel.AiPredictModelData, 0)
+	dataSource := aiPredictModel.ModelDataSourceDaily
+	if chartInfo.Source == utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY {
+		dataSource = aiPredictModel.ModelDataSourceMonthly
+	}
+	dataOb := new(aiPredictModel.AiPredictModelData)
+	dataCond := fmt.Sprintf(` AND %s = ?`, dataOb.Cols().IndexCode)
+	dataPars := make([]interface{}, 0)
+	dataPars = append(dataPars, indexItem.IndexCode)
+	list, e := dataOb.GetItemsByCondition(dataCond, dataPars, []string{}, fmt.Sprintf("%s DESC", dataOb.Cols().DataTime))
+	if e != nil {
+		err = fmt.Errorf("获取标的数据失败, %v", e)
+		return
+	}
+	for _, v := range list {
+		if v.Source == dataSource {
+			indexData = append(indexData, v)
+			continue
+		}
+	}
+
+	//判断是否存在缓存,如果存在缓存,那么直接从缓存中获取
+	key := data.GetChartInfoDataKey(chartInfo.ChartInfoId)
+	if utils.Re == nil && isCache {
+		if utils.Re == nil && utils.Rc.IsExist(key) {
+			if chartData, e := utils.Rc.RedisBytes(key); e == nil {
+				e = json.Unmarshal(chartData, &resp)
+				if e != nil || resp == nil {
+					return
+				}
+				isOk = true
+				return
+			}
+		}
+	}
+
+	// 图表详情
+	chartDetail, e := services.GetAiPredictChartDetailByData(indexItem, indexData, dataSource)
+	if e != nil {
+		err = fmt.Errorf("获取图表详情失败, %v", e)
+		return
+	}
+	resp.ChartInfo = chartDetail.ChartInfo
+	resp.EdbInfoList = chartDetail.EdbInfoList
+	resp.XEdbIdValue = chartDetail.XEdbIdValue
+	resp.YDataList = chartDetail.YDataList
+	resp.XDataList = chartDetail.XDataList
+	resp.BarChartInfo = chartDetail.BarChartInfo
+	resp.CorrelationChartInfo = chartDetail.CorrelationChartInfo
+	resp.DataResp = chartDetail.DataResp
+	resp.Status = true
+
+	if utils.Re == nil {
+		jsonData, _ := json.Marshal(resp)
+		_ = utils.Rc.Put(key, jsonData, 10*time.Minute)
+	}
+	isOk = true
 	return
 }

+ 81 - 0
models/ai_predict_model/ai_predict_model_data.go

@@ -0,0 +1,81 @@
+package data_manage
+
+import (
+	"database/sql"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	ModelDataSourceMonthly = 1 // 月度预测数据
+	ModelDataSourceDaily   = 2 // 日度预测数据
+)
+
+// AiPredictModelData AI预测模型标的数据
+type AiPredictModelData struct {
+	AiPredictModelDataId  int             `orm:"column(ai_predict_model_data_id);pk" gorm:"primaryKey"`
+	AiPredictModelIndexId int             `description:"标的ID"`
+	IndexCode             string          `description:"标的编码"`
+	DataTime              time.Time       `description:"数据日期"`
+	Value                 sql.NullFloat64 `description:"实际值"`
+	PredictValue          sql.NullFloat64 `description:"预测值"`
+	Direction             string          `description:"方向"`
+	DeviationRate         string          `description:"偏差率"`
+	CreateTime            time.Time       `description:"创建时间"`
+	ModifyTime            time.Time       `description:"修改时间"`
+	DataTimestamp         int64           `description:"数据日期时间戳"`
+	Source                int             `description:"来源:1-月度预测(默认);2-日度预测"`
+}
+
+func (m *AiPredictModelData) TableName() string {
+	return "ai_predict_model_data"
+}
+
+type AiPredictModelDataCols struct {
+	PrimaryId             string
+	AiPredictModelIndexId string
+	IndexCode             string
+	DataTime              string
+	Value                 string
+	PredictValue          string
+	Direction             string
+	DeviationRate         string
+	CreateTime            string
+	ModifyTime            string
+	DataTimestamp         string
+	Source                string
+}
+
+func (m *AiPredictModelData) Cols() AiPredictModelDataCols {
+	return AiPredictModelDataCols{
+		PrimaryId:             "ai_predict_model_data_id",
+		AiPredictModelIndexId: "ai_predict_model_index_id",
+		IndexCode:             "index_code",
+		DataTime:              "data_time",
+		Value:                 "value",
+		PredictValue:          "predict_value",
+		Direction:             "direction",
+		DeviationRate:         "deviation_rate",
+		CreateTime:            "create_time",
+		ModifyTime:            "modify_time",
+		DataTimestamp:         "data_timestamp",
+		Source:                "source",
+	}
+}
+
+func (m *AiPredictModelData) 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
+}

+ 130 - 0
models/ai_predict_model/ai_predict_model_framework.go

@@ -0,0 +1,130 @@
+package data_manage
+
+import (
+	"eta/eta_mobile/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// AiPredictModelFramework 图库框架表
+type AiPredictModelFramework struct {
+	AiPredictModelFrameworkId int       `orm:"column(ai_predict_model_framework_id);pk" gorm:"primaryKey"`
+	FrameworkCode             string    `description:"框架唯一编码"`
+	FrameworkName             string    `description:"框架名称"`
+	FrameworkImg              string    `description:"框架图片"`
+	FrameworkContent          string    `description:"框架内容"`
+	IsPublic                  int       `description:"是否公开:0-私有;1-公开"`
+	PublicTime                time.Time `description:"公开时间"`
+	Sort                      int       `description:"排序"`
+	AdminId                   int       `description:"创建人ID"`
+	AdminName                 string    `description:"创建人姓名"`
+	CreateTime                time.Time `description:"创建时间"`
+	ModifyTime                time.Time `description:"更新时间"`
+}
+
+func (m *AiPredictModelFramework) TableName() string {
+	return "ai_predict_model_framework"
+}
+
+func (m *AiPredictModelFramework) PrimaryId() string {
+	return AiPredictModelFrameworkColumns.AiPredictModelFrameworkId
+}
+
+var AiPredictModelFrameworkColumns = struct {
+	AiPredictModelFrameworkId string
+	FrameworkCode             string
+	FrameworkName             string
+	FrameworkImg              string
+	FrameworkContent          string
+	IsPublic                  string
+	PublicTime                string
+	Sort                      string
+	AdminId                   string
+	AdminName                 string
+	CreateTime                string
+	ModifyTime                string
+}{
+	AiPredictModelFrameworkId: "ai_predict_model_framework_id",
+	FrameworkCode:             "framework_code",
+	FrameworkName:             "framework_name",
+	FrameworkImg:              "framework_img",
+	FrameworkContent:          "framework_content",
+	IsPublic:                  "is_public",
+	PublicTime:                "public_time",
+	Sort:                      "sort",
+	AdminId:                   "admin_id",
+	AdminName:                 "admin_name",
+	CreateTime:                "create_time",
+	ModifyTime:                "modify_time",
+}
+
+func (m *AiPredictModelFramework) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*AiPredictModelFramework, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := `ORDER BY create_time DESC`
+	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
+}
+
+// AiPredictModelFrameworkItem 图库框架表信息
+type AiPredictModelFrameworkItem struct {
+	AiPredictModelFrameworkId int                                `description:"框架ID"`
+	FrameworkCode             string                             `description:"框架唯一编码"`
+	FrameworkName             string                             `description:"框架名称"`
+	FrameworkImg              string                             `description:"框架图片"`
+	FrameworkContent          string                             `description:"框架内容"`
+	IsPublic                  int                                `description:"是否公开:0-私有;1-公开"`
+	PublicTime                string                             `description:"公开时间"`
+	Sort                      int                                `description:"排序"`
+	AdminId                   int                                `description:"创建人ID"`
+	AdminName                 string                             `description:"创建人姓名"`
+	CreateTime                string                             `description:"创建时间"`
+	ModifyTime                string                             `description:"更新时间"`
+	Nodes                     []*AiPredictModelFrameworkNodeItem `description:"框架节点" gorm:"-"`
+	Button                    AiPredictModelFrameworkItemButton  `description:"操作按钮" gorm:"-"`
+}
+
+// AiPredictModelFrameworkItemButton 操作按钮
+type AiPredictModelFrameworkItemButton struct {
+	OpButton     bool `description:"是否可编辑"`
+	DeleteButton bool `description:"是否可删除"`
+	MoveButton   bool `description:"是否可移动"`
+}
+
+// FormatAiPredictModelFramework2Item 格式化框架信息
+func FormatAiPredictModelFramework2Item(origin *AiPredictModelFramework, nodes []*AiPredictModelFrameworkNodeItem) (item *AiPredictModelFrameworkItem) {
+	if origin == nil {
+		return
+	}
+	item = new(AiPredictModelFrameworkItem)
+	item.AiPredictModelFrameworkId = origin.AiPredictModelFrameworkId
+	item.FrameworkCode = origin.FrameworkCode
+	item.FrameworkName = origin.FrameworkName
+	item.FrameworkImg = origin.FrameworkImg
+	item.FrameworkContent = origin.FrameworkContent
+	item.IsPublic = origin.IsPublic
+	item.PublicTime = utils.TimeTransferString(utils.FormatDateTime, origin.PublicTime)
+	item.Sort = origin.Sort
+	item.AdminId = origin.AdminId
+	item.AdminName = origin.AdminName
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, origin.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, origin.ModifyTime)
+	item.Nodes = nodes
+	return
+}
+
+// AiPredictModelFrameworkPublicMenuItem 公开框架目录
+type AiPredictModelFrameworkPublicMenuItem struct {
+	AdminId    int                            `description:"创建人ID"`
+	MenuName   string                         `description:"目录名称"`
+	Frameworks []*AiPredictModelFrameworkItem `description:"框架列表"`
+}

+ 33 - 0
models/ai_predict_model/ai_predict_model_framework_node.go

@@ -0,0 +1,33 @@
+package data_manage
+
+import (
+	"time"
+)
+
+// AiPredictModelFrameworkNode 图库框架节点表
+type AiPredictModelFrameworkNode struct {
+	AiPredictModelFrameworkNodeId int       `orm:"column(ai_predict_model_framework_node_id);pk" gorm:"primaryKey"`
+	AiPredictModelFrameworkId     int       `description:"框架ID"`
+	FrameworkName                 string    `description:"框架名称"`
+	NodeId                        string    `description:"节点ID"`
+	NodeName                      string    `description:"节点名称"`
+	AiPredictModelIndexId         int       `description:"我的图表分类ID"`
+	CreateTime                    time.Time `description:"创建时间"`
+}
+
+func (m *AiPredictModelFrameworkNode) TableName() string {
+	return "ai_predict_model_framework_node"
+}
+
+// AiPredictModelFrameworkNodeItem 图库框架节点信息
+type AiPredictModelFrameworkNodeItem struct {
+	AiPredictModelFrameworkNodeId int
+	AiPredictModelFrameworkId     int    `description:"框架ID"`
+	FrameworkName                 string `description:"框架名称"`
+	NodeId                        string `description:"节点ID"`
+	NodeName                      string `description:"节点名称"`
+	AiPredictModelIndexId         int    `description:"模型标的ID"`
+	IndexName                     string `description:"模型标的名称"`
+	AiPredictModelNum             int    `description:"分类下的图表数"`
+	CreateTime                    string `description:"创建时间"`
+}

+ 96 - 0
models/ai_predict_model/ai_predict_model_index.go

@@ -0,0 +1,96 @@
+package data_manage
+
+import (
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"time"
+)
+
+// AiPredictModelIndex AI预测模型标的
+type AiPredictModelIndex struct {
+	AiPredictModelIndexId int       `orm:"column(ai_predict_model_index_id);pk" gorm:"primaryKey"`
+	IndexName             string    `description:"标的名称"`
+	IndexCode             string    `description:"自生成的指标编码"`
+	ClassifyId            int       `description:"分类ID"`
+	ModelFramework        string    `description:"模型框架"`
+	PredictDate           time.Time `description:"预测日期"`
+	PredictValue          float64   `description:"预测值"`
+	PredictFrequency      string    `description:"预测频度"`
+	DirectionAccuracy     string    `description:"方向准确度"`
+	AbsoluteDeviation     string    `description:"绝对偏差"`
+	ExtraConfig           string    `description:"模型参数"`
+	Sort                  int       `description:"排序"`
+	SysUserId             int       `description:"创建人ID"`
+	SysUserRealName       string    `description:"创建人姓名"`
+	LeftMin               string    `description:"图表左侧最小值"`
+	LeftMax               string    `description:"图表左侧最大值"`
+	CreateTime            time.Time `description:"创建时间"`
+	ModifyTime            time.Time `description:"修改时间"`
+}
+
+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) 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
+}
+
+type AiPredictModelIndexExtraConfig struct {
+	MonthlyChart struct {
+		LeftMin string `description:"图表左侧最小值"`
+		LeftMax string `description:"图表左侧最大值"`
+		Unit    string `description:"单位"`
+	}
+	DailyChart struct {
+		LeftMin           string `description:"图表左侧最小值"`
+		LeftMax           string `description:"图表左侧最大值"`
+		Unit              string `description:"单位"`
+		PredictLegendName string `description:"预测图例的名称(通常为Predicted)"`
+	}
+}

+ 6 - 0
models/data_manage/chart_edb_mapping.go

@@ -331,3 +331,9 @@ func GetChartMappingList(chartInfoId int) (list []*ChartEdbMapping, err error) {
 	_, err = o.Raw(sql, chartInfoId).QueryRows(&list)
 	return
 }
+
+func GetChartEdbMappingsByChartInfoId(chartInfoId int) (list []*ChartEdbInfoMapping, err error) {
+	sql := ` SELECT * FROM chart_edb_mapping AS a WHERE chart_info_id = ? ORDER BY chart_edb_mapping_id ASC`
+	_, err = orm.NewOrmUsingDB("data").Raw(sql, chartInfoId).QueryRows(&list)
+	return
+}

+ 9 - 0
models/data_manage/chart_info.go

@@ -2767,3 +2767,12 @@ type ExcelChartEdbView struct {
 	DataSequenceStr string `description:"数据序列选区"`
 	FromTag         string `description:"标签"`
 }
+
+// GetAiPredictChartInfoByIndexId 获取AI预测模型图表
+func GetAiPredictChartInfoByIndexId(source, indexId int) (item *ChartInfo, err error) {
+	sql := `SELECT * FROM chart_info WHERE chart_info_id = (
+		  SELECT chart_info_id FROM chart_edb_mapping WHERE source = ? AND edb_info_id = ?
+		) LIMIT 1`
+	err = orm.NewOrmUsingDB("data").Raw(sql, source, indexId).QueryRow(&item)
+	return
+}

+ 18 - 0
routers/commentsRouter.go

@@ -7,6 +7,24 @@ import (
 
 func init() {
 
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/ai_predict_model:AiPredictModelFrameworkController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/ai_predict_model:AiPredictModelFrameworkController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/framework/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/ai_predict_model:AiPredictModelIndexController"],
+        beego.ControllerComments{
+            Method: "SearchByEs",
+            Router: `/chart/search_by_es`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/correlation:CorrelationChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_mobile/controllers/data_manage/correlation:CorrelationChartClassifyController"],
         beego.ControllerComments{
             Method: "AddChartClassify",

+ 7 - 0
routers/router.go

@@ -10,6 +10,7 @@ package routers
 import (
 	"eta/eta_mobile/controllers"
 	"eta/eta_mobile/controllers/data_manage"
+	"eta/eta_mobile/controllers/data_manage/ai_predict_model"
 	"eta/eta_mobile/controllers/data_manage/correlation"
 	"eta/eta_mobile/controllers/data_manage/cross_variety"
 	"eta/eta_mobile/controllers/data_manage/data_manage_permission"
@@ -337,6 +338,12 @@ func init() {
 				&eta_forum.EtaForumController{},
 			),
 		),
+		web.NSNamespace("/ai_predict_model",
+			web.NSInclude(
+				&ai_predict_model.AiPredictModelIndexController{},
+				&ai_predict_model.AiPredictModelFrameworkController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 221 - 0
services/ai_predict_model_index.go

@@ -0,0 +1,221 @@
+package services
+
+import (
+	"encoding/json"
+	aiPredictModel "eta/eta_mobile/models/ai_predict_model"
+	"eta/eta_mobile/models/data_manage"
+	"eta/eta_mobile/services/data"
+	"eta/eta_mobile/utils"
+	"fmt"
+	"sort"
+	"time"
+)
+
+func GetAiPredictChartDetailByData(indexItem *aiPredictModel.AiPredictModelIndex, indexData []*aiPredictModel.AiPredictModelData, source int) (resp *data_manage.ChartInfoDetailResp, err error) {
+	resp = new(data_manage.ChartInfoDetailResp)
+
+	// 标的配置
+	var extraConfig aiPredictModel.AiPredictModelIndexExtraConfig
+	if indexItem.ExtraConfig != "" {
+		if e := json.Unmarshal([]byte(indexItem.ExtraConfig), &extraConfig); e != nil {
+			err = fmt.Errorf("标的额外配置解析失败, Config: %s, Err: %v", indexItem.ExtraConfig, e)
+			return
+		}
+	}
+
+	// 图表信息
+	var predictLegendName, confLeftMin, confLeftMax, unit string
+	if source == aiPredictModel.ModelDataSourceDaily {
+		predictLegendName = extraConfig.DailyChart.PredictLegendName
+		if predictLegendName == "" {
+			predictLegendName = "Predicted"
+		}
+		unit = extraConfig.DailyChart.Unit
+		confLeftMin = extraConfig.DailyChart.LeftMin
+		confLeftMax = extraConfig.DailyChart.LeftMax
+	}
+	if source == aiPredictModel.ModelDataSourceMonthly {
+		predictLegendName = "预测值"
+		unit = extraConfig.MonthlyChart.Unit
+		confLeftMin = extraConfig.MonthlyChart.LeftMin
+		confLeftMax = extraConfig.MonthlyChart.LeftMax
+	}
+	// 这里简单兼容下吧,暂时就不修数据了
+	if confLeftMin == "" {
+		confLeftMin = indexItem.LeftMin
+	}
+	if confLeftMax == "" {
+		confLeftMax = indexItem.LeftMax
+	}
+
+	// 获取指标对应的图表
+	chartSourceMapping := map[int]int{
+		aiPredictModel.ModelDataSourceMonthly: utils.CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY,
+		aiPredictModel.ModelDataSourceDaily:   utils.CHART_SOURCE_AI_PREDICT_MODEL_DAILY,
+	}
+	chartInfo, e := data_manage.GetAiPredictChartInfoByIndexId(chartSourceMapping[source], indexItem.AiPredictModelIndexId)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		err = fmt.Errorf("获取标的图表失败, %v", e)
+		return
+	}
+
+	// 获取曲线图主题样式
+	chartView := new(data_manage.ChartInfoView)
+	if chartInfo != nil && chartInfo.ChartInfoId > 0 {
+		chartView.ChartInfoId = chartInfo.ChartInfoId
+		chartView.ChartName = chartInfo.ChartName
+		chartView.ChartNameEn = chartInfo.ChartNameEn
+		chartView.Source = chartInfo.Source
+		chartView.ChartImage = chartInfo.ChartImage
+	} else {
+		chartView.ChartName = indexItem.IndexName
+		chartView.ChartNameEn = indexItem.IndexName
+	}
+	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预测模型"
+	chartView.Unit = unit
+	chartView.UnitEn = unit
+
+	// 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
+	edbActual.Unit = unit
+	edbActual.UnitEn = unit
+
+	edbPredict.EdbName = predictLegendName
+	edbPredict.EdbNameEn = predictLegendName
+	edbPredict.IsAxis = 1
+	edbPredict.Unit = unit
+	edbPredict.UnitEn = unit
+	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
+	var actualLatestTimestamp int64 // 实际值最后一天的时间戳,作为日度图表的分割线
+	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
+				actualLatestTimestamp = v.DataTime.UnixNano() / 1e6
+				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,
+			})
+		}
+	}
+
+	// 图表数据这里均做一个升序排序
+	sort.Slice(actualData, func(i, j int) bool {
+		return actualData[i].DataTimestamp < actualData[j].DataTimestamp
+	})
+	sort.Slice(predictData, func(i, j int) bool {
+		return predictData[i].DataTimestamp < predictData[j].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 confLeftMin != "" {
+		chartView.LeftMin = confLeftMin
+	} else {
+		leftMin := actualMin
+		if leftMin > predictMin {
+			leftMin = predictMin
+		}
+		chartView.LeftMin = fmt.Sprint(leftMin)
+	}
+	if confLeftMax != "" {
+		chartView.LeftMax = confLeftMax
+	} 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)
+
+	// 日度图表的分割线日期
+	if source == aiPredictModel.ModelDataSourceDaily {
+		var dataResp struct {
+			ActualLatestTimestamp int64
+		}
+		dataResp.ActualLatestTimestamp = actualLatestTimestamp
+		resp.DataResp = dataResp
+	}
+
+	resp.ChartInfo = chartView
+	resp.EdbInfoList = edbList
+	return
+}

+ 20 - 0
utils/common.go

@@ -2598,3 +2598,23 @@ func ConvertNumToBase62[T TUint](num T) string {
 
 	return string(result)
 }
+
+// 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
+}

+ 3 - 0
utils/constants.go

@@ -331,6 +331,9 @@ const (
 	CHART_SOURCE_CROSS_HEDGING                   = 10 // 跨品种分析图表
 	CHART_SOURCE_BALANCE_EXCEL                   = 11 // 平衡表图表
 	CHART_SOURCE_RANGE_ANALYSIS                  = 12 // 	区间分析图表
+	CHART_SOURCE_TRADE_ANALYSIS_PROCESS          = 13 // 持仓分析-建仓过程图表
+	CHART_SOURCE_AI_PREDICT_MODEL_DAILY          = 14 // AI预测模型图表-日度预测
+	CHART_SOURCE_AI_PREDICT_MODEL_MONTHLY        = 15 // AI预测模型图表-月度预测
 )
 
 // 批量配置图表的位置来源