소스 검색

品种价格、观点评价、结果统计

hsun 1 주 전
부모
커밋
0e15d56701

+ 148 - 9
controllers/assessment/assessment_form.go

@@ -289,9 +289,15 @@ func (this *AssessmentFormController) Detail() {
 		resp.List = append(resp.List, v.Format2Detail())
 	}
 
-	// TODO:品种价格详情
-
-	// TODO:观点评价
+	// 获取品种价格详情及观点评价
+	varietyPrice, forecastComment, e := services.GetVarietyPriceAndForecastComment(list)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取品种价格详情及观点评价失败, %v", e)
+		return
+	}
+	resp.VarietyPrice = varietyPrice
+	resp.ForecastComment = forecastComment
 
 	br.Data = resp
 	br.Ret = 200
@@ -348,13 +354,9 @@ func (this *AssessmentFormController) PageList() {
 		pars := make([]interface{}, 0)
 		pars = append(pars, sysUser.AdminId)
 		item, e := researcherOb.GetItemByCondition(cond, pars, "")
-		if e != nil {
-			if utils.IsErrNoRow(e) {
-				br.Msg = "您不是研究员,不可填报观点"
-				return
-			}
+		if e != nil && !utils.IsErrNoRow(e) {
 			br.Msg = "获取失败"
-			br.ErrMsg = "获取研究员失败"
+			br.ErrMsg = fmt.Sprintf("获取研究员失败, %v", e)
 			return
 		}
 		if item != nil && item.AssessmentResearcherId > 0 {
@@ -929,3 +931,140 @@ func (this *AssessmentFormController) Remove() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// ResultStatistic
+// @Title 结果统计
+// @Description 结果统计
+// @Param	request	body models.AssessmentFormResultStatisticReq true "type json string"
+// @Success 200 string "获取成功"
+// @router /form/result_statistic [get]
+func (this *AssessmentFormController) ResultStatistic() {
+	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
+	}
+	params := new(models.AssessmentFormResultStatisticReq)
+	if e := this.ParseForm(params); e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	params.AssessmentResearcherIds = strings.TrimSpace(params.AssessmentResearcherIds)
+	if params.AssessmentResearcherIds == "" {
+		br.Msg = "请选择研究员"
+		return
+	}
+	params.StartTime = strings.TrimSpace(params.StartTime)
+	params.EndTime = strings.TrimSpace(params.EndTime)
+	if params.StartTime == "" || params.EndTime == "" {
+		br.Msg = "请选择开始结束时间"
+		return
+	}
+	_, e := time.Parse(utils.FormatDate, params.StartTime)
+	if e != nil {
+		br.Msg = "开始时间格式有误"
+		return
+	}
+	ed, e := time.Parse(utils.FormatDate, params.EndTime)
+	if e != nil {
+		br.Msg = "结束时间格式有误"
+		return
+	}
+	parEndDate := ed.AddDate(0, 0, 28).Format(utils.FormatDate) // 实际填报单和数据的取值范围为结束日期+4周
+	resp := make([]*models.AssessmentFormResultStatisticItem, 0)
+
+	var researcherIds []int
+	arr := strings.Split(params.AssessmentResearcherIds, ",")
+	for _, v := range arr {
+		i, _ := strconv.Atoi(v)
+		if i > 0 {
+			researcherIds = append(researcherIds, i)
+		}
+	}
+	if len(researcherIds) == 0 {
+		br.Msg = "研究员有误"
+		br.ErrMsg = fmt.Sprintf("选择的研究员IDs有误, %s", params.AssessmentResearcherIds)
+		return
+	}
+
+	// 获取研究员在[开始日期至结束日期+4周]的填报单
+	forms := make([]*models.AssessmentForm, 0)
+	{
+		formOb := new(models.AssessmentForm)
+		cond := fmt.Sprintf(` AND %s = ? AND %s IN (?) AND (%s BETWEEN ? AND ?)`, formOb.Cols().Status, formOb.Cols().ResearcherId, formOb.Cols().BaseDate)
+		pars := make([]interface{}, 0)
+		pars = append(pars, models.AssessmentFormStatusSubmitted, params.AssessmentResearcherIds, params.StartTime, parEndDate)
+		list, e := formOb.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取填报单失败, %v", e)
+			return
+		}
+		forms = list
+	}
+	if len(forms) == 0 {
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	// 初始化响应结果,取出填报单关联的品种
+	var varietyIds []int
+	existVarietyId := make(map[int]bool)
+	existRespKey := make(map[string]bool)
+	for _, v := range forms {
+		if !existVarietyId[v.VarietyId] {
+			existVarietyId[v.VarietyId] = true
+			varietyIds = append(varietyIds, v.VarietyId)
+		}
+		k := fmt.Sprintf("%d-%d", v.ResearcherId, v.VarietyId)
+		if existRespKey[k] {
+			continue
+		}
+		t := new(models.AssessmentFormResultStatisticItem)
+		t.ResearcherId = v.ResearcherId
+		t.ResearcherAdminId = v.ResearcherAdminId
+		t.ResearcherName = v.ResearcherName
+		t.VarietyId = v.VarietyId
+		t.VarietyName = v.VarietyName
+		t.VarietyCode = v.VarietyCode
+		resp = append(resp, t)
+	}
+
+	// 并查询对应时间的品种数据
+	varietyData := make([]*models.AssessmentVarietyData, 0)
+	{
+		dataOb := new(models.AssessmentVarietyData)
+		cond := fmt.Sprintf(` AND %s IN (?) AND (%s BETWEEN ? AND ?)`, dataOb.Cols().VarietyId, dataOb.Cols().WeekDate)
+		pars := make([]interface{}, 0)
+		pars = append(pars, varietyIds, params.StartTime, parEndDate)
+		list, e := dataOb.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取品种数据失败, %v", e)
+			return
+		}
+		varietyData = list
+	}
+
+	// 计算正确率
+	resp, e = services.CalculateResultStatistic(forms, varietyData, resp)
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 72 - 25
controllers/assessment/assessment_researcher.go

@@ -446,30 +446,77 @@ func (this *AssessmentResearcherController) PageList() {
 	cond := fmt.Sprintf(` AND %s = ?`, researcherOb.Cols().Enabled)
 	pars := make([]interface{}, 0)
 	pars = append(pars, models.AssessmentResearcherEnabled)
-	if params.RealName != "" {
-		kw := fmt.Sprint("%", params.RealName, "%")
-		cond += fmt.Sprintf(` AND %s LIKE ?`, researcherOb.Cols().RealName)
-		pars = append(pars, kw)
-	}
-	if params.AssessmentResearcherIds != "" {
-		var researcherIds []int
-		arr := strings.Split(params.AssessmentResearcherIds, ",")
-		for _, v := range arr {
-			i, _ := strconv.Atoi(v)
-			if i > 0 {
-				researcherIds = append(researcherIds, i)
+	{
+		// 研究员姓名
+		if params.RealName != "" {
+			kw := fmt.Sprint("%", params.RealName, "%")
+			cond += fmt.Sprintf(` AND %s LIKE ?`, researcherOb.Cols().RealName)
+			pars = append(pars, kw)
+		}
+
+		// 研究员IDs
+		if params.AssessmentResearcherIds != "" {
+			var researcherIds []int
+			arr := strings.Split(params.AssessmentResearcherIds, ",")
+			for _, v := range arr {
+				i, _ := strconv.Atoi(v)
+				if i > 0 {
+					researcherIds = append(researcherIds, i)
+				}
+			}
+			if len(researcherIds) == 0 {
+				resp.Paging = paging.GetPaging(params.CurrentIndex, params.PageSize, 0)
+				br.Data = resp
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				return
 			}
+			cond += fmt.Sprintf(` AND %s IN (?)`, researcherOb.Cols().PrimaryId)
+			pars = append(pars, researcherIds)
 		}
-		if len(researcherIds) == 0 {
-			resp.Paging = paging.GetPaging(params.CurrentIndex, params.PageSize, 0)
-			br.Data = resp
-			br.Ret = 200
-			br.Success = true
-			br.Msg = "获取成功"
-			return
+
+		// 仅获取自己有权限的,与研究员IDs条件冲突
+		if params.AssessmentResearcherIds == "" && params.ResearcherAuthType > 0 {
+			// 自己如果是研究员的话要把自己也加进去
+			var researcherIds []int
+			condResearcher := fmt.Sprintf(` AND %s = ?`, researcherOb.Cols().AdminId)
+			parsResearcher := make([]interface{}, 0)
+			parsResearcher = append(parsResearcher, sysUser.AdminId)
+			researcher, e := researcherOb.GetItemByCondition(condResearcher, parsResearcher, "")
+			if e != nil && !utils.IsErrNoRow(e) {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取研究员失败, %v", e)
+				return
+			}
+			if researcher != nil && researcher.AssessmentResearcherId > 0 {
+				researcherIds = append(researcherIds, researcher.AssessmentResearcherId)
+			}
+
+			mappingOb := new(models.AssessmentResearcherAdminMapping)
+			condMapping := fmt.Sprintf(` AND %s = ? AND %s = ?`, mappingOb.Cols().AuthAdminId, mappingOb.Cols().AuthType)
+			parsMapping := make([]interface{}, 0)
+			parsMapping = append(parsMapping, sysUser.AdminId, params.ResearcherAuthType)
+			list, e := mappingOb.GetItemsByCondition(condMapping, parsMapping, []string{}, "")
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取用户有权限的研究员失败, %v", e)
+				return
+			}
+			for _, v := range list {
+				researcherIds = append(researcherIds, v.AssessmentResearcherId)
+			}
+			if len(researcherIds) == 0 {
+				resp.Paging = paging.GetPaging(params.CurrentIndex, params.PageSize, 0)
+				br.Data = resp
+				br.Ret = 200
+				br.Success = true
+				br.Msg = "获取成功"
+				return
+			}
+			cond += fmt.Sprintf(` AND %s IN (?)`, researcherOb.Cols().PrimaryId)
+			pars = append(pars, researcherIds)
 		}
-		cond += fmt.Sprintf(` AND %s IN (?)`, researcherOb.Cols().PrimaryId)
-		pars = append(pars, researcherIds)
 	}
 
 	// 分页列表
@@ -516,10 +563,10 @@ func (this *AssessmentResearcherController) PageList() {
 	viewAdminMappings, assessmentAdminMappings := make(map[int][]models.AssessmentResearcherDetail), make(map[int][]models.AssessmentResearcherDetail)
 	{
 		mappingOb := new(models.AssessmentResearcherAdminMapping)
-		cond := fmt.Sprintf(` AND %s IN (?)`, mappingOb.Cols().AdminId)
-		pars := make([]interface{}, 0)
-		pars = append(pars, adminIds)
-		list, e := mappingOb.GetItemsByCondition(cond, pars, []string{}, "")
+		condMapping := fmt.Sprintf(` AND %s IN (?)`, mappingOb.Cols().AdminId)
+		parsMapping := make([]interface{}, 0)
+		parsMapping = append(parsMapping, adminIds)
+		list, e := mappingOb.GetItemsByCondition(condMapping, parsMapping, []string{}, "")
 		if e != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = fmt.Sprintf("获取研究员用户权限关系失败, %v", e)

+ 5 - 5
controllers/base_auth.go

@@ -252,11 +252,11 @@ func (c *BaseAuthController) Prepare() {
 			for _, s := range apis {
 				apiMap[s] = true
 			}
-			//if !apiMap[uri] {
-			//	c.JSON(models.BaseResponse{Ret: 403, Msg: "无权访问!", ErrMsg: "无权访问!"}, false, false)
-			//	c.StopRun()
-			//	return
-			//}
+			if !apiMap[uri] {
+				c.JSON(models.BaseResponse{Ret: 403, Msg: "无权访问!", ErrMsg: "无权访问!"}, false, false)
+				c.StopRun()
+				return
+			}
 		} else {
 			c.JSON(models.BaseResponse{Ret: 408, Msg: "请求异常,请联系客服!", ErrMsg: "POST之外的请求,暂不支持"}, false, false)
 			c.StopRun()

+ 48 - 16
models/assessment_form.go

@@ -12,6 +12,14 @@ import (
 const (
 	AssessmentFormStatusDraft     = 0
 	AssessmentFormStatusSubmitted = 1
+
+	AssessmentFormMonthlyPriceUp    = "涨"
+	AssessmentFormMonthlyPriceDown  = "跌"
+	AssessmentFormMonthlyPriceShake = "震荡"
+	AssessmentFormWeekUpYes         = "是"
+	AssessmentFormWeekUpNo          = "否"
+	AssessmentFormWeekDownYes       = "是"
+	AssessmentFormWeekDownNo        = "否"
 )
 
 // AssessmentForm 研究员考核填报单
@@ -295,26 +303,29 @@ type AssessmentFormViewResp struct {
 
 // AssessmentFormVarietyPrice 品种价格详情
 type AssessmentFormVarietyPrice struct {
-	VarietyId      int     `description:"品种ID"`
-	VarietyCode    string  `description:"品种编码"`
-	VarietyName    string  `description:"品种名称"`
-	EndDate        string  `description:"最新日期"`
-	LatestValue    float64 `description:"最新价格"`
-	BaseDatePrice  float64 `description:"基准日收盘价"`
-	NextWeekPrice  float64 `description:"N+1周价格"`
-	NextMonthPrice float64 `description:"N+4周价格"`
+	VarietyId      int    `description:"品种ID"`
+	VarietyCode    string `description:"品种编码"`
+	VarietyName    string `description:"品种名称"`
+	EndDate        string `description:"最新日期"`
+	LatestValue    string `description:"最新价格"`
+	BaseDatePrice  string `description:"基准日收盘价"`
+	NextWeekPrice  string `description:"N+1周价格"`
+	NextMonthPrice string `description:"N+4周价格"`
 }
 
 // AssessmentFormForecastComment 观点评价
 type AssessmentFormForecastComment struct {
-	VarietyId           int    `description:"品种ID"`
-	VarietyCode         string `description:"品种编码"`
-	VarietyName         string `description:"品种名称"`
-	WeekTime            string `description:"周度(格式:202501,202502)"`
-	SubmitTime          string `description:"填报日期"`
-	MonthlyPriceComment string `description:"月度涨跌评价"`
-	WeeklyUpComment     string `description:"周度上行风险评价"`
-	WeeklyDownComment   string `description:"周度下行风险评价"`
+	VarietyId                 int    `description:"品种ID"`
+	VarietyCode               string `description:"品种编码"`
+	VarietyName               string `description:"品种名称"`
+	WeekTime                  string `description:"周度(格式:202501,202502)"`
+	SubmitTime                string `description:"填报日期"`
+	MonthlyPriceComment       string `description:"月度涨跌评价"`
+	MonthlyPriceForecastRight bool   `description:"月度涨跌判断是否正确"`
+	WeeklyUpComment           string `description:"周度上行风险评价"`
+	WeeklyUpForecastRight     bool   `description:"周度上行风险判断是否正确"`
+	WeeklyDownComment         string `description:"周度下行风险评价"`
+	WeeklyDownForecastRight   bool   `description:"周度下行风险判断是否正确"`
 }
 
 // AssessmentFormCheckAddResp 新建填报权限响应
@@ -323,3 +334,24 @@ type AssessmentFormCheckAddResp struct {
 	HasVariety   bool `description:"是否绑定品种"`
 	NewForm      bool `description:"是否可新建本周填报"`
 }
+
+// AssessmentFormResultStatisticReq 填报单结果统计请求
+type AssessmentFormResultStatisticReq struct {
+	AssessmentResearcherIds string `form:"AssessmentResearcherIds" description:"研究员IDs"`
+	StartTime               string `form:"StartTime" description:"开始时间"`
+	EndTime                 string `form:"EndTime" description:"结束时间"`
+}
+
+// AssessmentFormResultStatisticItem 填报单结果统计
+type AssessmentFormResultStatisticItem struct {
+	ResearcherId          int     `description:"研究员ID"`
+	ResearcherAdminId     int     `description:"研究员用户ID"`
+	ResearcherName        string  `description:"研究员姓名"`
+	VarietyId             int     `description:"品种ID"`
+	VarietyCode           string  `description:"品种编码"`
+	VarietyName           string  `description:"品种名称"`
+	MonthlyTrendAccuracy  float64 `description:"月度趋势正确率"`
+	WeeklyWarningAccuracy float64 `description:"周度预警正确率"`
+	TotalAccuracyA        float64 `description:"综合正确率1"`
+	TotalAccuracyB        float64 `description:"综合正确率2"`
+}

+ 1 - 0
models/assessment_researcher.go

@@ -164,6 +164,7 @@ type AssessmentResearcherPageListReq struct {
 	CurrentIndex            int    `form:"CurrentIndex"`
 	RealName                string `form:"RealName" description:"用户姓名"`
 	AssessmentResearcherIds string `form:"AssessmentResearcherIds" description:"研究员IDs"`
+	ResearcherAuthType      int    `form:"ResearcherAuthType" description:"权限类型:0-全部;1-当前用户有查看权限的;2-当前用户有统计权限的"`
 }
 
 // AssessmentResearcherPageListResp 研究员列表响应

+ 17 - 0
models/assessment_variety_data.go

@@ -131,3 +131,20 @@ func (m *AssessmentVarietyData) GetItemsByCondition(condition string, pars []int
 	err = global.DEFAULT_DB.Raw(sql, pars...).Find(&items).Error
 	return
 }
+
+// GetVarietyMaxDateData 获取品种最新数据
+func (m *AssessmentVarietyData) GetVarietyMaxDateData(varietyIds []int) (items []*AssessmentVarietyData, err error) {
+	if len(varietyIds) == 0 {
+		return
+	}
+	sql := `SELECT a.*
+		FROM assessment_variety_data a
+		INNER JOIN (
+			SELECT variety_id, MAX(week_date) AS max_week_date
+			FROM assessment_variety_data
+			WHERE variety_id IN (?)
+			GROUP BY variety_id
+		) b ON a.variety_id = b.variety_id AND a.week_date = b.max_week_date`
+	err = global.DEFAULT_DB.Raw(sql, varietyIds).Find(&items).Error
+	return
+}

+ 9 - 0
routers/commentsRouter.go

@@ -286,6 +286,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/assessment:AssessmentFormController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/assessment:AssessmentFormController"],
+        beego.ControllerComments{
+            Method: "ResultStatistic",
+            Router: `/form/result_statistic`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/assessment:AssessmentFormController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/assessment:AssessmentFormController"],
         beego.ControllerComments{
             Method: "Save",

+ 339 - 0
services/assessment_form.go

@@ -4,6 +4,7 @@ import (
 	"eta/eta_api/models"
 	"eta/eta_api/utils"
 	"fmt"
+	"math"
 	"sync"
 	"time"
 )
@@ -175,3 +176,341 @@ func CheckAssessmentFormButton(item *models.AssessmentForm, sysAdminId int, auth
 	}
 	return
 }
+
+// GetVarietyPriceAndForecastComment 获取品种价格详情及观点评价
+func GetVarietyPriceAndForecastComment(forms []*models.AssessmentForm) (varietyPrice []*models.AssessmentFormVarietyPrice, forecastComment []*models.AssessmentFormForecastComment, err error) {
+	if len(forms) == 0 {
+		return
+	}
+	baseDate := forms[0].BaseDate
+	var varietyIds []int
+	for _, v := range forms {
+		varietyIds = append(varietyIds, v.VarietyId)
+	}
+	if len(varietyIds) == 0 || baseDate.IsZero() {
+		return
+	}
+
+	// 获取品种信息
+	varietyOb := new(models.AssessmentVariety)
+	varietyMatch := make(map[int]*models.AssessmentVariety)
+	{
+		cond := fmt.Sprintf(` AND %s IN (?)`, varietyOb.Cols().PrimaryId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, varietyIds)
+		list, e := varietyOb.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取品种信息失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			varietyMatch[v.AssessmentVarietyId] = v
+		}
+	}
+
+	varietyLatestData := make(map[int]*models.AssessmentVarietyData)
+	varietyBaseDateData := make(map[int]*models.AssessmentVarietyData)
+	varietyNextWeekData := make(map[int]*models.AssessmentVarietyData)
+	varietyNextMonthData := make(map[int]*models.AssessmentVarietyData)
+
+	// 获取最新日期和数据
+	dataOb := new(models.AssessmentVarietyData)
+	latestData, e := dataOb.GetVarietyMaxDateData(varietyIds)
+	if e != nil {
+		err = fmt.Errorf("获取品种最新数据失败, %v", e)
+		return
+	}
+	for _, v := range latestData {
+		varietyLatestData[v.VarietyId] = v
+	}
+
+	// 获取基准日期N至N+4周的数据
+	var dateArr []string
+	nextWeek := baseDate.AddDate(0, 0, 7)
+	nextMonth := baseDate.AddDate(0, 0, 28)
+	dateArr = append(dateArr, baseDate.Format(utils.FormatDate), nextWeek.Format(utils.FormatDate), nextMonth.Format(utils.FormatDate))
+	{
+		cond := fmt.Sprintf(` AND %s IN (?) AND %s IN (?)`, dataOb.Cols().VarietyId, dataOb.Cols().WeekDate)
+		pars := make([]interface{}, 0)
+		pars = append(pars, varietyIds, dateArr)
+		list, e := dataOb.GetItemsByCondition(cond, pars, []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取基准日至N+4周数据失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			if varietyMatch[v.VarietyId] == nil {
+				continue
+			}
+			if baseDate.Equal(v.WeekDate) {
+				varietyBaseDateData[v.VarietyId] = v
+				continue
+			}
+			if baseDate.AddDate(0, 0, 7).Equal(v.WeekDate) {
+				varietyNextWeekData[v.VarietyId] = v
+				continue
+			}
+			if baseDate.AddDate(0, 0, 28).Equal(v.WeekDate) {
+				varietyNextMonthData[v.VarietyId] = v
+				continue
+			}
+		}
+	}
+
+	varietyPrice = make([]*models.AssessmentFormVarietyPrice, 0)
+	forecastComment = make([]*models.AssessmentFormForecastComment, 0)
+	for _, v := range forms {
+		vat := varietyMatch[v.VarietyId]
+		if vat == nil {
+			utils.FileLog.Info(fmt.Sprintf("GetVarietyPriceAndForecastComment, 品种不存在: %d", v.VarietyId))
+			continue
+		}
+
+		// 品种价格评价
+		vp := new(models.AssessmentFormVarietyPrice)
+		vp.VarietyId = v.VarietyId
+		vp.VarietyCode = v.VarietyCode
+		vp.VarietyName = v.VarietyName
+		if varietyLatestData[v.VarietyId] != nil {
+			vp.EndDate = varietyLatestData[v.VarietyId].WeekDate.Format(utils.FormatDate)
+			vp.LatestValue = fmt.Sprint(varietyLatestData[v.VarietyId].CloseValue)
+		}
+		var (
+			baseDataPrice     *float64
+			nextMonthPrice    *float64
+			nextWeekHighPrice *float64
+			nextWeekLowPrice  *float64
+		)
+		if varietyBaseDateData[v.VarietyId] != nil {
+			baseDataPrice = &varietyBaseDateData[v.VarietyId].CloseValue
+			vp.BaseDatePrice = fmt.Sprint(varietyBaseDateData[v.VarietyId].CloseValue)
+		}
+		if varietyNextWeekData[v.VarietyId] != nil {
+			nextWeekHighPrice = &varietyNextWeekData[v.VarietyId].HighValue
+			nextWeekLowPrice = &varietyNextWeekData[v.VarietyId].LowValue
+			vp.NextWeekPrice = fmt.Sprint(varietyNextWeekData[v.VarietyId].CloseValue)
+		}
+		if varietyNextMonthData[v.VarietyId] != nil {
+			nextMonthPrice = &varietyNextMonthData[v.VarietyId].CloseValue
+			vp.NextMonthPrice = fmt.Sprint(varietyNextMonthData[v.VarietyId].CloseValue)
+		}
+		varietyPrice = append(varietyPrice, vp)
+
+		// 观点评价
+		fc := new(models.AssessmentFormForecastComment)
+		fc.VarietyId = v.VarietyId
+		fc.VarietyCode = v.VarietyCode
+		fc.VarietyName = v.VarietyName
+		fc.WeekTime = v.WeekTime
+		if !v.SubmitTime.IsZero() {
+			fc.SubmitTime = v.SubmitTime.Format(utils.FormatDateUnSpace)
+		}
+
+		// 月度涨跌评价
+		if baseDataPrice != nil && nextMonthPrice != nil {
+			_, tips, right := calculateMonthlyPriceTrend(*baseDataPrice, *nextMonthPrice, vat.MonthlyFluctuate, v.MonthlyPriceForecast)
+			fc.MonthlyPriceComment = tips
+			fc.MonthlyPriceForecastRight = right
+		}
+
+		// 周度上下行风险
+		if baseDataPrice != nil && nextWeekHighPrice != nil {
+			_, tips, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekHighPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, true)
+			fc.WeeklyUpComment = tips
+			fc.WeeklyUpForecastRight = right
+		}
+		if baseDataPrice != nil && nextWeekLowPrice != nil {
+			_, tips, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekLowPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, false)
+			fc.WeeklyDownComment = tips
+			fc.WeeklyDownForecastRight = right
+		}
+		forecastComment = append(forecastComment, fc)
+	}
+	return
+}
+
+// calculateMonthly 判断月度涨跌趋势
+func calculateMonthlyPriceTrend(basePrice, monthPrice, monthlyFluctuate float64, forecast string) (result, tips string, right bool) {
+	if basePrice <= 0 || monthPrice <= 0 || monthlyFluctuate <= 0 {
+		return
+	}
+	// 计算月度价格变化比例:月度价格/当周价格-1
+	percent := (monthPrice/basePrice - 1) * 100
+
+	// 判断价格趋势
+	switch {
+	case percent > monthlyFluctuate:
+		result = models.AssessmentFormMonthlyPriceUp
+	case percent < -monthlyFluctuate:
+		result = models.AssessmentFormMonthlyPriceDown
+	default:
+		result = models.AssessmentFormMonthlyPriceShake
+	}
+
+	// 如果有进行预测,判断是否正确,返回提示语句
+	if forecast == "" {
+		return
+	}
+	tips = fmt.Sprintf("判断 %s,实际 %s", forecast, result)
+	if forecast == result {
+		right = true
+	}
+	return
+}
+
+// calculateWeekUpDownTrend 判断周度上下行风险
+func calculateWeekUpDownTrend(basePrice, weekPrice, weekFluctuate float64, forecast string, calculateUp bool) (result, tips string, right bool) {
+	if basePrice <= 0 || weekPrice <= 0 || weekFluctuate <= 0 {
+		return
+	}
+	percent := (weekPrice/basePrice - 1) * 100
+
+	// 上下行
+	result = models.AssessmentFormWeekUpNo
+	if calculateUp {
+		if percent > weekFluctuate {
+			result = models.AssessmentFormWeekUpYes
+		}
+	} else {
+		if percent < -weekFluctuate {
+			result = models.AssessmentFormWeekUpYes
+		}
+	}
+
+	// 如果有进行预测,判断是否正确,返回提示语句
+	if forecast == "" {
+		return
+	}
+	if forecast == result {
+		right = true
+	}
+	forecastMap := map[string]string{models.AssessmentFormWeekUpYes: "提示风险", models.AssessmentFormWeekUpNo: "未提示风险"}
+	resultMap := map[string]string{models.AssessmentFormWeekUpYes: "风险发生", models.AssessmentFormWeekUpNo: "风险未发生"}
+	tips = fmt.Sprint(forecastMap[forecast], " ", resultMap[result])
+	return
+}
+
+func CalculateResultStatistic(forms []*models.AssessmentForm, varietyData []*models.AssessmentVarietyData, results []*models.AssessmentFormResultStatisticItem) (resp []*models.AssessmentFormResultStatisticItem, err error) {
+	if len(forms) == 0 {
+		return
+	}
+	calculateMappingQ := make(map[string]int) // 月度趋势分子Q(即月度涨跌趋势判断正确的次数)
+	calculateMappingP := make(map[string]int) // 月度趋势分母P(即月度涨跌趋势判断总次数)
+	calculateMappingT := make(map[string]int) // 周度风险分子T(即周度上下行风险判断正确的次数)
+	calculateMappingS := make(map[string]int) // 周度风险分母S(即周度上下行风险判断的总次数)
+
+	// 品种数据
+	varietyDateData := make(map[string]*models.AssessmentVarietyData)
+	for _, v := range varietyData {
+		k := fmt.Sprintf("%d-%s", v.VarietyId, v.WeekDate.Format(utils.FormatDate))
+		varietyDateData[k] = v
+	}
+
+	// 获取品种信息
+	varietyMatch := make(map[int]*models.AssessmentVariety)
+	{
+		varietyOb := new(models.AssessmentVariety)
+		list, e := varietyOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
+		if e != nil {
+			err = fmt.Errorf("获取品种信息失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			varietyMatch[v.AssessmentVarietyId] = v
+		}
+	}
+
+	// 对填报单的[研究员ID-品种ID]进行QPTS的计数
+	for _, v := range forms {
+		vat := varietyMatch[v.VarietyId]
+		if vat == nil {
+			utils.FileLog.Info(fmt.Sprintf("CalculateResultStatistic, 品种不存在: %d", v.VarietyId))
+			continue
+		}
+		key := fmt.Sprintf("%d-%d", v.ResearcherId, v.VarietyId)
+
+		// 找出填报单品种对应的基准日期数据、N+1周的最高最低价格、N+4周的价格
+		var (
+			baseDataPrice     *float64
+			nextMonthPrice    *float64
+			nextWeekHighPrice *float64
+			nextWeekLowPrice  *float64
+		)
+		kb := fmt.Sprintf("%d-%s", v.VarietyId, v.BaseDate.Format(utils.FormatDate))
+		if varietyDateData[kb] != nil {
+			baseDataPrice = &varietyDateData[kb].CloseValue
+		}
+		kw := fmt.Sprintf("%d-%s", v.VarietyId, v.BaseDate.AddDate(0, 0, 7).Format(utils.FormatDate))
+		if varietyDateData[kw] != nil {
+			nextWeekHighPrice = &varietyDateData[kw].HighValue
+			nextWeekLowPrice = &varietyDateData[kw].LowValue
+		}
+		km := fmt.Sprintf("%d-%s", v.VarietyId, v.BaseDate.AddDate(0, 0, 28).Format(utils.FormatDate))
+		if varietyDateData[km] != nil {
+			nextMonthPrice = &varietyDateData[km].CloseValue
+		}
+
+		// 月度涨跌评价
+		if baseDataPrice != nil && nextMonthPrice != nil {
+			_, _, right := calculateMonthlyPriceTrend(*baseDataPrice, *nextMonthPrice, vat.MonthlyFluctuate, v.MonthlyPriceForecast)
+			calculateMappingP[key] += 1
+			if right {
+				calculateMappingQ[key] += 1
+			}
+		}
+
+		// 周度上下行风险
+		if baseDataPrice != nil && nextWeekHighPrice != nil {
+			_, _, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekHighPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, true)
+			calculateMappingS[key] += 1
+			if right {
+				calculateMappingT[key] += 1
+			}
+		}
+		if baseDataPrice != nil && nextWeekLowPrice != nil {
+			_, _, right := calculateWeekUpDownTrend(*baseDataPrice, *nextWeekLowPrice, vat.WeeklyFluctuate, v.WeeklyUpForecast, false)
+			calculateMappingS[key] += 1
+			if right {
+				calculateMappingT[key] += 1
+			}
+		}
+	}
+
+	// 计算正确率,结果取整
+	for _, v := range results {
+		k := fmt.Sprintf("%d-%d", v.ResearcherId, v.VarietyId)
+		// 月趋势正确率:Q/P
+		q := calculateMappingQ[k]
+		p := calculateMappingP[k]
+		if p > 0 {
+			v.MonthlyTrendAccuracy = math.Round(float64(q) / float64(p) * 100)
+		}
+
+		// 周度预警正确率:T/S
+		t := calculateMappingT[k]
+		s := calculateMappingS[k]
+		if s > 0 {
+			v.WeeklyWarningAccuracy = math.Round(float64(t) / float64(s) * 100)
+		}
+
+		// 综合正确率1:(Q+2T)/(P+2S)
+		a := q + 2*t
+		b := p + 2*s
+		if b > 0 {
+			v.TotalAccuracyA = math.Round(float64(a) / float64(b) * 100)
+		}
+
+		// 综合正确率2:Q/2P+T/2S
+		var c, d float64
+		if p > 0 {
+			c = math.Round(float64(q) / float64(2*p) * 100)
+		}
+		if s > 0 {
+			d = math.Round(float64(t) / float64(2*s) * 100)
+		}
+		v.TotalAccuracyB = c + d
+	}
+
+	resp = results
+	return
+}