package cross_variety import ( "errors" "eta/eta_chart_lib/models" cross_varietyModel "eta/eta_chart_lib/models/data_manage/cross_variety" "eta/eta_chart_lib/models/data_manage/cross_variety/request" "eta/eta_chart_lib/services/data" "eta/eta_chart_lib/utils" "fmt" "github.com/shopspring/decimal" "time" ) // ChartInfoResp 截面散点图数据 type ChartInfoResp struct { XName string `description:"x轴名称"` XNameEn string `description:"x轴名称(英文)"` XUnitName string `description:"x轴单位名称"` XUnitNameEn string `description:"x轴单位名称(英文)"` YName string `description:"y轴名称"` YNameEn string `description:"y轴名称(英文)"` YUnitName string `description:"y轴单位名称"` YUnitNameEn string `description:"y轴单位名称(英文)"` XMinValue string `description:"X轴的最小值"` XMaxValue string `description:"X轴的最大值"` YMinValue string `description:"Y轴的最小值"` YMaxValue string `description:"Y轴的最大值"` DataList []SectionScatterSeriesItemResp `description:"数据列"` } // SectionScatterSeriesItemResp 系列的返回 type SectionScatterSeriesItemResp struct { Name string `description:"系列名"` NameEn string `description:"系列名(英文)"` Color string `description:"颜色"` CoordinatePointData []CoordinatePoint `description:"趋势线的前后坐标点"` } // CoordinatePoint 坐标点 type CoordinatePoint struct { X float64 Y float64 XEdbInfoId int YEdbInfoId int XDate string YDate string DateType int `description:"日期类型:1-最新日期;2-N天前;3-固定日期"` DaysAgo int `description:"N天前的N值"` } // GetChartData // @Description: 获取跨品种分析图表数据 // @author: Roc // @datetime 2023-11-24 09:42:59 // @param chartInfoId int // @param config request.ChartConfigReq // @return edbList []*data_manage.ChartEdbInfoMapping // @return dataResp ChartInfoResp // @return err error // @return errMsg string // @return isSendEmail bool func GetChartData(chartInfoId int, config request.ChartConfigReq) (edbList []*models.ChartEdbInfoMapping, dataResp ChartInfoResp, err error, errMsg string, isSendEmail bool) { moveUnitDays, ok := utils.FrequencyDaysMap[config.CalculateUnit] if !ok { errMsg = "错误的分析周期" err = errors.New(errMsg) isSendEmail = false return } isSendEmail = true // 品种map varietyMap := make(map[int]*cross_varietyModel.ChartVariety) { varietyList, tmpErr := cross_varietyModel.GetVarietyListByIdList(config.VarietyList) if tmpErr != nil { err = tmpErr return } for _, v := range varietyList { varietyMap[v.ChartVarietyId] = v } } // 标签m var xTagInfo, yTagInfo *cross_varietyModel.ChartTag { tagList, tmpErr := cross_varietyModel.GetTagListByIdList([]int{config.TagX, config.TagY}) if tmpErr != nil { err = tmpErr return } for _, v := range tagList { if v.ChartTagId == config.TagX { xTagInfo = v } else if v.ChartTagId == config.TagY { yTagInfo = v } } } if xTagInfo == nil { errMsg = "找不到对应的X轴标签" err = errors.New(errMsg) return } if yTagInfo == nil { errMsg = "找不到对应的Y轴标签" err = errors.New(errMsg) return } xVarietyEdbMap, yVarietyEdbMap, edbInfoIdList, err := GetXYEdbIdList(config.TagX, config.TagY, config.VarietyList) if err != nil { return } if len(edbInfoIdList) <= 0 { errMsg = "品种未配置指标" err = errors.New(errMsg) isSendEmail = false return } mappingList, err := models.GetChartEdbMappingListByEdbInfoIdList(edbInfoIdList) if err != nil { errMsg = "获取指标信息失败" err = errors.New("获取指标信息失败,ERR:" + err.Error()) return } // 指标对应的所有数据 chartType := 1 //1:普通图,2:季节性图 calendar := "公历" edbDataListMap, edbList, err := data.GetEdbDataMapList(chartInfoId, chartType, calendar, "", "", mappingList, "") if err != nil { return } currDay := time.Now() currDay = time.Date(currDay.Year(), currDay.Month(), currDay.Day(), 0, 0, 0, 0, time.Local) dataMap := make(map[string]float64) dateMap := make(map[string]string) dateTypeMap := make(map[int]int) // 日期配置key对应的日期类型 daysAgoMap := make(map[int]int) // 日期配置key对应的N天前的N值 for dateIndex, dateConfig := range config.DateConfigList { dateTypeMap[dateIndex] = dateConfig.DateType daysAgoMap[dateIndex] = dateConfig.Num for _, edbInfoMapping := range mappingList { // 数据会是正序的 dataList, ok := edbDataListMap[edbInfoMapping.EdbInfoId] if !ok { continue } lenData := len(dataList) if lenData <= 0 { continue } // 数据的开始索引 k := lenData - 1 // 数据的最晚日期 dataEndDateStr := dataList[k].DataTime dataEndDate, tmpErr := time.ParseInLocation(utils.FormatDate, dataEndDateStr, time.Local) if tmpErr != nil { err = tmpErr return } // 数据开始日期 endDateStr := `` var endDate time.Time var currVal float64 switch dateConfig.DateType { case 1: // 1:最新日期; endDateStr = dataEndDateStr endDate = dataEndDate currVal = dataList[k].Value case 2: // 2:N天前(原为指标最新日期的N天前, 现为系统日期的N天) tmpEndDate := time.Now().AddDate(0, 0, -dateConfig.Num) tmpEndDateStr := tmpEndDate.Format(utils.FormatDate) for i := k; i >= 0; i-- { tmpDateStr := dataList[i].DataTime // 如果正好是这一天,那么就直接break了 if tmpEndDateStr == tmpDateStr { k = i endDateStr = tmpDateStr endDate = tmpEndDate currVal = dataList[i].Value break } tmpDate, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDateStr, time.Local) if tmpErr != nil { err = tmpErr return } // 如果这期的日期晚于选择的日期,那么继续遍历 if tmpDate.After(tmpEndDate) { continue } k = i endDateStr = tmpDateStr endDate = tmpDate currVal = dataList[i].Value break } case 3: // 固定日期 if dateConfig.FixDate == "" { errMsg = "固定日期不可为空" err = fmt.Errorf("固定日期为空") return } strFixDate := dateConfig.FixDate fixDate, e := time.ParseInLocation(utils.FormatDate, strFixDate, time.Local) if e != nil { errMsg = "固定日期格式有误" err = fmt.Errorf("固定日期有误, FixDate: %s", dateConfig.FixDate) return } for i := k; i >= 0; i-- { strThisDate := dataList[i].DataTime // 如果正好是这一天,那么就直接break了 if strFixDate == strThisDate { k = i endDateStr = strThisDate endDate = fixDate currVal = dataList[i].Value break } // 若固定日期无值, 则取固定日期之前, 能找到的第一个值(同上N天前的逻辑) thisDate, e := time.ParseInLocation(utils.FormatDate, strThisDate, time.Local) if e != nil { err = fmt.Errorf("数据日期格式有误: %s", e.Error()) return } if thisDate.After(fixDate) { continue } k = i endDateStr = strThisDate endDate = thisDate currVal = dataList[i].Value break } } // 没有找到日期,那么就不处理 if endDateStr == `` || endDate.IsZero() { continue } // 最早的日期 earliestDate := endDate.AddDate(0, 0, -config.CalculateValue*moveUnitDays) earliestDateStr := earliestDate.Format(utils.FormatDate) var percentVal float64 // 百分位计算值 // 百分位数据区间算法 if config.PercentType == utils.PercentCalculateTypeRange { var minVal, maxVal float64 var isNotFirst bool // 是否是第一条数据 for i := k; i >= 0; i-- { tmpData := dataList[i] if !isNotFirst { maxVal = tmpData.Value minVal = tmpData.Value isNotFirst = true continue } tmpDateStr := dataList[i].DataTime // 如果正好是这一天,那么就直接break了 if earliestDateStr == tmpDateStr { break } tmpDate, tmpErr := time.ParseInLocation(utils.FormatDate, tmpDateStr, time.Local) if tmpErr != nil { err = tmpErr return } // 如果这期的日期早于选择的日期,那么继续停止遍历 if tmpDate.Before(earliestDate) { continue } if tmpData.Value > maxVal { maxVal = tmpData.Value } if tmpData.Value < minVal { minVal = tmpData.Value } } // 最大值等于最小值,说明计算结果无效 if maxVal == minVal { continue } // 数据区间百分位=(现值-Min)/(Max-Min) tmpV := (currVal - minVal) / (maxVal - minVal) * 100 percentVal, _ = decimal.NewFromFloat(tmpV).Round(4).Float64() } // 百分位数据个数算法 // 数据区间第一个和最后一个数据点的时间和数据分别为(T1,S1)(T2,S2); N=T1到T2指标数据个数, n=小于等于S2的数据个数 // 个数百分位=(n-1)/(N-1) if config.PercentType == utils.PercentCalculateTypeNum { // T1为earliestDate, T2为endDate, S2为currVal var tinyN, bigN int lastVal := decimal.NewFromFloat(currVal) for i := k; i >= 0; i-- { date, e := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local) if e != nil { err = fmt.Errorf("数据日期格式有误: %s", e.Error()) return } if !date.Before(earliestDate) && !date.After(endDate) { bigN += 1 } dateVal := decimal.NewFromFloat(dataList[i].Value) if dateVal.LessThanOrEqual(lastVal) { tinyN += 1 } } // N=1时说明计算无效 if bigN == 1 { continue } numerator := decimal.NewFromInt(int64(tinyN - 1)) denominator := decimal.NewFromInt(int64(bigN - 1)) percentVal, _ = numerator.Div(denominator).Round(4).Float64() } // key的生成(日期配置下标+指标id) key := fmt.Sprint(dateIndex, "_", edbInfoMapping.EdbInfoId) dataMap[key] = percentVal dateMap[key] = endDateStr } } // 返回数据处理 dataList := make([]SectionScatterSeriesItemResp, 0) var xMinVal, xMaxVal, yMinVal, yMaxVal float64 var isNotFirst bool for _, varietyId := range config.VarietyList { xEdbInfoId, ok1 := xVarietyEdbMap[varietyId] if !ok1 { continue } yEdbInfoId, ok2 := yVarietyEdbMap[varietyId] if !ok2 { continue } variety, ok := varietyMap[varietyId] if !ok { continue } coordinatePointList := make([]CoordinatePoint, 0) for dateIndex, _ := range config.DateConfigList { key1 := fmt.Sprint(dateIndex, "_", xEdbInfoId) xVal, ok1 := dataMap[key1] if !ok1 { continue } key2 := fmt.Sprint(dateIndex, "_", yEdbInfoId) yVal, ok2 := dataMap[key2] if !ok2 { continue } if !isNotFirst { xMinVal = xVal xMaxVal = xVal yMinVal = yVal yMaxVal = yVal isNotFirst = true } else { if xVal < xMinVal { xMinVal = xVal } if xVal > xMaxVal { xMaxVal = xVal } if yVal < yMinVal { yMinVal = yVal } if yVal > yMaxVal { yMaxVal = yVal } } coordinatePointList = append(coordinatePointList, CoordinatePoint{ X: xVal, Y: yVal, XEdbInfoId: xEdbInfoId, YEdbInfoId: yEdbInfoId, XDate: dateMap[key1], YDate: dateMap[key2], DateType: dateTypeMap[dateIndex], // 日期类型 DaysAgo: daysAgoMap[dateIndex], // N天前的N值 }) } dataList = append(dataList, SectionScatterSeriesItemResp{ Name: variety.ChartVarietyName, NameEn: variety.ChartVarietyNameEn, Color: "", CoordinatePointData: coordinatePointList, }) } // 处理颜色 colorMap := utils.GetColorMap() for k, _ := range dataList { if c, ok1 := colorMap[k]; ok1 { dataList[k].Color = c } } dataResp = ChartInfoResp{ XName: xTagInfo.ChartTagName + "百分位", XNameEn: xTagInfo.ChartTagNameEn, XUnitName: "%", XUnitNameEn: "%", YName: yTagInfo.ChartTagName + "百分位", YNameEn: yTagInfo.ChartTagNameEn, YUnitName: "%", YUnitNameEn: "%", XMinValue: fmt.Sprint(xMinVal), XMaxValue: fmt.Sprint(xMaxVal), YMinValue: fmt.Sprint(yMinVal), YMaxValue: fmt.Sprint(yMaxVal), DataList: dataList, } // 去除返回指标中的数据信息,避免没必要的数据传输 for k, _ := range edbList { edbList[k].DataList = nil } return } // GetXYEdbIdList // @Description: 根据标签id和品种获取指标列表信息 // @author: Roc // @datetime 2023-11-27 14:31:23 // @param tagX int // @param tagY int // @param varietyList []int // @return xVarietyEdbMap map[int]int // @return yVarietyEdbMap map[int]int // @return edbInfoIdList []int // @return errMsg string // @return err error func GetXYEdbIdList(tagX, tagY int, varietyList []int) (xVarietyEdbMap, yVarietyEdbMap map[int]int, edbInfoIdList []int, err error) { edbInfoIdList = make([]int, 0) xVarietyEdbMap = make(map[int]int) yVarietyEdbMap = make(map[int]int) xList, err := cross_varietyModel.GetChartTagVarietyListByTagAndVariety(tagX, varietyList) if err != nil { err = errors.New("获取X轴的品种指标配置信息失败,Err:" + err.Error()) return } yList, err := cross_varietyModel.GetChartTagVarietyListByTagAndVariety(tagY, varietyList) if err != nil { err = errors.New("获取Y轴的品种指标配置信息失败,Err:" + err.Error()) return } baseVarietyIdMap := make(map[int]int) for _, v := range xList { baseVarietyIdMap[v.ChartVarietyId] = v.ChartVarietyId } // 两个标签里面的品种并集 needVarietyIdMap := make(map[int]int) for _, v := range yList { if val, ok := baseVarietyIdMap[v.ChartVarietyId]; ok { // 如果在 map2 中存在相同的键,则将键和值添加到结果中 needVarietyIdMap[v.ChartVarietyId] = val } } for _, v := range xList { if _, ok := needVarietyIdMap[v.ChartVarietyId]; ok { xVarietyEdbMap[v.ChartVarietyId] = v.EdbInfoId edbInfoIdList = append(edbInfoIdList, v.EdbInfoId) } } for _, v := range yList { if _, ok := needVarietyIdMap[v.ChartVarietyId]; ok { yVarietyEdbMap[v.ChartVarietyId] = v.EdbInfoId edbInfoIdList = append(edbInfoIdList, v.EdbInfoId) } } return }