Explorar o código

Merge branch 'master' into feature/v1.0.29

yuelujie hai 5 meses
pai
achega
b1bf2413e1

+ 965 - 0
marketanalysis/common.go

@@ -0,0 +1,965 @@
+package marketanalysis
+
+import (
+	"app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/encrypt"
+	elastic "app.yhyue.com/moapp/jybase/es"
+	"app.yhyue.com/moapp/jybase/mongodb"
+	"encoding/json"
+	"fmt"
+	esV7 "github.com/olivere/elastic/v7"
+	"log"
+	"math"
+	"strings"
+	"sync"
+	"time"
+)
+
+// 中标单位分析
+func WinningAnalysis(thisWinnerRow BuyerWinnerRow, rMap *sync.Map, entArrMap map[string]float64, winnerName map[string]string, sy *sync.WaitGroup, offline bool) {
+	defer sy.Done()
+	type s_Winner struct {
+		Name        string      `json:"key"`
+		TCount      int         `json:"t_count"`  //项目数量
+		TAmount     float64     `json:"t_amount"` //项目金额
+		TotalAmount interface{} `json:"total_amount"`
+		TotalNumber interface{} `json:"total_number"`
+	}
+	//中标单位-中标规模分布
+	winnerA := make(map[string]*distributionTrend)
+	for _, v := range thisWinnerRow.WinnerAmountDistribution.EntidlistTerms.Buckets {
+		if vs, ok := entArrMap[v.Key]; ok {
+			v.Amount.Value += vs
+			delete(entArrMap, v.Key)
+		}
+		amountDistribution(v.Amount.Value, winnerA)
+	}
+	for _, f := range entArrMap {
+		amountDistribution(f, winnerA)
+	}
+	var (
+		countAll  int
+		amountAll float64
+	)
+	for _, v := range winnerA {
+		countAll += v.Count
+		amountAll += v.Amount
+	}
+	buyerMap := []interface{}{}
+	for _, v := range Analysis {
+		var data s_Winner
+		data.Name = v
+		vlu, ok := winnerA[v]
+		if ok {
+			data.TCount = vlu.Count
+			data.TAmount = vlu.Amount
+			if countAll != 0 {
+				data.TotalNumber = float64(vlu.Count) / float64(countAll)
+			}
+			if amountAll != 0 {
+				data.TotalAmount = vlu.Amount / amountAll
+			}
+		}
+		buyerMap = append(buyerMap, data)
+	}
+
+	type number_projects struct {
+		Id     interface{} `json:"id"`
+		Name   string      `json:"name"`
+		Number int64       `json:"number"`
+		//Accounted interface{} `json:"accounted"`
+		Data interface{} `json:"buyertop3"`
+	}
+	type number_project struct {
+		Name   string `json:"name"`
+		Number int64  `json:"number"`
+		//Accounted interface{} `json:"accounted"`
+	}
+
+	//中标单位-项目数量TOP3
+	countMap := []interface{}{}
+	for _, v := range thisWinnerRow.WinnerCountTop3.SWinnerCount {
+		var _d number_projects
+		_d.Number = v.BuyerCount.Value
+		if winnerName[v.Key] == "" {
+			continue
+		}
+		_d.Name = winnerName[v.Key]
+		_d.Id = encodeId(v.Key)
+		/*if thisWinnerRow.ProjectCount.Value != 0 {
+			_d.Accounted = float64(v.BuyerCount.Value) / float64(thisWinnerRow.ProjectCount.Value)
+		}*/
+
+		var ss []interface{}
+		for _, v1 := range v.BuyerTop.Buckets {
+			var _dd number_project
+			_dd.Name = v1.Key
+			_dd.Number = v1.BuyerWinnerAmount.Value
+			/*if _d.Number != 0 {
+				_dd.Accounted = float64(v1.BuyerWinnerAmount.Value) / float64(_d.Number)
+			}*/
+			ss = append(ss, _dd)
+		}
+		_d.Data = ss
+		if !offline {
+			if len(ss) > Top3 {
+				_d.Data = ss[:Top3]
+			}
+			if len(countMap) >= Top30 {
+				break
+			}
+		}
+		countMap = append(countMap, _d)
+	}
+
+	//中标单位-项目金额TOP3
+	type amount_projects struct {
+		Id     interface{} `json:"id"`
+		Name   string      `json:"name"`
+		Amount float64     `json:"amount"`
+		//Accounted interface{} `json:"accounted"`
+		Data interface{} `json:"buyertop3"`
+	}
+	type buyertop3 struct {
+		Name   string  `json:"name"`
+		Amount float64 `json:"amount"`
+		//Accounted interface{} `json:"accounted"`
+	}
+	amountMap := []interface{}{}
+	for _, v := range thisWinnerRow.WinnerAmountTop3.SWinnerAmount {
+		if v.SWinnerAmount.Value == 0 || winnerName[v.Key] == "" {
+			continue
+		}
+		var _d amount_projects
+		_d.Amount = v.SWinnerAmount.Value
+		_d.Name = winnerName[v.Key]
+		_d.Id = encodeId(v.Key)
+		/*if thisWinnerRow.ProjectAmount.Value != 0 {
+			_d.Accounted = v.SWinnerAmount.Value / thisWinnerRow.ProjectAmount.Value
+		}*/
+
+		var ss []interface{}
+		for _, v1 := range v.BuyerTop.Buckets {
+			if v1.BuyerWinnerAmount.Value == 0 {
+				continue
+			}
+			var _dd buyertop3
+			_dd.Name = v1.Key
+			_dd.Amount = v1.BuyerWinnerAmount.Value
+			/*if _d.Amount != 0 {
+				_dd.Accounted = v1.BuyerWinnerAmount.Value / _d.Amount
+			}*/
+			ss = append(ss, _dd)
+		}
+		_d.Data = ss
+		if !offline {
+			if len(ss) > Top3 {
+				_d.Data = ss[:Top3]
+			}
+			if len(amountMap) >= Top30 {
+				break
+			}
+		}
+		amountMap = append(amountMap, _d)
+	}
+	rMap.Store("winner_time_distribution", buyerMap)
+	rMap.Store("winner_count_top3", countMap)
+	rMap.Store("winner_amount_top3", amountMap)
+}
+func amountDistribution(v float64, data map[string]*distributionTrend) {
+	if v <= 0 {
+		return
+	}
+	if v < 100000 {
+		if data["<10万"] == nil {
+			data["<10万"] = new(distributionTrend)
+		}
+		data["<10万"].Amount += v
+		data["<10万"].Count++
+	} else if v < 500000 {
+		if data["10万~50万"] == nil {
+			data["10万~50万"] = new(distributionTrend)
+		}
+		data["10万~50万"].Amount += v
+		data["10万~50万"].Count++
+	} else if v < 1000000 {
+		if data["50万~100万"] == nil {
+			data["50万~100万"] = new(distributionTrend)
+		}
+		data["50万~100万"].Amount += v
+		data["50万~100万"].Count++
+	} else if v < 1000000*5 {
+		if data["100万~500万"] == nil {
+			data["100万~500万"] = new(distributionTrend)
+		}
+		data["100万~500万"].Amount += v
+		data["100万~500万"].Count++
+	} else if v < 1000000*10 {
+		if data["500万~1000万"] == nil {
+			data["500万~1000万"] = new(distributionTrend)
+		}
+		data["500万~1000万"].Amount += v
+		data["500万~1000万"].Count++
+	} else if v < 100000000 {
+		if data["1000万~1亿"] == nil {
+			data["1000万~1亿"] = new(distributionTrend)
+		}
+		data["1000万~1亿"].Amount += v
+		data["1000万~1亿"].Count++
+	} else if v >= 100000000 {
+		if data["≥1亿"] == nil {
+			data["≥1亿"] = new(distributionTrend)
+		}
+		data["≥1亿"].Amount += v
+		data["≥1亿"].Count++
+	}
+
+}
+func BuyerAnalysis(thisBuyerRow BuyerWinnerRow, rMap *sync.Map, winnerName map[string]string, sy *sync.WaitGroup, offline bool) {
+	defer sy.Done()
+	type buyer struct {
+		Name        string      `json:"key"`
+		TCount      int         `json:"t_count"`  //项目数量
+		TAmount     float64     `json:"t_amount"` //金额数量
+		TotalAmount interface{} `json:"total_amount"`
+		TotalNumber interface{} `json:"total_number"`
+	}
+	//采购单位-采购规模分布
+	buyerMap := []interface{}{}
+
+	//计算采购单位各区间金额
+	buyerA := make(map[string]*distributionTrend)
+	for _, v := range thisBuyerRow.BuyerAmountDistribution.Buckets {
+		amountDistribution(v.Amount.Value, buyerA)
+	}
+	var (
+		countAll  int
+		amountAll float64
+	)
+	for _, v := range buyerA {
+		countAll += v.Count
+		amountAll += v.Amount
+	}
+
+	for _, v := range Analysis {
+		var data buyer
+		data.Name = v
+		vlu, ok := buyerA[v]
+		if ok {
+			data.TAmount = vlu.Amount
+			data.TCount = vlu.Count
+			if countAll != 0 {
+				data.TotalNumber = float64(vlu.Count) / float64(countAll)
+			}
+			if amountAll != 0 {
+				data.TotalAmount = vlu.Amount / amountAll
+			}
+		}
+		buyerMap = append(buyerMap, data)
+	}
+
+	type number_projects struct {
+		Name   string `json:"name"`
+		Number int64  `json:"number"`
+		//Accounted interface{} `json:"accounted"`
+		Data interface{} `json:"winnertop3"`
+	}
+	type number_project struct {
+		Id     interface{} `json:"id"`
+		Name   string      `json:"name"`
+		Number int64       `json:"number"`
+		//Accounted interface{} `json:"accounted"`
+	}
+
+	//采购单位-项目数量TOP3
+	countMap := []interface{}{}
+	for _, v := range thisBuyerRow.BuyerCountTop3.Buckets {
+		var _d number_projects
+		_d.Name = v.Key
+		_d.Number = v.BuyerCount.Value
+		/*if thisBuyerRow.ProjectCount.Value != 0 {
+			_d.Accounted = float64(v.BuyerCount.Value) / float64(thisBuyerRow.ProjectCount.Value)
+		}*/
+		var ss []interface{}
+		for _, v1 := range v.SWinnerTop.Buckets {
+			var _dd number_project
+			_dd.Number = v1.BuyerWinnerAmount.Value
+			if winnerName[v1.Key] == "" {
+				continue
+			}
+			_dd.Name = winnerName[v1.Key]
+			_dd.Id = encodeId(v1.Key)
+
+			/*if _d.Number != 0 {
+				_dd.Accounted = float64(v1.BuyerWinnerAmount.Value) / float64(_d.Number)
+			}*/
+			ss = append(ss, _dd)
+		}
+		_d.Data = ss
+		if !offline {
+			if len(ss) > Top3 {
+				_d.Data = ss[:Top3]
+			}
+			if len(countMap) >= Top30 {
+				break
+			}
+		}
+		countMap = append(countMap, _d)
+	}
+
+	//采购单位-项目金额TOP3
+	type amount_projects struct {
+		Name   string  `json:"name"`
+		Amount float64 `json:"amount"`
+		//Accounted interface{} `json:"accounted"`
+		Data interface{} `json:"winnertop3"`
+	}
+	type amount_project struct {
+		Id     interface{} `json:"id"`
+		Name   string      `json:"name"`
+		Amount float64     `json:"amount"`
+		//Accounted interface{} `json:"accounted"`
+	}
+	amountMap := []interface{}{}
+	for _, v := range thisBuyerRow.BuyerAmountTop3.Buckets {
+		if v.BuyerAmount.Value == 0 {
+			continue
+		}
+		var _d amount_projects
+		_d.Name = v.Key
+		_d.Amount = v.BuyerAmount.Value
+		/*if thisBuyerRow.ProjectAmount.Value != 0 {
+			_d.Accounted = v.BuyerAmount.Value / thisBuyerRow.ProjectAmount.Value
+		}*/
+
+		var ss []interface{}
+		for _, v1 := range v.SWinnerTop.Buckets {
+			if v1.BuyerWinnerAmount.Value == 0 || winnerName[v1.Key] == "" {
+				continue
+			}
+			var _dd amount_project
+			_dd.Amount = v1.BuyerWinnerAmount.Value
+
+			_dd.Name = winnerName[v1.Key]
+			_dd.Id = encodeId(v1.Key)
+			/*	if _d.Amount != 0 {
+				_dd.Accounted = v1.BuyerWinnerAmount.Value / _d.Amount
+			}*/
+
+			ss = append(ss, _dd)
+		}
+		_d.Data = ss
+		if !offline {
+			if len(ss) > Top3 {
+				_d.Data = ss[:Top3]
+			}
+			if len(amountMap) >= Top30 {
+				break
+			}
+		}
+		amountMap = append(amountMap, _d)
+	}
+	rMap.Store("buyer_time_distribution", buyerMap)
+	rMap.Store("buyer_count_top3", countMap)
+	rMap.Store("buyer_amount_top3", amountMap)
+}
+
+// top3数量计算
+func CountCompute(thisRow AreaCTop, types string, eid map[string]string, Offline bool) (rData []map[string]interface{}) {
+	if types == "area" {
+		area_infos := thisRow.AreaCountTop3
+		for _, v := range area_infos.Buckets {
+			if v.Total == 0 {
+				break
+			}
+			rM := map[string]interface{}{}
+			rWinner := []map[string]interface{}{}
+			rM["name"] = v.Area
+			rM["area_count"] = v.Total
+			for _, va := range v.WinnerTop.Buckets {
+				if va.WinnerTotal == 0 {
+					break
+				}
+				rW := map[string]interface{}{}
+				id := encodeId(va.Winner)
+				rW["id"] = id
+				rW["winner"] = eid[va.Winner]
+				if eid[va.Winner] == "" {
+					continue
+				}
+				rW["winner_total"] = va.WinnerTotal
+				rWinner = append(rWinner, rW)
+			}
+			if !Offline && len(rWinner) == 0 {
+				continue
+			}
+			rM["winner"] = rWinner
+			//实时分析 取前三个
+			if len(rWinner) > Top3 && !Offline {
+				rM["winner"] = rWinner[:Top3]
+			}
+			if thisRow.Total != 0 {
+				vv := float64(v.Total) / float64(thisRow.Total)
+				rM["area_scale"] = vv
+			} else {
+				rM["area_scale"] = 0
+			}
+
+			rData = append(rData, rM)
+		}
+	} else {
+		area_infos := thisRow.BuyclassCountTop3
+		for _, v := range area_infos.Buckets {
+			if v.Total == 0 {
+				break
+			}
+			rM := map[string]interface{}{}
+			rWinner := []map[string]interface{}{}
+			rM["name"] = v.Buyclass
+			rM["buyclass_count"] = v.Total
+			for _, va := range v.BidcountTop.Buckets {
+				if va.WinnerTotal == 0 {
+					break
+				}
+				rW := map[string]interface{}{}
+				id := encodeId(va.Winner)
+				rW["id"] = id
+				rW["winner"] = eid[va.Winner]
+				if eid[va.Winner] == "" {
+					continue
+				}
+				rW["winner_total"] = va.WinnerTotal
+				rWinner = append(rWinner, rW)
+			}
+			if !Offline && len(rWinner) == 0 {
+				continue
+			}
+			rM["winner"] = rWinner
+			//实时分析 取前三个
+			if len(rWinner) > Top3 && !Offline {
+				rM["winner"] = rWinner[:Top3]
+			}
+			if thisRow.Total != 0 {
+				vv := float64(v.Total) / float64(thisRow.Total)
+				rM["buyclass_scale"] = vv
+			} else {
+				rM["buyclass_scale"] = 0
+			}
+
+			rData = append(rData, rM)
+		}
+	}
+	return
+}
+
+// top3金额计算
+func AmountCompute(thisRow AreaCTop, types string, eid map[string]string, Offline bool) (rData []map[string]interface{}) {
+	if types == "area" {
+		area_infos := thisRow.AreaAmountTop3
+		for _, v := range area_infos.Buckets {
+			if v.AreaAmount.Amount == 0 {
+				break
+			}
+			rM := map[string]interface{}{}
+			rWinner := []map[string]interface{}{}
+			rM["name"] = v.Key
+			rM["area_amount"] = v.AreaAmount.Amount
+			for _, va := range v.WinnerTop.Buckets {
+				if va.WinnerAmount.Amount == 0 {
+					break
+				}
+				rW := map[string]interface{}{}
+				id := encodeId(va.Winner)
+				rW["id"] = id
+				rW["winner"] = eid[va.Winner]
+				if eid[va.Winner] == "" {
+					continue
+				}
+				rW["winner_amount"] = va.WinnerAmount.Amount
+				//rW["amount_scale"] = va.WinnerAmount.Amount / v.AreaAmount.Amount
+				rWinner = append(rWinner, rW)
+			}
+			rM["winner"] = rWinner
+			//实时分析 取前三个
+			if len(rWinner) > Top3 && !Offline {
+				rM["winner"] = rWinner[:Top3]
+			}
+			if thisRow.Amount.Value != 0 {
+				vv := v.AreaAmount.Amount / thisRow.Amount.Value
+				rM["area_scale"] = vv
+			} else {
+				rM["area_scale"] = 0
+			}
+			rData = append(rData, rM)
+		}
+	} else {
+		area_infos := thisRow.BuyclassAmountTop3
+		for _, v := range area_infos.Buckets {
+			if v.AreaAmount.Amount == 0 {
+				break
+			}
+			rM := map[string]interface{}{}
+			rWinner := []map[string]interface{}{}
+			rM["name"] = v.Key
+			rM["buyclass_amount"] = v.AreaAmount.Amount
+			for _, va := range v.WinnerTop.Buckets {
+				if va.WinnerAmount.Amount == 0 {
+					break
+				}
+				rW := map[string]interface{}{}
+				id := encodeId(va.Winner)
+				rW["id"] = id
+				rW["winner"] = eid[va.Winner]
+				if eid[va.Winner] == "" {
+					continue
+				}
+				rW["winner_amount"] = va.WinnerAmount.Amount
+				//rW["amount_scale"] = va.WinnerAmount.Amount / v.AreaAmount.Amount
+				rWinner = append(rWinner, rW)
+			}
+			rM["winner"] = rWinner
+			//实时分析 取前三个
+			if len(rWinner) > Top3 && !Offline {
+				rM["winner"] = rWinner[:Top3]
+			}
+			if thisRow.Amount.Value != 0 {
+				vv := v.AreaAmount.Amount / thisRow.Amount.Value
+				rM["buyclass_scale"] = vv
+			} else {
+				rM["buyclass_scale"] = 0
+			}
+			rData = append(rData, rM)
+		}
+	}
+	return
+}
+
+// 企业id查企业名 传入数组
+func IDToName(ids []string) (iMap map[string]string) {
+	iMap = map[string]string{}
+	d := strings.Join(ids, `","`)
+	finalSql := fmt.Sprintf(queryIdto, d, len(ids))
+	log.Println("IDToName sql:", finalSql)
+	hits := elastic.Get("qyxy", "qyxy", finalSql)
+	if hits == nil || len(*hits) == 0 {
+		return
+	}
+	for _, item := range *hits {
+		id, _ := item["_id"].(string)
+		name, _ := item["company_name"].(string)
+		if id != "" && name != "" {
+			iMap[id] = name
+		}
+	}
+	return
+}
+
+// 客户分布
+func CustomerDistribute(thisRow AreaCTop) (data []map[string]interface{}, otherData map[string]interface{}) {
+	var (
+		total  int64
+		amount float64
+	)
+	for _, v := range thisRow.BuyerclassScale.Buckets {
+		if v.AreaTotal == 0 && v.BuyclassAmount.Value == 0 {
+			continue
+		}
+		rM := map[string]interface{}{}
+		rM["buyclass"] = v.Area
+		rM["total"] = v.AreaTotal
+		rM["amount"] = v.BuyclassAmount.Value
+		total += v.AreaTotal
+		amount += v.BuyclassAmount.Value
+		data = append(data, rM)
+	}
+	//for _, v := range thisRow.BuyerclassScaleOther.Buckets {
+	//	total += v.AreaTotal
+	//	amount += v.BuyclassAmount.Value
+	//}
+	otherData = make(map[string]interface{})
+	otherData["buyclass"] = "其它"
+	if pCount := common.Int64All(thisRow.ProjectCount.DocCount) - total; pCount > 0 {
+		otherData["total"] = pCount
+	}
+	if pAmount := thisRow.Amount.Value - amount; pAmount > 0 {
+		otherData["amount"] = pAmount
+	}
+	return
+}
+
+// 地区分布
+func AreaDistribute(thisRow AreaCTop) (data []map[string]interface{}) {
+	for _, v := range thisRow.AreaDistribution.Buckets {
+		rM := map[string]interface{}{}
+		rM["area"] = v.Area
+		rM["total"] = v.AreaTotal
+		rM["amount"] = v.AreaAmount.Value
+		var rmc []map[string]interface{}
+		if len(v.CityGroup.Buckets) > 0 {
+			for _, c := range v.CityGroup.Buckets {
+				rmc = append(rmc, map[string]interface{}{
+					"city":   c.City,
+					"total":  c.CityTotal,
+					"amount": c.CityAmount.Value,
+				})
+			}
+			rM["areaDetails"] = rmc
+		}
+		data = append(data, rM)
+	}
+	return
+}
+
+// 项目规模分布
+func ProjectScale(thisRow AreaCTop) (data []interface{}) {
+	ammount := thisRow.Amount.Value
+	total := thisRow.CountNot0.Count
+	buckets := thisRow.SortpriceRanges.Buckets
+	type Scale struct {
+		Name      string  `bson:"Name"`
+		Persent_c float64 `bson:"Persent_c"`
+		Persent_a float64 `bson:"Persent_a"`
+		Ammount   float64 `json:"ammount"`
+		Total     int64   `json:"total"`
+	}
+	for _, v := range buckets {
+		data = append(data, Scale{
+			Name:      v.Name,
+			Ammount:   v.SumSortprice.Value,
+			Total:     v.Total,
+			Persent_a: Formula(float64(v.Total), float64(total)),
+			Persent_c: Formula(v.SumSortprice.Value, ammount),
+		})
+	}
+	return
+}
+
+// 计算公式
+func Formula(current, total float64) (result float64) {
+	if total == 0 {
+		return 0
+	}
+	result = current / total
+	return
+}
+
+func InterToSliceString(obj interface{}) []string {
+	var sli = make([]string, 0)
+	if obj == nil {
+		return sli
+	}
+
+	if _, ok := obj.([]interface{}); ok {
+		for _, i := range obj.([]interface{}) {
+			sli = append(sli, i.(string))
+		}
+	}
+
+	if _, ok := obj.([]string); ok {
+		return obj.([]string)
+	}
+
+	return sli
+}
+func sequential(now, old float64) interface{} {
+	if old == 0 {
+		return nil
+	}
+	return (now - old) / old
+}
+
+// GetAggs 聚合查询
+func GetAggs(index, itype, query string) esV7.Aggregations {
+	v1, _, _ := elastic.GetAggs(index, itype, query)
+	return v1
+}
+
+func GetAggsWithCount(index, itype, query string) (esV7.Aggregations, int64) {
+	if len(query) > 0 {
+		query = `{"track_total_hits": true,` + query[1:]
+	}
+	v1, v2, _ := elastic.GetAggs(index, itype, query)
+	return v1, v2
+}
+
+func getPreviousMarket(sTime, eTime time.Time) int64 {
+	var os_time int64
+	s_time := sTime
+	if SEMonth(sTime, eTime) {
+		var min int
+		//统计月份
+		for sTime.Before(eTime) {
+			sTime = sTime.AddDate(0, 1, 0)
+			min++
+		}
+		os_time = s_time.AddDate(0, -min, 0).Unix()
+	} else {
+		os_time = s_time.AddDate(0, 0, -int(math.Ceil(eTime.Sub(sTime).Hours()/24))).Unix()
+	}
+
+	return os_time
+}
+
+func Rest(res esV7.Aggregations, thisRow *marketTime) {
+	for name, object := range res {
+		bArr, err := object.MarshalJSON()
+		if len(bArr) == 0 || err != nil {
+			continue
+		}
+		if name == "thismarket" {
+			if json.Unmarshal(bArr, &thisRow.Thismarket) != nil {
+				continue
+			}
+		} else if name == "oldmarket" {
+			if json.Unmarshal(bArr, &thisRow.Oldmarket) != nil {
+				continue
+			}
+		} else if name == "monthtime" {
+			if json.Unmarshal(bArr, &thisRow.Monthtime) != nil {
+				continue
+			}
+		} else if name == "yeartime" {
+			if json.Unmarshal(bArr, &thisRow.Yeartime) != nil {
+				continue
+			}
+		}
+	}
+}
+
+func GetMonthData(isOffline bool, sTime, eTime time.Time) (bool, string) {
+	var _b bool
+	//整月多取一个月进行环比
+	if !isOffline && SEMonth(sTime, eTime) {
+		_b = true
+		sTime = sTime.AddDate(0, -1, 0)
+	}
+
+	return _b, getBidamountStatistics(sTime, eTime)
+}
+
+func getBidamountStatistics(sTime, eTime time.Time) string {
+	timeRange := ``
+	tmpTime, rTime, tEndTime := sTime, sTime, getMonthRange(eTime, false)
+	for rTime.Before(tEndTime) {
+		ts, te := getMonthRange(tmpTime, true), getMonthRange(tmpTime, false)
+		if sTime == tmpTime {
+			ts = sTime
+		}
+		if te == tEndTime {
+			te = eTime
+		}
+		if ts.Before(te) {
+			timeRange += fmt.Sprintf(`{"key":"%s","from":%d,"to":%d},`, fmt.Sprintf("%d-%d", ts.Year(), ts.Month()), ts.Unix(), te.Unix())
+		}
+		rTime = rTime.AddDate(0, 1, 0)
+		if int(rTime.Month())-int(tmpTime.Month()) > 1 {
+			rTime = rTime.AddDate(0, -1, 0)
+		}
+		tmpTime = rTime
+	}
+	if timeRange == `` {
+		return ""
+	}
+	return timeRange[:len(timeRange)-1]
+}
+
+// getMonthRange获取月份范围
+// isStart true本月月初  false 本月月末(下月月初)
+func getMonthRange(t time.Time, isStart bool) time.Time {
+	if isStart {
+		return time.Date(t.Year(), t.Month(), 1, 0, 0, 0, 0, t.Location())
+	}
+	return time.Date(t.Year(), t.Month()+1, 1, 0, 0, 0, 0, t.Location())
+}
+
+func GetYearData(isOffline bool, sTime, eTime time.Time) (bool, string) {
+	var _b bool
+	//整月多取一个月进行环比
+	if !isOffline && sTime.Month() == 1 && sTime.Day() == 1 && eTime.Month() == 12 && eTime.Day() == 31 {
+		_b = true
+		sTime = sTime.AddDate(-1, 0, 0)
+	}
+	return _b, getCommonYearStatistics(sTime, eTime)
+}
+
+// getYearRange获取月份范围
+// isStart true本月月初  false 本月月末(下月月初)
+func getYearRange(t time.Time, isStart bool) time.Time {
+	if isStart {
+		return time.Date(t.Year(), 1, 1, 0, 0, 0, 0, t.Location())
+	}
+	return time.Date(t.Year()+1, 1, 1, 0, 0, 0, 0, t.Location())
+}
+
+// 年份统计
+func getCommonYearStatistics(sTime, eTime time.Time) string {
+	timeRange := ``
+	tmpTime, rTime, tEndTime := sTime, sTime, getYearRange(eTime, false)
+	for rTime.Before(tEndTime) {
+		ts, te := getYearRange(tmpTime, true), getYearRange(tmpTime, false)
+		if sTime == tmpTime {
+			ts = sTime
+		}
+		if te == tEndTime {
+			te = eTime
+		}
+		if ts.Before(te) {
+			timeRange += fmt.Sprintf(`{"key":"%d","from":%d,"to":%d},`, ts.Year(), ts.Unix(), te.Unix())
+		}
+		rTime = rTime.AddDate(1, 0, 0)
+		tmpTime = rTime
+	}
+	if timeRange == `` {
+		return ""
+	}
+	return timeRange[:len(timeRange)-1]
+}
+
+func GetEntNameByIds(ids []string) (returnMap map[string]string) {
+	returnMap = map[string]string{}
+	if len(ids) == 0 {
+		return
+	}
+	list := elastic.Get("qyxy", "qyxy", fmt.Sprintf(`{"query":{"bool":{"must":[{"terms":{"_id":["%s"]}}]}},"_source":["_id","company_name"],"size":%d}`, strings.Join(ids, `","`), len(ids)))
+	if list == nil || len(*list) == 0 {
+		return
+	}
+	for _, item := range *list {
+		id, _ := item["_id"].(string)
+		name, _ := item["company_name"].(string)
+		if id != "" && name != "" {
+			returnMap[id] = name
+		}
+	}
+	return
+}
+
+// GetMsgOpen 获取用户服务通知开关是否开启
+func GetMsgOpen(mgo *mongodb.MongodbSim, mgoUserId string, positionType int, entId, entUserId int) bool {
+	//-- 服务通知不区分身份,只存在user表中
+	//pushSetMap := &map[string]interface{}{}
+	//if positionType == 1 {
+	//	pushSetMap, _ = mgo.FindOne("ent_user", map[string]interface{}{"i_entid": entId, "i_userid": entUserId})
+	//} else {
+	//	pushSetMap, _ = mgo.FindById("user", mgoUserId, `{"o_pushset":1,"s_m_openid":1}`)
+	//}
+	pushSetMap, _ := mgo.FindById("user", mgoUserId, `{"o_pushset":1,"s_m_openid":1}`)
+	//log.Println(mgoUserId, pushSetMap)
+	if pushSetMap != nil && len(*pushSetMap) > 0 {
+		pushset := common.ObjToMap((*pushSetMap)["o_pushset"])
+		if pushset == nil || len(*pushset) == 0 {
+			return false
+		}
+		msgServicePushSet := common.ObjToMap((*pushset)["o_msg_service"])
+		if msgServicePushSet != nil {
+			if common.IntAll((*msgServicePushSet)["i_apppush"]) == 1 || common.IntAll((*msgServicePushSet)["i_wxpush"]) == 1 {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+func encodeId(sid string) string {
+	if sid == "" || sid == "-" { //不存在的id为-
+		return ""
+	}
+	return encrypt.EncodeArticleId2ByCheck(sid)
+}
+
+// getGroupKeywordArr 模糊拆分为多个精准匹配
+func getGroupKeywordArr(res []viewKeyWord) (rData []viewKeyWord) {
+	for _, kw := range res {
+		if kw.MatchWay == 1 {
+			for _, kk := range kw.Keyword {
+				rData = append(rData, viewKeyWord{
+					Keyword: []string{kk},
+					Exclude: kw.Exclude,
+				})
+			}
+			for _, kk := range kw.Appended {
+				rData = append(rData, viewKeyWord{
+					Keyword: []string{kk},
+					Exclude: kw.Exclude,
+				})
+			}
+		} else {
+			rData = append(rData, kw)
+		}
+	}
+	return
+}
+
+// getAllKeywordArr 获取所有匹配词
+func getAllKeywordArr(res []keyWordGroup) (rData []viewKeyWord) {
+	for _, kwg := range res {
+		rData = append(rData, getGroupKeywordArr(kwg.A_Key)...)
+	}
+	return
+}
+
+func getKeyWordSql(v viewKeyWord, matchingMode string) string {
+	var (
+		shoulds, must_not []string
+		//默认查询项目名称与标的物
+		localMultiMatch = `{"multi_match": {"query": %s,"type": "phrase", "fields": [` + FieldsDetail + `]}}`
+	)
+	if matchingMode == "title" { //只匹配项目名称
+		localMultiMatch = `{"multi_match": {"query": %s,"type": "phrase", "fields": [` + Fields + `]}}`
+	}
+	//附加词
+	for _, vv := range v.Keyword {
+		vv = strings.TrimSpace(vv)
+		if vv == "" {
+			continue
+		}
+		shoulds = append(shoulds, fmt.Sprintf(localMultiMatch, "\""+vv+"\""))
+	}
+
+	for _, vv := range v.Appended {
+		vv = strings.TrimSpace(vv)
+		if vv == "" {
+			continue
+		}
+		shoulds = append(shoulds, fmt.Sprintf(localMultiMatch, "\""+vv+"\""))
+	}
+
+	//排除词
+	for _, vv := range v.Exclude {
+		vv = strings.TrimSpace(vv)
+		if vv == "" {
+			continue
+		}
+		must_not = append(must_not, fmt.Sprintf(localMultiMatch, "\""+vv+"\""))
+	}
+
+	//添加
+	if len(shoulds) > 0 {
+		notStr := ""
+		if len(must_not) > 0 {
+			notStr = fmt.Sprintf(`,"must_not":[%s]`, strings.Join(must_not, ","))
+		}
+		return fmt.Sprintf(queryBoolMustAnd, strings.Join(shoulds, ","), notStr)
+	}
+	return ""
+}
+
+// 判断是否月初到月末
+func SEMonth(sTime, eTime time.Time) bool {
+	var day int
+	month := int(eTime.Month())
+	if month == 2 {
+		if eTime.Year()%4 == 0 {
+			day = 29
+		} else {
+			day = 28
+		}
+	} else {
+		day = yMDay[month]
+	}
+
+	if sTime.Day() == 1 && eTime.Day() == day {
+		return true
+	}
+	return false
+}

+ 76 - 0
marketanalysis/consts.go

@@ -0,0 +1,76 @@
+package marketanalysis
+
+const (
+	MarketQueryItem      = iota //回显查询条件
+	MarketScaleMain             //市场规模
+	MarketTopProject            //项目规模TOP10
+	MarketProjectAllData        //项目规模 地区分布 客户分布 地区客户top3
+	MarketScaleRefine           //细化市场
+	MarketBuyerAndWinner        //市场-采购单位&&中标企业
+
+	queryBoolMustAnd = `{"bool": {"must": [%s]%s}}`
+	pSearchDecMust   = `"bidstatus": ["中标","成交","合同","单一"]`
+	queryBoolMust    = `{"terms": {%s}}`
+	//市场分析聚合查询
+	aggsMarketAnalysis = `"%s": {"range": {"field": "jgtime","ranges": [%s]},"aggs":{"project_count": {"filter": {"match_all":{}}},"project_amount":{"sum":{"field":"sortprice"}},"project_avgMoney": { "avg": {"field": "sortprice","missing": 0}} ,"buyer_count":{"cardinality":{"field":"buyer","precision_threshold": 40000}},"winner_count":{"cardinality":{"field":"s_winner","precision_threshold": 40000}}}}`
+	//时间分布统计
+	projectTimeDistribution = `"%s": {"range": { "field": "jgtime","ranges": [%s]},"aggs":{ "scale_amount": {"sum": {"field": "sortprice"}}, "scale_total": {"filter": {"match_all":{}}}}}`
+	//采购单位聚合查询
+	buyerProcurementScale = `"buyer_amount_distribution": {"terms": {"field": "buyer","order": [{"amount": "desc"}],"size":%d},"aggs": {"amount": {"sum": {"field": "sortprice"}}}}`
+	//采购单位top3(价格)buyer_sortprice
+	buyerSortprice = `"buyer_amount_top3": {"terms": {"field": "buyer","order": [{"buyer_amount": "desc"}],"size": %d},"aggs": {"buyer_amount": {"sum": {"field": "sortprice"}},"s_winner_top": {"terms": {"field": "entidlist","exclude":["-"],"order": [{"buyer_winner_amount": "desc"}],"size": %d},"aggs": {"buyer_winner_amount": {"sum": {"field": "sortprice"}}}}}}`
+	//采购单位top3(数量)
+	buyerCount = `"buyer_count_top3": {"terms": {"field": "buyer","order": [{"buyer_count": "desc"}],"size": %d},"aggs": {"buyer_count": {"filter":{"match_all":{}}},"s_winner_top": {"terms": {"field": "entidlist","exclude":["-"],"order": [{"buyer_winner_count": "desc"}],"size": %d},"aggs": { "buyer_winner_count": {"filter":{"match_all":{}}}}}}}`
+	//中标单位
+	winnerProcurementScale = `"winner_amount_distribution": {"filter": {"script": {"script": "doc['entidlist'].length == 1"}},"aggs": {"entidlist_terms": {"terms": {"field": "entidlist","order": {"amount": "desc"},"size": %d},"aggs": {"amount": {"sum": {"field": "sortprice"}}}}}}`
+	//中标单位top3(价格)
+	winnerSortprice = `"winner_amount_top3": {"terms": {"field": "entidlist","exclude":["-"],"order": [{"s_winner_amount": "desc"}],"size": %d},"aggs": {"s_winner_amount": {"sum": {"field": "sortprice"}},"buyer_top": {"terms": {"field": "buyer","order": [{"buyer_winner_amount": "desc"}],"size": %d},"aggs": {"buyer_winner_amount": {"sum": {"field": "sortprice"}}}}}}`
+	//中标单位(数量)
+	winnerCount              = `"winner_count_top3": {"terms": {"field": "entidlist","exclude":["-"],"order": [{"s_winner_count": "desc"}],"size": %d},"aggs": {"s_winner_count": {"filter":{"match_all":{}}},"buyer_top": {"terms": {"field": "buyer","order": [{"buyer_winner_count": "desc"}],"size": %d},"aggs": {"buyer_winner_count": {"filter":{"match_all":{}}}}}}}`
+	queryAggsSortprice       = `"sortprice_ranges": {"range":{"field":"sortprice","ranges":[%s]},"aggs":{"sum_sortprice":{"sum":{"field":"sortprice"}}}}`
+	aggsArea                 = `"area_distribution": {"terms": {"field": "area","size": 40},"aggs": {"area_amount": {"sum": {"field": "sortprice"}},"area_total": {"filter": {"match_all":{}}},"city_group": {"terms": {"field": "city","size": 40},"aggs": {"city_amount": {"sum": {"field": "sortprice"}}}}}}`
+	queryTop10               = `,"sort": [{"sortprice": "desc"}],"from": 0,"size": 10`
+	aggsAreaAmounttop3       = `"area_amount_top3":{"terms":{"field":"area","exclude":["全国"],"order":[{"area_amount":"desc"}],"size":34},"aggs":{"area_amount":{"sum":{"field":"sortprice"}},"winner_top":{"terms":{"field":"entidlist","exclude": ["-"],"order":[{"area_winner_amount":"desc"}],"size":%d},"aggs":{"area_winner_amount":{"sum":{"field":"sortprice"}}}}}}`
+	aggsAreaCounttop3        = `"area_count_top3":{"terms":{"field":"area","exclude":["全国"],"order":[{"area_count":"desc"}],"size":40},"aggs":{"area_count":{"filter":{"match_all":{}}},"winner_top":{"terms":{"field":"entidlist","exclude": ["-"],"order":[{"area_winner_count":"desc"}],"size":%d},"aggs":{"area_winner_count":{"filter":{"match_all":{}}}}}}}`
+	aggsBuyerclass           = `"buyerclass_scale":{"terms":{"field":"buyerclass","size":2000,"exclude":["其它",""]},"aggs":{"buyerclass_amount":{"sum":{"field":"sortprice"}},"buyerclass_total":{"filter":{"match_all":{}}}}}`
+	aggsBuyerclassCounttop3  = `"buyerclass_count_top3":{"terms":{"field":"buyerclass","exclude":["其它",""],"order":[{"buyerclass_count":"desc"}],"size":70},"aggs":{"buyerclass_count":{"filter":{"match_all":{}}},"bidcount_top":{"terms":{"field":"entidlist","exclude": ["-"],"order":[{"buyer_winner_count":"desc"}],"size":%d},"aggs":{"buyer_winner_count":{"filter":{"match_all":{}}}}}}}`
+	aggsBuyerclassAmounttop3 = `"buyerclass_amount_top3":{"terms":{"field":"buyerclass","exclude":["其它",""],"order":[{"buyerclass_amount":"desc"}],"size":70},"aggs":{"buyerclass_amount":{"sum":{"field":"sortprice"}},"winner_top":{"terms":{"field":"entidlist","exclude": ["-"],"order":[{"buyer_winner_amount":"desc"}],"size":%d},"aggs":{"buyer_winner_amount":{"sum":{"field":"sortprice"}}}}}}`
+	sortpriceStr             = `{"key":"<10万","from":0.0000000000001,"to":100000},{"key":"10万-50万","from":100000,"to":500000},{"key":"50万-100万","from":500000,"to":1000000},{"key":"100万-500万","from":1000000,"to":5000000},{"key":"500万-1000万","from":5000000,"to":10000000},{"key":"1000万-1亿","from":10000000,"to":100000000},{"key":"≥1亿","from":100000000}`
+	aggsAllCM                = `"project_count": {"filter": {"match_all":{}}},"project_count_not0": {"filter": {"range": {"sortprice": {"gt": 0}}}},"project_amount": {"sum": {"field": "sortprice"}}`
+
+	queryIdto = `{"query": {"bool": {"must": [{"terms": {"id": ["%s"]}}]}},"_source":["_id","company_name"],"size": %d}`
+
+	ReportHistoryTable = "marketAnalysisReport"
+	ReportCacheDB      = "other"
+	ReportCacheKey     = "marketAnalysis_%s_%d"
+	ReportCacheTime    = 60 * 5
+	ReportCanceledKey  = "marketAnalysisCanceled_%s" // 取消的报告id
+	ReportCanceledTime = 60 * 60 * 24
+
+	ReportStateGenerating     = 0                             // 报告生成状态 生成中
+	ReportStateGenerated      = 1                             // 生成 成功
+	ReportStateCanceled       = 2                             // 已取消
+	ReportStateFailed         = -1                            // 生成失败
+	CollMarketScaleMain       = "marketanalysisreport_scal"   //市场规模 报告模块对应的mongo 集合名称
+	CollMarketTopProject      = "marketanalysisreport_top"    //项目规模TOP10
+	CollMarketProjectAllData  = "marketanalysisreport_all"    //项目规模 地区分布 客户分布 地区客户top3
+	CollMarketScaleRefine     = "marketanalysisreport_refine" //细化市场
+	CollMarketBuyerAndWinner  = "marketanalysisreport_bw"     //市场-采购单位&&中标企业
+	ValueError                = -1                            // 分析数据过大
+	ValueOffline              = 1                             // 离线
+	ValueRealTime             = 2                             // 实时
+	TablejianyuMarUserAccount = "jianyu.mar_user_account"     // 离线市场报告分析关键词标准信息表
+	Top3                      = 3
+	Top5                      = 5
+	Top10                     = 10
+	Top20                     = 20
+	Top30                     = 30
+	Top50                     = 50
+	Top1000                   = 1000
+	BWDistribution            = 100000
+)
+
+var (
+	yMDay    = map[int]int{1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31}
+	Analysis = []string{"<10万", "10万~50万", "50万~100万", "100万~500万", "500万~1000万", "1000万~1亿", "≥1亿"}
+)

+ 391 - 0
marketanalysis/entity.go

@@ -0,0 +1,391 @@
+package marketanalysis
+
+type BWBuckets struct {
+	Buyerterms struct {
+		Buckets []struct {
+			Key      string `json:"key"`
+			DocCount int    `json:"doc_count"`
+		} `json:"buckets"`
+	} `json:"buyerterms"`
+	Winnerterms struct {
+		Buckets []struct {
+			Key      string `json:"key"`
+			DocCount int    `json:"doc_count"`
+		} `json:"buckets"`
+	} `json:"winnerterms"`
+}
+type marketTime struct {
+	Thismarket struct {
+		Buckets []marketBuckets `json:"buckets"`
+	} `json:"thismarket"`
+	Oldmarket struct {
+		Buckets []marketBuckets `json:"buckets"`
+	} `json:"oldmarket"`
+	Monthtime struct {
+		Buckets []Buckets `json:"buckets"`
+	} `json:"monthtime"`
+	Yeartime struct {
+		Buckets []Buckets `json:"buckets"`
+	} `json:"yeartime"`
+}
+type marketBuckets struct {
+	ProjectCount struct {
+		DocCount int `json:"doc_count"`
+	} `json:"project_count"`
+	ProjectAmount struct {
+		Value float64 `json:"value"`
+	} `json:"project_amount"`
+	BuyerCount struct {
+		Value int `json:"value"`
+	} `json:"buyer_count"`
+	WinnerCount struct {
+		Value int `json:"value"`
+	} `json:"winner_count"`
+	ProjectAvgMoney struct {
+		//DocCount  int `json:"doc_count"`
+		//AvgAmount struct {
+		//	Value float64 `json:"value"`
+		//} `json:"avg_amount"`
+		Value float64 `json:"value"`
+	} `json:"project_avgMoney"`
+}
+type Buckets struct {
+	Key          string `json:"key"`
+	From         int    `json:"from"`
+	FromAsString string `json:"from_as_string"`
+	To           int    `json:"to"`
+	ToAsString   string `json:"to_as_string"`
+	DocCount     int    `json:"doc_count"`
+	ScaleTotal   struct {
+		DocCount int `json:"doc_count"`
+	} `json:"scale_total"`
+	ScaleAmount struct {
+		Value float64 `json:"value"`
+	} `json:"scale_amount"`
+}
+
+type AreaCTop struct {
+	ProjectCount struct {
+		DocCount int `json:"doc_count"`
+	} `json:"project_count"`
+	Total     int64 `json:"doc_count"`
+	CountNot0 struct {
+		Count int64 `json:"doc_count"`
+	} `json:"project_count_not0"`
+	Amount struct {
+		Value float64 `json:"value"`
+	} `json:"project_amount"`
+	SortpriceRanges struct {
+		Buckets []struct {
+			Name         string `json:"key"`
+			Total        int64  `json:"doc_count"`
+			SumSortprice struct {
+				Value float64 `json:"value"`
+			} `json:"sum_sortprice"`
+		}
+	} `json:"sortprice_ranges"`
+	AreaDistribution struct {
+		Buckets []struct {
+			Area       string `json:"key"`
+			AreaTotal  int64  `json:"doc_count"`
+			AreaAmount struct {
+				Value float64 `json:"value"`
+			} `json:"area_amount"`
+			BuyclassAmount struct {
+				Value float64 `json:"value"`
+			} `json:"buyerclass_amount"`
+			CityGroup struct {
+				Buckets []struct {
+					City       string `json:"key"`
+					CityTotal  int64  `json:"doc_count"`
+					CityAmount struct {
+						Value float64 `json:"value"`
+					} `json:"city_amount"`
+				}
+			} `json:"city_group"`
+		}
+	} `json:"area_distribution"`
+	BuyerclassScale struct {
+		Buckets []struct {
+			Area       string `json:"key"`
+			AreaTotal  int64  `json:"doc_count"`
+			AreaAmount struct {
+				Value float64 `json:"value"`
+			} `json:"area_amount"`
+			BuyclassAmount struct {
+				Value float64 `json:"value"`
+			} `json:"buyerclass_amount"`
+		}
+	} `json:"buyerclass_scale"`
+	BuyerclassScaleOther struct {
+		Buckets []struct {
+			Area       string `json:"key"`
+			AreaTotal  int64  `json:"doc_count"`
+			AreaAmount struct {
+				Value float64 `json:"value"`
+			} `json:"area_amount"`
+			BuyclassAmount struct {
+				Value float64 `json:"value"`
+			} `json:"buyerclass_amount"`
+		}
+	} `json:"buyerclass_scale_other"`
+	AreaAmountTop3 struct {
+		Buckets []struct {
+			Key        string `json:"key"`
+			Total      int64  `json:"doc_count"`
+			AreaAmount struct {
+				Amount float64 `json:"value"`
+			} `json:"area_amount"`
+			WinnerTop struct {
+				Buckets []struct {
+					Winner       string `json:"key"`
+					WinnerTotal  int64  `json:"doc_count"`
+					WinnerAmount struct {
+						Amount float64 `json:"value"`
+					} `json:"area_winner_amount"`
+					ProjectDetail struct {
+						Hits struct {
+							Hits []struct {
+								Source struct {
+									Entidlist []string `json:"entidlist"`
+									SWinner   string   `json:"s_winner"`
+								} `json:"_source"`
+							}
+						}
+					} `json:"project_detail"`
+				}
+			} `json:"winner_top"`
+		}
+	} `json:"area_amount_top3"`
+	AreaCountTop3 struct {
+		Buckets []struct {
+			Area      string `json:"key"`
+			Total     int64  `json:"doc_count"`
+			WinnerTop struct {
+				Buckets []struct {
+					Winner        string `json:"key"`
+					WinnerTotal   int64  `json:"doc_count"`
+					ProjectDetail struct {
+						Hits struct {
+							Hits []struct {
+								Source struct {
+									Entidlist []string `json:"entidlist"`
+									SWinner   string   `json:"s_winner"`
+								} `json:"_source"`
+							}
+						}
+					} `json:"project_detail"`
+				}
+			} `json:"winner_top"`
+		}
+	} `json:"area_count_top3"`
+	BuyclassAmountTop3 struct {
+		Buckets []struct {
+			Key        string `json:"key"`
+			Total      int64  `json:"doc_count"`
+			AreaAmount struct {
+				Amount float64 `json:"value"`
+			} `json:"buyerclass_amount"`
+			WinnerTop struct {
+				Buckets []struct {
+					Winner       string `json:"key"`
+					WinnerTotal  int64  `json:"doc_count"`
+					WinnerAmount struct {
+						Amount float64 `json:"value"`
+					} `json:"buyer_winner_amount"`
+					ProjectDetail struct {
+						Hits struct {
+							Hits []struct {
+								Source struct {
+									Entidlist []string `json:"entidlist"`
+									SWinner   string   `json:"s_winner"`
+								} `json:"_source"`
+							}
+						}
+					} `json:"project_detail"`
+				}
+			} `json:"winner_top"`
+		}
+	} `json:"buyerclass_amount_top3"`
+	BuyclassCountTop3 struct {
+		Buckets []struct {
+			Buyclass    string `json:"key"`
+			Total       int64  `json:"doc_count"`
+			BidcountTop struct {
+				Buckets []struct {
+					Winner        string `json:"key"`
+					WinnerTotal   int64  `json:"doc_count"`
+					ProjectDetail struct {
+						Hits struct {
+							Hits []struct {
+								Source struct {
+									Entidlist []string `json:"entidlist"`
+									SWinner   string   `json:"s_winner"`
+								} `json:"_source"`
+							}
+						}
+					} `json:"project_detail"`
+				}
+			} `json:"bidcount_top"`
+		}
+	} `json:"buyerclass_count_top3"`
+}
+
+type BuyerWinnerRow struct {
+	BuyerAmountDistribution struct {
+		Buckets []struct {
+			Key      string `json:"key"`
+			DocCount int    `json:"doc_count"`
+			Amount   struct {
+				Value float64 `json:"value"`
+			} `json:"amount"`
+		} `json:"buckets"`
+	} `json:"buyer_amount_distribution"`
+
+	ProjectAmount struct {
+		Value float64 `json:"value"`
+	} `json:"project_amount"`
+	BuyerCountTop3 struct {
+		Buckets []struct {
+			Key        string `json:"key"`
+			BuyerCount struct {
+				//Value int64 `json:"value"`
+				Value int64 `json:"doc_count"`
+			} `json:"buyer_count"`
+			SWinnerTop struct {
+				Buckets []struct {
+					Key           string `json:"key"`
+					DocCount      int64  `json:"doc_count"`
+					ProjectDetail struct {
+						Hits struct {
+							Total    int `json:"total"`
+							MaxScore int `json:"max_score"`
+							Hits     []struct {
+								Source struct {
+									Entidlist []string `json:"entidlist"`
+									SWinner   string   `json:"s_winner"`
+								} `json:"_source"`
+							} `json:"hits"`
+						} `json:"hits"`
+					} `json:"project_detail"`
+					BuyerWinnerAmount struct {
+						//Value int64 `json:"value"`
+						Value int64 `json:"doc_count"`
+					} `json:"buyer_winner_count"`
+				} `json:"buckets"`
+				Value float64 `json:"value"`
+			} `json:"s_winner_top"`
+		} `json:"buckets"`
+	} `json:"buyer_count_top3"`
+	BuyerAmountTop3 struct {
+		Buckets []struct {
+			Key         string `json:"key"`
+			BuyerAmount struct {
+				Value float64 `json:"value"`
+			} `json:"buyer_amount"`
+			SWinnerTop struct {
+				Buckets []struct {
+					Key           string `json:"key"`
+					DocCount      int64  `json:"doc_count"`
+					ProjectDetail struct {
+						Hits struct {
+							Total    int `json:"total"`
+							MaxScore int `json:"max_score"`
+							Hits     []struct {
+								Source struct {
+									Entidlist []string `json:"entidlist"`
+									SWinner   string   `json:"s_winner"`
+								} `json:"_source"`
+							} `json:"hits"`
+						} `json:"hits"`
+					} `json:"project_detail"`
+					BuyerWinnerAmount struct {
+						Value float64 `json:"value"`
+					} `json:"buyer_winner_amount"`
+				} `json:"buckets"`
+				Value float64 `json:"value"`
+			} `json:"s_winner_top"`
+		} `json:"buckets"`
+	} `json:"buyer_amount_top3"`
+
+	WinnerAmountDistribution struct {
+		EntidlistTerms struct {
+			Buckets []struct {
+				Key    string `json:"key"`
+				Amount struct {
+					Value float64 `json:"value"`
+				} `json:"amount"`
+			} `json:"buckets"`
+		} `json:"entidlist_terms"`
+	} `json:"winner_amount_distribution"`
+
+	WinnerCountTop3 struct {
+		SWinnerCount []struct {
+			Key           string `json:"key"`
+			ProjectDetail struct {
+				Hits struct {
+					Total    int `json:"total"`
+					MaxScore int `json:"max_score"`
+					Hits     []struct {
+						Id     string `json:"_id"`
+						Source struct {
+							Entidlist []string `json:"entidlist"`
+							SWinner   string   `json:"s_winner"`
+						} `json:"_source"`
+					} `json:"hits"`
+				} `json:"hits"`
+			} `json:"project_detail"`
+			BuyerCount struct {
+				//Value int64 `json:"value"`
+				Value int64 `json:"doc_count"`
+			} `json:"s_winner_count"`
+			BuyerTop struct {
+				Buckets []struct {
+					Key               string `json:"key"`
+					DocCount          int64  `json:"doc_count"`
+					BuyerWinnerAmount struct {
+						//Value int64 `json:"value"`
+						Value int64 `json:"doc_count"`
+					} `json:"buyer_winner_count"`
+				} `json:"buckets"`
+				Value float64 `json:"value"`
+			} `json:"buyer_top"`
+		} `json:"buckets"`
+	} `json:"winner_count_top3"`
+	WinnerAmountTop3 struct {
+		SWinnerAmount []struct {
+			Key           string `json:"key"`
+			ProjectDetail struct {
+				Hits struct {
+					Total    int `json:"total"`
+					MaxScore int `json:"max_score"`
+					Hits     []struct {
+						Source struct {
+							Entidlist []string `json:"entidlist"`
+							SWinner   string   `json:"s_winner"`
+						} `json:"_source"`
+					} `json:"hits"`
+				} `json:"hits"`
+			} `json:"project_detail"`
+			SWinnerAmount struct {
+				Value float64 `json:"value"`
+			} `json:"s_winner_amount"`
+			BuyerTop struct {
+				Buckets []struct {
+					Key               string `json:"key"`
+					DocCount          int64  `json:"doc_count"`
+					BuyerWinnerAmount struct {
+						Value float64 `json:"value"`
+					} `json:"buyer_winner_amount"`
+				} `json:"buckets"`
+				Value float64 `json:"value"`
+			} `json:"buyer_top"`
+		} `json:"buckets"`
+	} `json:"winner_amount_top3"`
+}
+
+type distributionTrend struct {
+	Key    string
+	Count  int
+	Amount float64
+}

+ 1611 - 0
marketanalysis/marketanalysis.go

@@ -0,0 +1,1611 @@
+package marketanalysis
+
+import (
+	"app.yhyue.com/moapp/jybase/common"
+	"app.yhyue.com/moapp/jybase/encrypt"
+	elastic "app.yhyue.com/moapp/jybase/es"
+	"app.yhyue.com/moapp/jybase/mongodb"
+	"app.yhyue.com/moapp/jybase/mysql"
+	"app.yhyue.com/moapp/jybase/redis"
+	"app.yhyue.com/moapp/jypkg/common/src/qfw/util/jy"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"log"
+	"strings"
+	"sync"
+	"time"
+)
+
+var (
+	MAPool            chan bool
+	MATimeout         int
+	MAProjectNumLimit int
+	MAKeyWordsCount   int
+	MinKeyWordsCount  int //关键词数量最低值 小于此数量,默认走线上
+	MaxPCount         int //项目最大限制数量
+	ProjectCount      int
+	PtIndex           = "projectset"
+	PtType            = "projectset"
+	FieldsDetail      = `"purchasing","projectname.pname"`
+	Fields            = `"projectname.pname"`
+	BWCount           = Top1000
+	BWDCount          = BWDistribution
+)
+
+func MAInit(limit, timeOut, projectNumLimit, keyWordsCount, pCount, minKCount, maxPCount, bwc, bwdc int, ptIndex, ptType string, fields []string) {
+	if limit == 0 {
+		limit = 5
+	}
+	MAPool = make(chan bool, limit)
+	for i := 0; i < limit; i++ {
+		MAPool <- true
+	}
+	if timeOut <= 0 {
+		timeOut = 20
+	}
+	MATimeout = timeOut
+	if projectNumLimit <= 0 {
+		projectNumLimit = 600000
+	}
+	MAProjectNumLimit = projectNumLimit
+	if keyWordsCount <= 0 {
+		keyWordsCount = 300
+	}
+	MAKeyWordsCount = keyWordsCount
+
+	if minKCount <= 0 {
+		minKCount = 100
+	}
+	MinKeyWordsCount = minKCount
+	if maxPCount <= 0 {
+		maxPCount = 5000000
+	}
+	MaxPCount = maxPCount
+	//项目数量
+	ProjectCount = pCount
+	//es 索引
+	if ptIndex != "" {
+		PtIndex = ptIndex
+		PtType = ptType
+	}
+	//
+	if len(fields) > 1 {
+		Fields = fmt.Sprintf(`"%s"`, fields[0])
+		FieldsDetail = fmt.Sprintf(`"%s"`, strings.Join(fields, `","`))
+	}
+	//
+	if bwc > 0 {
+		BWCount = bwc
+	}
+	if bwdc > 0 {
+		BWDCount = bwdc
+	}
+}
+
+// AnalysisRequestParam 接口原请求参数
+type AnalysisRequestParam struct {
+	KeysItemsStr   string //分析内容【字符串】结构和o_member_jy.a_items保持一致
+	RangeTime      string //时间【字符串】 时间戳开始-结束时间戳
+	RangeTimeExtra string //时间【字符串】前段回显使用
+	Area           string //省份【对象字符串】
+	Industry       string //行业【对象字符串】
+	BuyerClass     string //采购单位类型【字符串】多个采购单位类型用逗号拼接
+	Buyer          string //采购单位
+	Winner         string //中标单位
+	Sort           int    //排序:默认0:成交时间倒序;1:项目金额倒序
+	PageSize       int    //默认每页10条
+	PageNum        int    //默认当前第一页
+	IsDetail       bool   //是否是项目明细请求
+	MatchingMode   string //匹配方式 title:标题 content:项目名称/标的物
+}
+
+type viewKeyWord struct {
+	Keyword  []string `json:"key"`       //关键词
+	Appended []string `json:"appendkey"` //附加词
+	Exclude  []string `json:"notkey"`    //排除词
+	MatchWay int      `json:"matchway"`  //匹配模式
+}
+
+// keyWordGroup 订阅词结构体
+type keyWordGroup struct {
+	A_Key      []viewKeyWord `json:"a_key"`
+	ItemName   string        `json:"s_item"`
+	UpdateTime int64         `json:"updatetime"`
+}
+
+// AnalysisRequestFormat 格式化后参数
+type AnalysisRequestFormat struct {
+	KeysItems    []keyWordGroup
+	Area, City   []string //省份城市
+	STime, ETime int64    //开始结束时间
+	Industry     []string //行业
+	BuyerClass   []string //采购单位类型
+	Buyer        string   //采购单位
+	Winner       string   //中标单位
+	Sort         int      //排序:默认0:成交时间倒序;1:项目金额倒序
+	PageSize     int      //默认每页10条
+	PageNum      int      //默认当前第一页
+}
+
+type AnalysisEntity struct {
+	MgoRecordId   string
+	BaseParam     AnalysisRequestParam
+	FormatParam   AnalysisRequestFormat
+	UId, Pid      string
+	ProjectInfo   projectInfo
+	Offline       int // 1-离线 2-实时
+	State         int // 状态:默认0:生成中;1:已生成;2:已取消;-1:生成失败
+	MgoUserId     string
+	Phone         string // 手机号
+	PositionId    int64
+	OriginalTotal int64 // 数据总数
+	KeysTotal     int64 //关键词数量
+	PositionType  int
+	EntId         int
+	EntUserId     int
+	Source        string
+	Mgo           *mongodb.MongodbSim
+	MySql         *mysql.Mysql
+}
+
+type projectInfo struct {
+	Count int64
+	List  []ProjectList
+}
+
+type ProjectList struct {
+	Name       string   `json:"name"`       //项目名称
+	Id         string   `json:"id"`         //项目id
+	Area       string   `json:"area"`       //地区
+	DealTime   int64    `json:"dealTime"`   //成交时间
+	BidStatus  string   `json:"bidStatus"`  //项目类型
+	BuyerClass string   `json:"buyerClass"` //采购单位类型
+	Winner     []string `json:"winner"`     //中标单位
+	WinnerId   []string `json:"winnerId"`   //中标单位id
+	Buyer      string   `json:"buyer"`      //采购单位
+	BidAmount  float64  `json:"bidAmount"`  //中标金额
+	Budget     float64  `json:"budget"`     //预算
+}
+
+// ForMatData 获取格式化请求参数
+func (a *AnalysisEntity) ForMatData() error {
+	//格式化订阅词
+	if err := json.Unmarshal([]byte(a.BaseParam.KeysItemsStr), &a.FormatParam.KeysItems); err != nil {
+		return fmt.Errorf("关键词组格式异常")
+	}
+	if a.FormatParam.KeysItems == nil || len(a.FormatParam.KeysItems) == 0 {
+		return fmt.Errorf("请选择关键词组")
+	}
+	var flag bool
+	// 判断关键词是不是为空
+	for i := 0; i < len(a.FormatParam.KeysItems); i++ {
+		items := a.FormatParam.KeysItems[i]
+		for j := 0; j < len(items.A_Key); j++ {
+			AKey := items.A_Key[j]
+			if len(AKey.Keyword) > 0 {
+				flag = true
+				break
+			}
+		}
+		if flag {
+			break
+		}
+	}
+	if !flag {
+		return fmt.Errorf("请选择关键词组")
+	}
+	//格式化时间段
+	if timeArr := strings.Split(a.BaseParam.RangeTime, "-"); len(timeArr) == 2 {
+		a.FormatParam.STime = common.Int64All(timeArr[0])
+		a.FormatParam.ETime = common.Int64All(timeArr[1])
+		if a.FormatParam.STime == 0 || a.FormatParam.ETime == 0 {
+			return fmt.Errorf("开始时间和结束时间不能为空")
+		}
+	} else {
+		return fmt.Errorf("时间戳格式异常")
+	}
+	//格式化省份、城市
+	if areaStr := strings.TrimSpace(a.BaseParam.Area); areaStr != "" {
+		imap := map[string][]string{}
+		if err := json.Unmarshal([]byte(a.BaseParam.Area), &imap); err != nil {
+			return fmt.Errorf("非法地区信息")
+		}
+		var city, area []string
+		for name, v := range imap {
+			if len(v) == 0 {
+				area = append(area, name)
+			} else {
+				for _, vv := range v {
+					city = append(city, vv)
+				}
+			}
+		}
+		a.FormatParam.Area = area
+		a.FormatParam.City = city
+	}
+	//格式化行业
+	if industryStr := strings.TrimSpace(a.BaseParam.Industry); industryStr != "" {
+		imap := map[string][]string{}
+		if err := json.Unmarshal([]byte(industryStr), &imap); err != nil {
+			return fmt.Errorf("非法行业信息")
+		}
+		var farr []string
+		for name, v := range imap {
+			for _, vv := range v {
+				farr = append(farr, fmt.Sprintf("%s_%s", name, vv))
+			}
+		}
+		if len(farr) > 0 {
+			//P510 行业:其它
+			if qt := jy.IndustryHandle(strings.Join(farr, ",")); len(qt) > 0 {
+				farr = append(farr, qt...)
+			}
+		}
+		a.FormatParam.Industry = farr
+	}
+	//格式化类型
+	if buyerClassStr := strings.TrimSpace(a.BaseParam.BuyerClass); buyerClassStr != "" {
+		a.FormatParam.BuyerClass = strings.Split(buyerClassStr, ",")
+	}
+	//中标企业
+	a.FormatParam.Winner = a.BaseParam.Winner
+	//采购单位
+	a.FormatParam.Buyer = a.BaseParam.Buyer
+	//排序
+	a.FormatParam.Sort = common.If(a.BaseParam.Sort != 0 && a.BaseParam.Sort != 1, 0, a.BaseParam.Sort).(int)
+	if a.BaseParam.PageNum*a.BaseParam.PageSize > ProjectCount {
+		a.BaseParam.PageNum = ProjectCount / a.BaseParam.PageSize
+	}
+	//当前页码
+	a.FormatParam.PageNum = common.If(a.BaseParam.PageNum < 1 || a.BaseParam.PageNum > 1000, 1, a.BaseParam.PageNum).(int)
+	//默认每页10条
+	a.FormatParam.PageSize = common.If(a.BaseParam.PageSize < 1 || a.BaseParam.PageSize > 100, 50, a.BaseParam.PageSize).(int)
+	return nil
+}
+
+// ForMatData 获取格式化请求参数
+func (a *AnalysisEntity) ForMatDataPdf() (string, error) {
+	//格式化订阅词
+	if err := json.Unmarshal([]byte(a.BaseParam.KeysItemsStr), &a.FormatParam.KeysItems); err != nil {
+		return "", fmt.Errorf("关键词组格式异常")
+	}
+
+	//格式化时间段
+	if timeArr := strings.Split(a.BaseParam.RangeTime, "-"); len(timeArr) == 2 {
+		a.FormatParam.STime = common.Int64All(timeArr[0])
+		a.FormatParam.ETime = common.Int64All(timeArr[1])
+		if a.FormatParam.STime == 0 || a.FormatParam.ETime == 0 {
+			return "", fmt.Errorf("开始时间和结束时间不能为空")
+		}
+	} else {
+		return "", fmt.Errorf("时间戳格式异常")
+	}
+	//格式化省份、城市
+	if areaStr := strings.TrimSpace(a.BaseParam.Area); areaStr != "" {
+		imap := map[string][]string{}
+		if err := json.Unmarshal([]byte(a.BaseParam.Area), &imap); err != nil {
+			return "", fmt.Errorf("非法地区信息")
+		}
+		var city, area []string
+		for name, v := range imap {
+			if len(v) == 0 {
+				area = append(area, name)
+			} else {
+				for _, vv := range v {
+					city = append(city, vv)
+				}
+			}
+		}
+		a.FormatParam.Area = area
+		a.FormatParam.City = city
+	}
+	//格式化行业
+	if industryStr := strings.TrimSpace(a.BaseParam.Industry); industryStr != "" {
+		imap := map[string][]string{}
+		if err := json.Unmarshal([]byte(industryStr), &imap); err != nil {
+			return "", fmt.Errorf("非法行业信息")
+		}
+		var farr []string
+		for name, v := range imap {
+			for _, vv := range v {
+				farr = append(farr, fmt.Sprintf("%s_%s", name, vv))
+			}
+		}
+		a.FormatParam.Industry = farr
+	}
+	//格式化类型
+	if buyerClassStr := strings.TrimSpace(a.BaseParam.BuyerClass); buyerClassStr != "" {
+		a.FormatParam.BuyerClass = strings.Split(buyerClassStr, ",")
+	}
+	//中标企业
+	a.FormatParam.Winner = a.BaseParam.Winner
+	//采购单位
+	a.FormatParam.Buyer = a.BaseParam.Buyer
+	//排序
+	a.FormatParam.Sort = common.If(a.BaseParam.Sort != 0 && a.BaseParam.Sort != 1, 0, a.BaseParam.Sort).(int)
+	if a.BaseParam.PageNum*a.BaseParam.PageSize > ProjectCount {
+		a.BaseParam.PageNum = ProjectCount / a.BaseParam.PageSize
+	}
+	//当前页码
+	a.FormatParam.PageNum = common.If(a.BaseParam.PageNum < 1 || a.BaseParam.PageNum > 1000, 1, a.BaseParam.PageNum).(int)
+	//默认每页10条
+	a.FormatParam.PageSize = common.If(a.BaseParam.PageSize < 1 || a.BaseParam.PageSize > 100, 50, a.BaseParam.PageSize).(int)
+	data := map[string]interface{}{
+		"s_keysItems":      a.BaseParam.KeysItemsStr,
+		"s_rangeTime":      a.BaseParam.RangeTime,
+		"s_rangeTimeExtra": a.BaseParam.RangeTimeExtra,
+		"s_area":           a.BaseParam.Area,
+		"s_industry":       a.BaseParam.Industry,
+		"s_buyerClass":     a.BaseParam.BuyerClass,
+		"s_matchingMode":   a.BaseParam.MatchingMode,
+		"s_userId":         a.UId,
+		"s_parentId":       a.Pid,
+		"s_mgoUserId":      a.MgoUserId,
+		"i_positionId":     a.PositionId,
+		"s_phone":          a.Phone,
+		"audit":            1,
+	}
+	if a.OriginalTotal > 0 {
+		data["l_originalTotal"] = a.OriginalTotal
+	}
+	if a.Source != "" {
+		data["source"] = a.Source
+	}
+	rs, b := a.Mgo.FindOne(ReportHistoryTable, data)
+	if b && rs != nil && len(*rs) > 0 {
+		return common.InterfaceToStr((*rs)["_id"]), errors.New("当期分析已存在")
+	}
+	return "", nil
+}
+
+// GetProjectInfoList 项目明细
+func (a *AnalysisEntity) GetProjectInfoList() error {
+	var (
+		queryDefault = `,"sort": [{%s}],"from": %d,"size": %d`
+		start        = (a.FormatParam.PageNum - 1) * a.FormatParam.PageSize
+		sort         = `"jgtime": "desc"`
+	)
+	if a.FormatParam.Sort > 0 {
+		sort = `"bidamount": "desc","budget": "desc"`
+	}
+	countSql := fmt.Sprintf(a.GetCommonQuerySql(), "")
+	queryDefault = fmt.Sprintf(queryDefault, sort, start, a.FormatParam.PageSize)
+	finalSql := fmt.Sprintf(a.GetCommonQuerySql(), queryDefault)
+	log.Println("finalSql:", finalSql)
+	count, hits := elastic.GetWithCount(PtIndex, PtType, countSql, finalSql)
+	//hits, count := elastic.GetOA(PtIndex, PtType, finalSql)
+	if count > 0 {
+		a.ProjectInfo.Count = count
+		source := *hits
+		for _, v := range source {
+			var winnerIdArr []string
+			if common.ObjToString(v["s_winner"]) != "" && v["entidlist"] != nil {
+				idObjs, _ := v["entidlist"].([]interface{})
+				for _, v := range common.ObjArrToStringArr(idObjs) {
+					if v != "" && v != "-" {
+						v = encrypt.EncodeArticleId2ByCheck(v)
+					}
+					winnerIdArr = append(winnerIdArr, v)
+				}
+			}
+			a.ProjectInfo.List = append(a.ProjectInfo.List, ProjectList{
+				Name:       common.ObjToString(v["projectname"]),
+				Id:         encrypt.EncodeArticleId2ByCheck(common.ObjToString(v["id"])),
+				Area:       common.ObjToString(v["area"]),
+				DealTime:   common.Int64All(v["jgtime"]), //截止时间
+				BidStatus:  common.ObjToString(v["bidstatus"]),
+				BuyerClass: common.ObjToString(v["buyerclass"]),
+				Winner:     strings.Split(common.ObjToString(v["s_winner"]), ","),
+				WinnerId:   winnerIdArr,
+				Buyer:      common.ObjToString(v["buyer"]),
+				BidAmount:  common.Float64All(v["bidamount"]), //中标金额
+				Budget:     common.Float64All(v["budget"]),    //预算
+			})
+		}
+	}
+	return nil
+}
+
+// SaveAnalysisRecord 保存分析记录
+func (a *AnalysisEntity) SaveAnalysisRecord() error {
+	if a.Offline == ValueRealTime {
+		a.State = ReportStateGenerated
+	} else {
+		a.State = ReportStateGenerating
+	}
+	data := map[string]interface{}{
+		"s_keysItems":      a.BaseParam.KeysItemsStr,
+		"s_rangeTime":      a.BaseParam.RangeTime,
+		"s_rangeTimeExtra": a.BaseParam.RangeTimeExtra,
+		"s_area":           a.BaseParam.Area,
+		"s_industry":       a.BaseParam.Industry,
+		"s_buyerClass":     a.BaseParam.BuyerClass,
+		"s_matchingMode":   a.BaseParam.MatchingMode,
+		"s_userId":         a.UId,
+		"s_parentId":       a.Pid,
+		"i_state":          common.If(a.Source == "analysisPDF", 1, a.State), //状态:默认0:生成中;1:已生成;2:已取消;-1:生成失败
+		"l_updateTime":     time.Now().Unix(),                                //生成时间 or 取消时间
+		"l_createTime":     time.Now().Unix(),
+		"i_offline":        common.If(a.Source == "analysisPDF", 2, a.Offline),
+		"s_mgoUserId":      a.MgoUserId,
+		"i_positionId":     a.PositionId,
+		"s_phone":          a.Phone,
+	}
+	if a.OriginalTotal > 0 {
+		data["l_originalTotal"] = a.OriginalTotal
+	}
+	if a.KeysTotal > 0 {
+		data["l_keysTotal"] = a.KeysTotal
+	}
+	if a.Source != "" {
+		data["source"] = a.Source
+	}
+	a.MgoRecordId = a.Mgo.Save(ReportHistoryTable, data)
+	if a.MgoRecordId == "" {
+		return fmt.Errorf("分析创建异常")
+	}
+	return nil
+}
+
+// GetAnalysisFromMgoDb 从数据库中获取分析记录
+func (a *AnalysisEntity) GetAnalysisFromMgoDb() error {
+	if a.MgoRecordId == "" {
+		return fmt.Errorf("缺少参数")
+	}
+	queryMap := map[string]interface{}{
+		"_id":   mongodb.StringTOBsonId(a.MgoRecordId),
+		"i_del": map[string]interface{}{"$ne": 1},
+	}
+	if a.UId == a.Pid { //主账号
+		queryMap["s_parentId"] = a.Pid
+	} else {
+		queryMap["s_userId"] = a.UId
+	}
+	res, _ := a.Mgo.FindOne(ReportHistoryTable, queryMap)
+	if res == nil || len(*res) == 0 {
+		return fmt.Errorf("未查询到相关数据")
+	}
+	a.Offline = common.IntAll((*res)["i_offline"])
+	a.State = common.IntAll((*res)["i_state"])
+	a.BaseParam.KeysItemsStr, _ = (*res)["s_keysItems"].(string)
+	a.BaseParam.RangeTime, _ = (*res)["s_rangeTime"].(string)
+	a.BaseParam.RangeTimeExtra, _ = (*res)["s_rangeTimeExtra"].(string)
+	a.BaseParam.Area, _ = (*res)["s_area"].(string)
+	a.BaseParam.Industry, _ = (*res)["s_industry"].(string)
+	a.BaseParam.BuyerClass, _ = (*res)["s_buyerClass"].(string)
+	a.BaseParam.MatchingMode, _ = (*res)["s_matchingMode"].(string)
+	return nil
+}
+
+// removeEmptyRecord 删除空报告记录
+func (a *AnalysisEntity) removeEmptyRecord() {
+	queryMap := map[string]interface{}{
+		"_id": mongodb.StringTOBsonId(a.MgoRecordId),
+	}
+	if a.UId == a.Pid { //主账号
+		queryMap["s_parentId"] = a.Pid
+	} else {
+		queryMap["s_userId"] = a.UId
+	}
+	a.Mgo.Update(ReportHistoryTable, queryMap, map[string]interface{}{
+		"$set": map[string]interface{}{"i_del": 1},
+	}, false, false)
+	//log.Println("删除空报告", queryMap)
+}
+
+// GetRecordList 获取分析记录
+func (a *AnalysisEntity) GetRecordList(pageNum, PageSize int, positionType int, entId, entUserId int) (total int, list []map[string]interface{}) {
+	queryMap := map[string]interface{}{
+		"i_del": map[string]interface{}{"$ne": 1},
+	}
+	if a.UId == a.Pid { //主账号
+		queryMap["s_parentId"] = a.Pid
+	} else {
+		queryMap["s_userId"] = a.UId
+	}
+	if pageNum == 1 {
+		total = a.Mgo.Count(ReportHistoryTable, queryMap)
+		if total == 0 {
+			return
+		}
+	} else {
+		total = -1
+	}
+	res, _ := a.Mgo.Find(ReportHistoryTable, queryMap, `{"l_createTime":-1}`, nil, false, (pageNum-1)*PageSize, PageSize)
+	if res == nil || len(*res) == 0 {
+		return
+	}
+	var ids []string
+	for _, m := range *res {
+		ids = append(ids, common.InterfaceToStr(m["_id"]))
+	}
+	idMap := make(map[string]map[string]interface{})
+	tName, _ := GetMongoColl(MarketScaleMain)
+	mids, _ := a.Mgo.Find(tName, map[string]interface{}{
+		"s_m_id": map[string]interface{}{
+			"$in": ids,
+		},
+	}, "", `{"market_profile":1,"s_m_id":1}`, false, -1, -1)
+	if mids != nil && len(*mids) > 0 {
+		for _, m := range *mids {
+			marketProfile, _ := m["market_profile"].(map[string]interface{})
+			if common.IntAll(marketProfile["project_count"]) > 0 {
+				idMap[common.InterfaceToStr(m["s_m_id"])] = marketProfile
+			}
+		}
+	}
+
+	//用户消息开关
+	open := GetMsgOpen(a.Mgo, a.MgoUserId, positionType, entId, entUserId)
+	for _, row := range *res {
+		var status int
+		if row["i_state"] != nil {
+			status = common.IntAll(row["i_state"])
+			if status == ReportStateFailed {
+				status = ReportStateGenerating // 生成失败对外还是展示为生成中
+			}
+		}
+		var (
+			isDownload bool
+		)
+		marketProfile := make(map[string]interface{})
+		if marketProfile = idMap[mongodb.BsonIdToSId(row["_id"])]; marketProfile != nil && common.IntAll(marketProfile["project_count"]) > 0 && status == 1 {
+			isDownload = true
+		}
+		//P510  回显 移除 其它
+		industry := common.ObjToString(row["s_industry"])
+		if industry != "" {
+			industry = strings.ReplaceAll(industry, "\"其它\"", "")
+		}
+		data := map[string]interface{}{
+			"id":               encodeId(mongodb.BsonIdToSId(row["_id"])),
+			"keysItems":        common.ObjToString(row["s_keysItems"]),
+			"area":             common.ObjToString(row["s_area"]),
+			"industry":         industry,
+			"buyerclass":       common.ObjToString(row["s_buyerClass"]),
+			"rangeTime":        common.ObjToString(row["s_rangeTime"]),
+			"s_rangeTimeExtra": common.ObjToString(row["s_rangeTimeExtra"]),
+			"createTime":       common.Int64All(row["l_createTime"]),
+			"matchingMode":     common.ObjToString(row["s_matchingMode"]), //项目匹配方式
+			"state":            common.If(row["i_state"] == nil, nil, status),
+			"updateTime":       common.Int64All(row["l_updateTime"]),
+			"msgOpen":          open,
+			"isDownload":       isDownload,
+			"marketProfile":    marketProfile,
+		}
+		list = append(list, data)
+	}
+	return
+}
+
+func (a *AnalysisEntity) GetRecordPdfList(pageNum, PageSize int) (total int, list []map[string]interface{}) {
+	queryMap := map[string]interface{}{
+		"i_del":  map[string]interface{}{"$ne": 1},
+		"source": "analysisPDF",
+	}
+	if a.UId == a.Pid { //主账号
+		queryMap["s_parentId"] = a.Pid
+	} else {
+		queryMap["s_userId"] = a.UId
+	}
+	if pageNum == 1 {
+		total = a.Mgo.Count(ReportHistoryTable, queryMap)
+		if total == 0 {
+			return
+		}
+	} else {
+		total = -1
+	}
+	res, _ := a.Mgo.Find(ReportHistoryTable, queryMap, `{"l_createTime":-1}`, nil, false, (pageNum-1)*PageSize, PageSize)
+	if res == nil || len(*res) == 0 {
+		return
+	}
+	for _, row := range *res {
+		data := map[string]interface{}{
+			"id":               encodeId(mongodb.BsonIdToSId(row["_id"])),
+			"keysItems":        common.ObjToString(row["s_keysItems"]),
+			"area":             common.ObjToString(row["s_area"]),
+			"industry":         common.ObjToString(row["s_industry"]),
+			"buyerclass":       common.ObjToString(row["s_buyerClass"]),
+			"rangeTime":        common.ObjToString(row["s_rangeTime"]),
+			"s_rangeTimeExtra": common.ObjToString(row["s_rangeTimeExtra"]),
+			"createTime":       common.Int64All(row["l_createTime"]),
+			"matchingMode":     common.ObjToString(row["s_matchingMode"]), //项目匹配方式
+			"updateTime":       common.Int64All(row["l_updateTime"]),
+			"audit":            common.Int64All(row["audit"]),
+		}
+		list = append(list, data)
+	}
+	return
+}
+
+// GetQueryItem 获取查询条件,前端回显使用
+func (a *AnalysisEntity) getQueryItem() (map[string]interface{}, error) {
+	return map[string]interface{}{
+		"keysItems":        a.BaseParam.KeysItemsStr,
+		"area":             a.BaseParam.Area,
+		"industry":         a.BaseParam.Industry,
+		"buyerclass":       a.BaseParam.BuyerClass,
+		"rangeTime":        a.BaseParam.RangeTime,
+		"s_rangeTimeExtra": a.BaseParam.RangeTimeExtra,
+		"matchingMode":     a.BaseParam.MatchingMode,
+	}, nil
+}
+
+// GetPartResult 分块儿获取报告内容
+func (a *AnalysisEntity) GetPartResult(flag int) (map[string]interface{}, error) {
+	defer common.Catch()
+	if flag == MarketQueryItem { //返回查询内容
+		return a.getQueryItem()
+	}
+	thisCacheKey := fmt.Sprintf(ReportCacheKey, a.MgoRecordId, flag)
+	if cacheData := redis.Get(ReportCacheDB, thisCacheKey); cacheData != nil {
+		if cacheMap, ok := cacheData.(map[string]interface{}); ok && len(cacheMap) > 0 {
+			return cacheMap, nil
+		}
+	}
+	rData, err := func() (map[string]interface{}, error) {
+		//控制并发&&超时返回超时异常
+		select {
+		case <-time.After(time.Duration(MATimeout) * time.Second * 20):
+			return nil, fmt.Errorf("查询超时,请稍后重试")
+		case <-MAPool:
+		}
+		start := time.Now()
+		defer func() {
+			MAPool <- true
+			log.Printf("report %s[%d] speed %d ms\n", a.MgoRecordId, flag, time.Now().Sub(start).Milliseconds())
+		}()
+		//校验报告是否合法
+		//if flag != MarketScaleMain {
+		//	if exists, _ := redis.Exists(ReportCacheDB, fmt.Sprintf(ReportCacheKey, a.MgoRecordId, 1)); !exists {
+		//		return nil, fmt.Errorf("报告异常请求,请刷新重试")
+		//	}
+		//}
+		// 1. 查mongo
+		var rData map[string]interface{}
+		var err error
+		rData, err = a.GetMongoData(flag)
+		// 查到数据则直接返回
+		if err == nil && rData != nil {
+			return rData, nil
+		}
+		// 2.没有查询到数据 判断是不是离线的
+		//  正常情况下正在离线生的不会走到这里
+		// 离线的应该生成完报告之后才会调用获取结果接口 这里处理是防止直接调接口传正在离线生的报告
+		if a.Offline == ValueOffline || a.Offline == 0 {
+			return nil, err
+		}
+		// 3. 实时则查es
+		rData, err = a.RealTimeQuery(flag)
+		if err == nil && len(rData) > 0 {
+			// 4.存库
+			a.SaveMongoReport(rData, flag)
+			return rData, err
+		}
+		return nil, err
+	}()
+	if err == nil && rData != nil && len(rData) > 0 {
+		delete(rData, "s_m_id")
+		delete(rData, "_id")
+		redis.Put(ReportCacheDB, thisCacheKey, rData, ReportCacheTime)
+	}
+	return rData, err
+}
+
+// 离线  获取采购单位和中部单位数据
+func (a *AnalysisEntity) BWData() (buyers []string, winners []string) {
+	sql := `"buyer_terms": {"terms": {"field": "buyer","size": 50000}},"winner_terms": {"terms": {"field": "s_winner","size": 50000}}`
+	finalSql := fmt.Sprintf(a.GetCommonQuerySqlWithAggs(), sql)
+	res := GetAggs(PtIndex, PtType, finalSql)
+	if res == nil || len(res) == 0 {
+		return
+	}
+	var bw = BWBuckets{}
+	for name, object := range res {
+		bArr, err := object.MarshalJSON()
+		if len(bArr) == 0 || err != nil {
+			continue
+		}
+		if name == "buyer_terms" {
+			if json.Unmarshal(bArr, &bw.Buyerterms) != nil {
+				continue
+			}
+		} else if name == "winner_terms" {
+			if json.Unmarshal(bArr, &bw.Winnerterms) != nil {
+				continue
+			}
+		}
+	}
+	for _, bv := range bw.Buyerterms.Buckets {
+		buyers = append(buyers, bv.Key)
+	}
+	for _, wv := range bw.Winnerterms.Buckets {
+		winners = append(winners, wv.Key)
+	}
+	return
+}
+
+// 实时查询
+func (a *AnalysisEntity) RealTimeQuery(flag int) (map[string]interface{}, error) {
+	switch flag {
+	case MarketScaleMain:
+		rData, err := a.MarketTime()
+		//非离线
+		if a.Offline != ValueOffline && err != nil { //若无报告内容,删除报告记录
+			go a.removeEmptyRecord()
+		}
+		if rData != nil && a.Offline == ValueOffline {
+			//采购单位和中部单位 数据
+			rData["buyers"], rData["winners"] = a.BWData()
+		}
+		return rData, err
+	case MarketTopProject:
+		return a.ProjectTop10()
+	case MarketProjectAllData:
+		return a.AllData()
+	case MarketScaleRefine:
+		return a.MarketScaleRefineQuery()
+	case MarketBuyerAndWinner:
+		return a.BuyerWinnerAnalysis(), nil
+	}
+	return nil, fmt.Errorf("未知请求")
+}
+
+// GetMongoData 从mongo库查询数据
+func (a *AnalysisEntity) GetMongoData(flag int) (map[string]interface{}, error) {
+	collName, err := GetMongoColl(flag)
+	if err != nil || collName == "" {
+		return nil, fmt.Errorf("未知请求")
+	}
+	// 查询
+	query := map[string]interface{}{
+		"s_m_id": a.MgoRecordId,
+	}
+	data, b := a.Mgo.FindOne(collName, query)
+	if !b || data == nil || len(*data) == 0 {
+		log.Println("没有查询到数据", b, query, collName)
+		return nil, nil
+	}
+	delete(*data, "s_m_id")
+	delete(*data, "_id")
+	return *data, nil
+}
+
+// GetMongoColl 获取mgo库对应的Coll
+func GetMongoColl(flag int) (string, error) {
+	switch flag {
+	case MarketScaleMain:
+		return CollMarketScaleMain, nil
+	case MarketTopProject:
+		return CollMarketTopProject, nil
+	case MarketProjectAllData:
+		return CollMarketProjectAllData, nil
+	case MarketScaleRefine:
+		return CollMarketScaleRefine, nil
+	case MarketBuyerAndWinner:
+		return CollMarketBuyerAndWinner, nil
+	}
+	return "", fmt.Errorf("未知类型")
+}
+
+// SaveMongoReport 数据存mongo库
+func (a *AnalysisEntity) SaveMongoReport(rData map[string]interface{}, flag int) {
+	collName, _ := GetMongoColl(flag)
+	rData["s_m_id"] = a.MgoRecordId
+	b, err := json.Marshal(rData)
+	if err != nil {
+		log.Println("JSON marshal error:", err)
+	} else {
+		var saveMap map[string]interface{}
+		err = json.Unmarshal(b, &saveMap)
+		if err != nil {
+			log.Println("JSON Unmarshal error:", err)
+			a.Mgo.Save(collName, rData)
+		} else {
+			a.Mgo.Save(collName, saveMap)
+		}
+	}
+}
+
+// GetAnalyzingReport 是否有正在分析的离线报告
+func (a *AnalysisEntity) GetAnalyzingReport() string {
+	query := map[string]interface{}{
+		"s_userId": a.UId,
+		"i_state":  map[string]interface{}{"$in": []int{ReportStateGenerating, ReportStateFailed}}, //状态:默认0:生成中;1:已生成;2:已取消;-1:生成失败
+		"i_del":    map[string]interface{}{"$ne": 1},
+	}
+	//log.Println("query:", query)
+	rs, b := a.Mgo.FindOne(ReportHistoryTable, query)
+	//log.Println("rs,b:", rs, b)
+	if b && rs != nil {
+		return mongodb.BsonIdToSId((*rs)["_id"])
+	}
+	return ""
+}
+
+// IsOffline 判断是否符合在线分析的条件
+func (a *AnalysisEntity) IsOffline() {
+	//离线生成:订阅词(关键词+排除词)超过300个(数量支持配置),或单次分析数据超过60万条,则离线生成;
+	//  查询数据量
+	countSql := fmt.Sprintf(a.GetCommonQuerySql(), "")
+	log.Println(a.Phone, "IsOffline count SQL:", countSql)
+	now := time.Now()
+	dataCount := elastic.Count(PtIndex, PtType, countSql)
+	log.Println(a.Phone, "--IsOffline 数据量:", dataCount, "---", MaxPCount)
+	if dataCount > int64(MaxPCount) { //提示用户 数据量过大,修改生成报告条件
+		a.Offline = ValueError
+		return
+	}
+	a.OriginalTotal = dataCount
+	keyCount := 0
+	for i := 0; i < len(a.FormatParam.KeysItems); i++ {
+		items := a.FormatParam.KeysItems[i]
+		for j := 0; j < len(items.A_Key); j++ {
+			AKey := items.A_Key[j]
+			for k := 0; k < len(AKey.Keyword); k++ {
+				keyCount++
+			}
+			for k := 0; k < len(AKey.Appended); k++ {
+				keyCount++
+			}
+			for k := 0; k < len(AKey.Exclude); k++ {
+				keyCount++
+			}
+		}
+	}
+	a.KeysTotal = int64(keyCount)
+	//关键词数量 < 100 ,默认走线上
+	if keyCount < MinKeyWordsCount {
+		a.Offline = ValueRealTime
+		return
+	}
+	if keyCount > MAKeyWordsCount {
+		// 查询配置
+		if mac := a.getMarUserAccount(); mac != nil {
+			if keyCount >= mac.Threshold {
+				a.Offline = ValueOffline
+				return
+			}
+		} else {
+			a.Offline = ValueOffline
+			return
+		}
+	}
+	log.Println("IsOffline 统计数据量耗时:", time.Since(now))
+	if int(dataCount) > MAProjectNumLimit {
+		a.Offline = ValueOffline
+		return
+	}
+	a.Offline = ValueRealTime
+	return
+}
+
+type MarUserAccount struct {
+	Threshold int // 关键词离线标准量
+}
+
+// 获取离线市场报告分析关键词标准信息
+func (a *AnalysisEntity) getMarUserAccount() *MarUserAccount {
+	rs := a.MySql.SelectBySql(fmt.Sprintf("SELECT threshold FROM %s where  position_id=? and state = 0;", TablejianyuMarUserAccount), a.PositionId)
+	if rs != nil && len(*rs) > 0 {
+		return &MarUserAccount{Threshold: common.IntAll((*rs)[0]["threshold"])}
+	}
+	return nil
+}
+func (a *AnalysisEntity) GetReportState() (generated bool, needUpdate bool, err error) {
+	// 查库
+	err = a.GetAnalysisFromMgoDb()
+	if err != nil {
+		return
+	}
+	// 已经生成的可以直接返回
+	if a.State == ReportStateGenerated {
+		generated = true
+		return
+	}
+	// 该字段没有值 说明是还没有被更新的历史数据 需要更新
+	// 且判断之后后续需要更新i_state和i_offline字段
+	if a.Offline == 0 {
+		needUpdate = true
+	}
+	// 如果没有值的话 说明这是历史数据  需要接着走下面的流程进行判断
+	// 格式化数据 用于后面校验个数
+	if err = a.ForMatData(); err != nil {
+		return
+	}
+	return
+
+}
+
+// Cancel 取消正在分析中的报告
+func (a *AnalysisEntity) Cancel() (bool, error) {
+	// 取消报告
+	queryMap := map[string]interface{}{
+		"_id":     a.MgoRecordId,
+		"i_state": map[string]interface{}{"$in": []int{ReportStateGenerating, ReportStateFailed}}, //状态:默认0:生成中;1:已生成;2:已取消;-1:生成失败
+		"i_del":   map[string]interface{}{"$ne": 1},
+	}
+	if a.UId == a.Pid { //主账号
+		queryMap["s_parentId"] = a.Pid
+	} else {
+		queryMap["s_userId"] = a.UId
+	}
+	//验证
+	rs, b := a.Mgo.FindOne(ReportHistoryTable, queryMap)
+	//
+	if !b || rs == nil {
+		return false, fmt.Errorf("未查询到该记录")
+	}
+	update := a.Mgo.UpdateById(ReportHistoryTable, a.MgoRecordId, map[string]interface{}{"$set": map[string]interface{}{
+		"l_updateTime": time.Now().Unix(),
+		"i_state":      ReportStateCanceled,
+	}})
+	if !update {
+		log.Println("分析报告取消失败:", a.MgoRecordId)
+		return false, fmt.Errorf("取消失败")
+	}
+	// redis里面放取消标识
+	redis.Put(ReportCacheDB, fmt.Sprintf(ReportCanceledKey, a.MgoRecordId), 1, ReportCanceledTime)
+	return update, nil
+}
+
+// UpdateOffline 更新报告是否是离线报告
+func (a *AnalysisEntity) UpdateOffline(offline bool) bool {
+	set := map[string]interface{}{
+		"i_state":      common.If(offline, ReportStateGenerating, ReportStateGenerated),
+		"i_offline":    common.If(offline, ValueOffline, ValueRealTime),
+		"l_updateTime": time.Now().Unix(),
+		"s_mgoUserId":  a.MgoUserId, // 这里更新这些字段是因为这几个字段是p437 版本新加上的 历史数据没有这些字段
+		"s_positionId": a.PositionId,
+		"s_phone":      a.Phone,
+	}
+	if a.OriginalTotal > 0 {
+		set["l_originalTotal"] = a.OriginalTotal
+	}
+	data := map[string]interface{}{
+		"$set": set,
+	}
+	update := a.Mgo.UpdateById(ReportHistoryTable, a.MgoRecordId, data)
+	if !update {
+		log.Println("UpdateOffline 更新报告状态失败:", data, a.MgoRecordId)
+	}
+	return update
+}
+
+// UpdateState 更新报告生成状态
+func (a *AnalysisEntity) UpdateState(state int) bool {
+	setMap := map[string]interface{}{
+		"i_state":      state,
+		"l_updateTime": time.Now().Unix(),
+	}
+	if state == ReportStateGenerated {
+		setMap["l_finishTime"] = time.Now().Unix()
+	}
+	data := map[string]interface{}{
+		"$set": setMap,
+	}
+	update := a.Mgo.UpdateById(ReportHistoryTable, a.MgoRecordId, data)
+	if !update {
+		log.Println("UpdateState 更新报告生成状态失败:", data, a.MgoRecordId)
+	}
+	return update
+}
+
+// Delete 删除正在分析中的报告
+func (a *AnalysisEntity) Delete() (bool, error) {
+	// 删除报告
+	queryMap := map[string]interface{}{
+		"_id":   a.MgoRecordId,
+		"i_del": map[string]interface{}{"$ne": 1},
+	}
+	if a.UId == a.Pid { //主账号
+		queryMap["s_parentId"] = a.Pid
+	} else {
+		queryMap["s_userId"] = a.UId
+	}
+	//验证
+	rs, b := a.Mgo.FindOne(ReportHistoryTable, queryMap)
+	//
+	if !b || rs == nil {
+		return false, fmt.Errorf("未查询到该记录")
+	}
+	update := a.Mgo.UpdateById(ReportHistoryTable, a.MgoRecordId, map[string]interface{}{"$set": map[string]interface{}{
+		"l_updateTime": time.Now().Unix(),
+		"i_del":        1,
+	}})
+	if !update {
+		log.Println("分析报告删除失败:", a.MgoRecordId)
+		return false, fmt.Errorf("删除失败")
+	}
+	// redis里面放取消标识
+	redis.Put(ReportCacheDB, fmt.Sprintf(ReportCanceledKey, a.MgoRecordId), 1, ReportCanceledTime)
+	return update, nil
+}
+
+// GetCommonQuerySql 公共筛选
+func (a *AnalysisEntity) GetCommonQuerySql() string {
+	var musts, bools []string
+	//时间
+	musts = append(musts, fmt.Sprintf(`{"range":{"jgtime":{"gte":%d,"lte":%d}}}`, a.FormatParam.STime, a.FormatParam.ETime))
+	//地区
+	if len(a.FormatParam.Area) > 0 || len(a.FormatParam.City) > 0 {
+		var areaCity []string
+		if len(a.FormatParam.Area) > 0 {
+			areaCity = append(areaCity, fmt.Sprintf(`{"terms":{"area":["%s"]}}`, strings.Join(a.FormatParam.Area, `","`)))
+		}
+		if len(a.FormatParam.City) > 0 {
+			areaCity = append(areaCity, fmt.Sprintf(`{"terms":{"city":["%s"]}}`, strings.Join(a.FormatParam.City, `","`)))
+		}
+		musts = append(musts, fmt.Sprintf(`{"bool":{"should":[%s],"minimum_should_match": 1}}`, strings.Join(areaCity, ",")))
+	}
+	//行业
+	if len(a.FormatParam.Industry) > 0 {
+		musts = append(musts, fmt.Sprintf(`{"terms":{"subscopeclass":["%s"]}}`, strings.Join(a.FormatParam.Industry, `","`)))
+	}
+	//类型
+	if len(a.FormatParam.BuyerClass) > 0 {
+		musts = append(musts, fmt.Sprintf(`{"terms":{"buyerclass":["%s"]}}`, strings.Join(a.FormatParam.BuyerClass, `","`)))
+	}
+	//分析报告中标状态限制
+	musts = append(musts, fmt.Sprintf(queryBoolMust, pSearchDecMust))
+	//订阅词
+	for _, v := range getAllKeywordArr(a.FormatParam.KeysItems) {
+		if sql := getKeyWordSql(v, a.BaseParam.MatchingMode); sql != "" {
+			bools = append(bools, sql)
+		}
+	}
+	//中标企业
+	if a.FormatParam.Winner != "" {
+		var winnerId string
+		if mongodb.IsObjectIdHex(a.FormatParam.Winner) {
+			winnerId = a.FormatParam.Winner
+		} else {
+			rData := elastic.Get("qyxy", "qyxy", fmt.Sprintf(`{"query": {"bool": {"must": [{"term": {"company_name": "%s"}}]}},"_source":["_id"],"size": 1}`, a.FormatParam.Winner))
+			if rData != nil && len(*rData) > 0 {
+				winnerId = common.InterfaceToStr((*rData)[0]["_id"])
+			}
+		}
+		if winnerId != "" {
+			musts = append(musts, fmt.Sprintf(`{"term":{"entidlist":"%s"}}`, winnerId))
+		} else {
+			musts = append(musts, fmt.Sprintf(`{"term":{"s_winner":"%s"}}`, a.FormatParam.Winner))
+		}
+	}
+	//采购单位
+	if a.FormatParam.Buyer != "" {
+		musts = append(musts, fmt.Sprintf(`{"term":{"buyer":"%s"}}`, a.FormatParam.Buyer))
+	}
+	return fmt.Sprintf(`{"query":{"bool":{"must":[%s],"should":[%s],"minimum_should_match": %d}}%s}`, strings.Join(musts, ","), strings.Join(bools, ","), common.If(len(bools) > 0, 1, 0).(int), "%s")
+}
+
+// GetCommonQuerySqlWithAggs 此方法用于聚合查询
+func (a *AnalysisEntity) GetCommonQuerySqlWithAggs() string {
+	return fmt.Sprintf(a.GetCommonQuerySql(), `,"aggs":{%s},"size":0`)
+}
+
+// 市场概况+时间分布
+func (a *AnalysisEntity) MarketTime() (map[string]interface{}, error) {
+	var (
+		sql                   []string
+		monthB, yearB         bool
+		MonthRange, YearRange string
+		isOffline             = a.Offline == 1
+	)
+	sql = append(sql, fmt.Sprintf(aggsMarketAnalysis, "thismarket", fmt.Sprintf(`{"key":"%s","from":%d,"to":%d}`, "market", a.FormatParam.STime, a.FormatParam.ETime)))
+	n_stime := a.FormatParam.STime
+	var n_mae AnalysisEntity
+	n_mae.FormatParam = a.FormatParam
+	n_mae.BaseParam = a.BaseParam
+	n_mae.MgoRecordId = a.MgoRecordId
+	n_mae.FormatParam.ETime = a.FormatParam.ETime
+	if time.Unix(a.FormatParam.STime, 0).AddDate(1, 0, 0).Unix() >= time.Unix(a.FormatParam.ETime, 0).Unix() {
+		n_stime = getPreviousMarket(time.Unix(a.FormatParam.STime, 0), time.Unix(a.FormatParam.ETime, 0))
+		sql = append(sql, fmt.Sprintf(aggsMarketAnalysis, "oldmarket", fmt.Sprintf(`{"key":"%s","from":%d,"to":%d}`, "market", n_stime, a.FormatParam.STime)))
+	}
+
+	if isOffline || time.Unix(a.FormatParam.STime, 0).AddDate(0, 1, 0).Unix() < time.Unix(a.FormatParam.ETime, 0).Unix() {
+		var mon_time, year_time int64
+		stime, etime := time.Unix(a.FormatParam.STime, 0), time.Unix(a.FormatParam.ETime, 0)
+		monthB, MonthRange = GetMonthData(isOffline, stime, etime)
+		sql = append(sql, fmt.Sprintf(projectTimeDistribution, "monthtime", MonthRange))
+		if monthB {
+			mon_time = stime.AddDate(0, -1, 0).Unix()
+		} else {
+			mon_time = a.FormatParam.STime
+		}
+		//年度数据
+		yearB, YearRange = GetYearData(isOffline, stime, etime)
+		sql = append(sql, fmt.Sprintf(projectTimeDistribution, "yeartime", YearRange))
+		if yearB {
+			year_time = stime.AddDate(-1, 0, 0).Unix()
+		} else {
+			year_time = a.FormatParam.STime
+		}
+
+		if n_stime > mon_time {
+			n_stime = mon_time
+		}
+		if n_stime > year_time {
+			n_stime = year_time
+		}
+	}
+	//非离线
+	if !isOffline {
+		n_mae.FormatParam.STime = n_stime
+	}
+	finalSql := fmt.Sprintf(n_mae.GetCommonQuerySqlWithAggs(), strings.Join(sql, ","))
+	log.Printf("final MarketScaleRefineQuery sql: %s", finalSql)
+	rMap := make(map[string]interface{})
+	rMapData := make(map[string]interface{})
+	thisRow := marketTime{}
+	res := GetAggs(PtIndex, PtType, finalSql)
+	if res == nil || len(res) == 0 {
+		return nil, fmt.Errorf("未查询到项目")
+	}
+	Rest(res, &thisRow)
+	if thisRow.Thismarket.Buckets != nil && len(thisRow.Thismarket.Buckets) != 0 {
+		Projectmarket := thisRow.Thismarket.Buckets[0]
+		if !isOffline && Projectmarket.ProjectCount.DocCount == 0 {
+			return nil, fmt.Errorf("未查询到项目数据")
+		}
+		// 实时分析数据量 如果关键词少于100,项目数据量可能大于MAProjectNumLimit(600000)
+		//if Projectmarket.ProjectCount.DocCount > MAProjectNumLimit {
+		//	return nil, fmt.Errorf("项目数量超出上限")
+		//}
+		rMapData["project_count"] = Projectmarket.ProjectCount.DocCount
+		rMapData["projctamout"] = Projectmarket.ProjectAmount.Value
+		rMapData["projectavgmoney"] = Projectmarket.ProjectAvgMoney.Value
+		rMapData["buyercount"] = Projectmarket.BuyerCount.Value
+		rMapData["winnercount"] = Projectmarket.WinnerCount.Value
+		if thisRow.Oldmarket.Buckets != nil && len(thisRow.Oldmarket.Buckets) != 0 {
+			oldProjectmarket := thisRow.Oldmarket.Buckets[0]
+			//环比数据
+			rMapData["projctamount_ratio"] = sequential(Projectmarket.ProjectAmount.Value, oldProjectmarket.ProjectAmount.Value)
+			rMapData["project_count_ratio"] = sequential(float64(Projectmarket.ProjectCount.DocCount), float64(oldProjectmarket.ProjectCount.DocCount))
+			rMapData["projectavgmoney_ratio"] = sequential(Projectmarket.ProjectAvgMoney.Value, oldProjectmarket.ProjectAvgMoney.Value)
+			rMapData["buyercount_ratio"] = sequential(float64(Projectmarket.BuyerCount.Value), float64(oldProjectmarket.BuyerCount.Value))
+			rMapData["winnercount_ratio"] = sequential(float64(Projectmarket.WinnerCount.Value), float64(oldProjectmarket.WinnerCount.Value))
+		}
+	}
+	rMap["market_profile"] = rMapData
+
+	if thisRow.Monthtime.Buckets != nil && len(thisRow.Monthtime.Buckets) != 0 {
+		rMap["month_distribution"] = n_mae.TimeData(monthB, thisRow.Monthtime.Buckets)
+	}
+	if thisRow.Yeartime.Buckets != nil && len(thisRow.Yeartime.Buckets) != 0 {
+		rMap["year_distribution"] = n_mae.TimeData(yearB, thisRow.Yeartime.Buckets)
+	}
+	return rMap, nil
+}
+
+// 时间分布月,年通用数据处理
+func (a *AnalysisEntity) TimeData(_b bool, thisRow []Buckets) map[string]interface{} {
+	var count_ss, amout_ss []map[string]interface{}
+
+	for k, v := range thisRow {
+		//环比多取一期数据
+		if _b && k == 0 {
+			continue
+		}
+		count := make(map[string]interface{})
+		amount := make(map[string]interface{})
+		count["minth"] = v.Key
+		count["value"] = v.ScaleTotal.DocCount
+		amount["minth"] = v.Key
+		amount["value"] = v.ScaleAmount.Value
+		//整月,年统计环比
+		if _b {
+			doccount := thisRow[k-1].ScaleTotal.DocCount
+			amountvalue := thisRow[k-1].ScaleAmount.Value
+
+			count["ratio"] = sequential(float64(v.ScaleTotal.DocCount), float64(doccount))
+			amount["ratio"] = sequential(v.ScaleAmount.Value, amountvalue)
+		}
+		count_ss = append(count_ss, count)
+		amout_ss = append(amout_ss, amount)
+	}
+	rMapData := make(map[string]interface{})
+	rMapData["project_count"] = count_ss
+	rMapData["project_amount"] = amout_ss
+	return rMapData
+}
+
+// top10
+func (a *AnalysisEntity) ProjectTop10() (rMap map[string]interface{}, err error) {
+	finalSql := fmt.Sprintf(a.GetCommonQuerySql(), queryTop10)
+	log.Println("ProjectTop10:", finalSql)
+	hits := elastic.Get(PtIndex, PtType, finalSql)
+	rMap = map[string]interface{}{}
+	bArr := []map[string]interface{}{}
+	source := *hits
+	for _, v := range source {
+		bA := map[string]interface{}{}
+		if v["ids"] != nil && len(InterToSliceString(v["ids"])) > 0 {
+			idss := InterToSliceString(v["ids"])
+			bA["_id"] = encodeId(idss[len(idss)-1])
+		} else {
+			bA["_id"] = nil
+		}
+		if v["area"] == "" {
+			bA["city"] = nil
+		} else {
+			bA["area"] = v["area"]
+		}
+		if v["city"] == "" {
+			bA["city"] = nil
+		} else {
+			bA["city"] = v["city"]
+		}
+		bA["jgtime"] = v["jgtime"]
+		bA["projectname"] = v["s_projectname"]
+		bA["sortprice"] = v["sortprice"]
+		ids := v["entidlist"]
+		var id_s []string
+		var winner_s []string
+		for _, i := range InterToSliceString(ids) {
+			iid := encodeId(i)
+			id_s = append(id_s, iid)
+		}
+		bA["eidlist"] = id_s
+		bA["winner_s"] = nil
+		if common.ObjToString(v["s_winner"]) != "" {
+			w_s := strings.Split(common.ObjToString(v["s_winner"]), ",")
+			for _, vv := range w_s {
+				winner_s = append(winner_s, vv)
+			}
+			bA["winner_s"] = winner_s
+		}
+		bArr = append(bArr, bA)
+	}
+	rMap["ProjectTop10"] = bArr
+	return
+}
+
+func (a *AnalysisEntity) AllData() (rMap map[string]interface{}, err error) {
+	rMap = map[string]interface{}{}
+	var aggs []string
+	area := a.FormatParam.Area
+	city := a.FormatParam.City
+	buyclass := a.FormatParam.BuyerClass
+	aggs = append(aggs, aggsAllCM, fmt.Sprintf(queryAggsSortprice, sortpriceStr), aggsArea)
+	if (len(area) + len(city)) != 1 {
+		aaat3 := fmt.Sprintf(aggsAreaAmounttop3, Top30)
+		aact3 := fmt.Sprintf(aggsAreaCounttop3, Top30)
+		if a.Offline == ValueOffline {
+			aaat3 = fmt.Sprintf(aggsAreaAmounttop3, Top30)
+			aact3 = fmt.Sprintf(aggsAreaCounttop3, Top30)
+		}
+		aggs = append(aggs, aaat3, aact3)
+	}
+	if len(buyclass) != 1 {
+		abat3 := fmt.Sprintf(aggsBuyerclassAmounttop3, Top30)
+		abct3 := fmt.Sprintf(aggsBuyerclassCounttop3, Top30)
+		if a.Offline == ValueOffline {
+			abat3 = fmt.Sprintf(aggsBuyerclassAmounttop3, Top30)
+			abct3 = fmt.Sprintf(aggsBuyerclassCounttop3, Top30)
+		}
+		//aggs = append(aggs, aggs_buyerclass, aggs_buyerclass_other, aggs_buyerclass_amounttop3, aggs_buyerclass_counttop3)
+		aggs = append(aggs, aggsBuyerclass, abat3, abct3)
+	}
+	finalSql := fmt.Sprintf(a.GetCommonQuerySqlWithAggs(), strings.Join(aggs, ","))
+	log.Println("allData sql:", finalSql)
+	res := GetAggs(PtIndex, PtType, finalSql)
+	if res == nil || len(res) == 0 {
+		return
+	}
+	thisRow := AreaCTop{}
+	for name, object := range res {
+		bArr, err := object.MarshalJSON()
+		if len(bArr) == 0 || err != nil {
+			continue
+		}
+		if name == "project_amount" {
+			if json.Unmarshal(bArr, &thisRow.Amount) != nil {
+				continue
+			}
+		} else if name == "project_count" {
+			if json.Unmarshal(bArr, &thisRow.ProjectCount) != nil {
+				continue
+			}
+			//} else if name == "buyerclass_scale_other" {
+			//	if json.Unmarshal(bArr, &thisRow.BuyerclassScaleOther) != nil {
+			//		continue
+			//	}
+		} else if name == "project_count_not0" {
+			if json.Unmarshal(bArr, &thisRow.CountNot0) != nil {
+				continue
+			}
+		} else if name == "sortprice_ranges" {
+			if json.Unmarshal(bArr, &thisRow.SortpriceRanges) != nil {
+				continue
+			}
+		} else if name == "area_distribution" {
+			if json.Unmarshal(bArr, &thisRow.AreaDistribution) != nil {
+				continue
+			}
+		} else if name == "buyerclass_scale" {
+			if json.Unmarshal(bArr, &thisRow.BuyerclassScale) != nil {
+				continue
+			}
+		} else if name == "area_amount_top3" {
+			if json.Unmarshal(bArr, &thisRow.AreaAmountTop3) != nil {
+				continue
+			}
+		} else if name == "area_count_top3" {
+			if json.Unmarshal(bArr, &thisRow.AreaCountTop3) != nil {
+				continue
+			}
+		} else if name == "buyerclass_amount_top3" {
+			if json.Unmarshal(bArr, &thisRow.BuyclassAmountTop3) != nil {
+				continue
+			}
+		} else if name == "buyerclass_count_top3" {
+			if json.Unmarshal(bArr, &thisRow.BuyclassCountTop3) != nil {
+				continue
+			}
+		}
+	}
+	thisRow.Total = int64(thisRow.ProjectCount.DocCount)
+	data := ProjectScale(thisRow)
+	area_data := AreaDistribute(thisRow)
+	customerData, customerOther := CustomerDistribute(thisRow)
+	//customerOther, customerData := CustomerDistributeDetails(thisRow, customerDetails)
+	var ids []string
+	for _, v := range thisRow.AreaAmountTop3.Buckets {
+		for _, va := range v.WinnerTop.Buckets {
+			ids = append(ids, va.Winner)
+		}
+	}
+	for _, v := range thisRow.BuyclassAmountTop3.Buckets {
+		for _, va := range v.WinnerTop.Buckets {
+			ids = append(ids, va.Winner)
+		}
+	}
+	for _, v := range thisRow.AreaCountTop3.Buckets {
+		for _, va := range v.WinnerTop.Buckets {
+			ids = append(ids, va.Winner)
+		}
+	}
+	for _, v := range thisRow.BuyclassCountTop3.Buckets {
+		for _, va := range v.BidcountTop.Buckets {
+			ids = append(ids, va.Winner)
+		}
+	}
+	eid := IDToName(ids)
+	rMap["projectScale"] = data
+	rMap["area_infos"] = area_data
+	rMap["customer_scale"] = customerData
+	rMap["customer_scale_other"] = customerOther //客户分布(其它)
+	rMap["scaleAreaAmountTop"] = AmountCompute(thisRow, "area", eid, a.Offline == ValueOffline)
+	rMap["scaleBuyclassAmountTop"] = AmountCompute(thisRow, "buyclass", eid, a.Offline == ValueOffline)
+	rMap["scaleAreaCountTop"] = CountCompute(thisRow, "area", eid, a.Offline == ValueOffline)
+	rMap["scaleBuyclassCountTop"] = CountCompute(thisRow, "buyclass", eid, a.Offline == ValueOffline)
+	if a.Offline == ValueOffline {
+		rMap["count_not_0"] = thisRow.CountNot0.Count
+	}
+	return
+}
+
+// MarketScaleRefineQuery 细化聚合
+func (a *AnalysisEntity) MarketScaleRefineQuery() (rMap map[string]interface{}, err error) {
+	//关键词分组聚合
+	var aggsGroup []string
+	//获取总金额及总数
+	aggsGroup = append(aggsGroup, `"projectAmount":{"sum":{"field":"sortprice"}}`)
+	aggsGroup = append(aggsGroup, `"projectTotal":{"filter":{"match_all":{}}}`)
+	itemDataMap := map[string]int64{}
+	for _, group := range a.FormatParam.KeysItems {
+		var bools []string
+		for _, v := range getGroupKeywordArr(group.A_Key) {
+			if sql := getKeyWordSql(v, a.BaseParam.MatchingMode); sql != "" {
+				bools = append(bools, sql)
+			}
+		}
+		if len(bools) > 0 {
+			//临时调整 进行数据验证 Top3
+			rwt := Top30
+			rwa := Top30
+			if a.Offline == ValueOffline { //离线
+				rwt = Top30
+				rwa = Top30
+			}
+			aggsGroup = append(aggsGroup, fmt.Sprintf(`"%s":{"filter":{"bool":{"should":[%s],"minimum_should_match": 1}},"aggs":{"project_count":{"filter":{"match_all":{}}},"project_amount":{"sum":{"field":"sortprice"}},"winner_total_top":{"terms":{"field":"entidlist","exclude":["-"],"order":[{"refine_winner_total":"desc"}],"size":%d},"aggs":{"refine_winner_total":{"filter":{"match_all":{}}}}},"winner_amount_top":{"terms":{"field":"entidlist","exclude":["-"],"order":[{"refine_winner_amount":"desc"}],"size":%d},"aggs":{"refine_winner_amount":{"sum":{"field":"sortprice"}}}}}}`, group.ItemName, strings.Join(bools, ","), rwt, rwa))
+		}
+		itemDataMap[group.ItemName] = group.UpdateTime
+	}
+	finalSql := fmt.Sprintf(a.GetCommonQuerySqlWithAggs(), strings.Join(aggsGroup, ","))
+	fmt.Println("finalSql-----4", finalSql)
+	rMap = map[string]interface{}{}
+	res := GetAggs(PtIndex, PtType, finalSql)
+	if res == nil || len(res) == 0 {
+		return
+	}
+	scale := scaleRefineData{IdSwitch: map[string]string{}, IsOffline: a.Offline == ValueOffline}
+	for name, object := range res {
+		bArr, err := object.MarshalJSON()
+		if len(bArr) == 0 || err != nil {
+			continue
+		}
+		if name == "projectTotal" {
+			st := simpleTotal{}
+			if err := json.Unmarshal(bArr, &st); err == nil {
+				scale.Total = st
+			}
+			continue
+		} else if name == "projectAmount" {
+			ss := simpleSum{}
+			if err := json.Unmarshal(bArr, &ss); err == nil {
+				scale.Amount = ss
+			}
+			continue
+		}
+		thisRow := scaleRefineRow{}
+		if err := json.Unmarshal(bArr, &thisRow); err != nil {
+			continue
+		}
+		thisRow.ItemName = name
+		thisRow.UpdateTime = itemDataMap[name]
+		scale.Data = append(scale.Data, &thisRow)
+	}
+	scale.formatData() //获取所需数据
+	scale.doIdSwitch() //补充企业名称
+	return map[string]interface{}{
+		"scaleRefineAll":       scale.ReturnData.Overall,
+		"scaleRefineTotalTop":  scale.ReturnData.TotalTop,
+		"scaleRefineAmountTop": scale.ReturnData.AmountTop,
+	}, nil
+}
+
+// 采购单位分析
+func (a *AnalysisEntity) BuyerWinnerAnalysis() map[string]interface{} {
+	var datas []string
+	//采购单位-采购规模分布
+	//buyer_scalefmt := fmt.Sprintf(buyer_procurement_scale, sortprice_str)
+
+	//采购单位-项目数量 采购单位-项目金额
+	bc := fmt.Sprintf(buyerCount, Top50, Top30)
+	bs := fmt.Sprintf(buyerSortprice, Top50, Top30)
+	if a.Offline == ValueOffline {
+		bc = fmt.Sprintf(buyerCount, BWCount, Top30)
+		bs = fmt.Sprintf(buyerSortprice, BWCount, Top30)
+	}
+	bps := fmt.Sprintf(buyerProcurementScale, BWDCount)
+	datas = append(datas, bps, bc, bs)
+	//中标单位-规模分布
+	//winner_scalefmt := fmt.Sprintf(winner_procurement_scale, sortprice_str)
+
+	//中标单位-项目数量 中标单位-项目金额
+	wc := fmt.Sprintf(winnerCount, Top50, Top30) //正常需求30-3;目前是50-30,多获取数据 分析后进行数据损失补充,尽量减少 实时分析和离线分析的误差
+	ws := fmt.Sprintf(winnerSortprice, Top50, Top30)
+	if a.Offline == ValueOffline {
+		wc = fmt.Sprintf(winnerCount, BWCount, Top30)
+		ws = fmt.Sprintf(winnerSortprice, BWCount, Top30)
+	}
+	wps := fmt.Sprintf(winnerProcurementScale, BWDCount)
+	datas = append(datas, wps, wc, ws)
+	finalSql := fmt.Sprintf(a.GetCommonQuerySqlWithAggs(), strings.Join(datas, ","))
+	log.Printf("final PurchasingAnalysiseQuery sql: %s", finalSql)
+	t := time.Now()
+	res := GetAggs(PtIndex, PtType, finalSql)
+	if res == nil || len(res) == 0 {
+		return nil
+	}
+	//计算entidlist大于1的中标单位金额
+	entArrMap := make(map[string]float64)
+	winnerKeyMap := make(map[string]int)
+	newSql := strings.ReplaceAll(fmt.Sprintf(a.GetCommonQuerySql(), `,"sort": [{"sortprice": {"order": "desc"}}], "_source": ["entidlist","sortprice"],"size":10000`), `],"should":[`, fmt.Sprintf(`%s],"should":[`, `,{"script": {"script": {"source": "doc['entidlist'].length > 1"}}}`))
+	log.Println("winner new sql", newSql)
+	multiBid := elastic.Get(PtIndex, PtType, newSql)
+	if multiBid != nil && len(*multiBid) > 0 {
+		for _, m := range *multiBid {
+			entidlist, _ := m["entidlist"].([]interface{})
+			entArr := common.ObjArrToStringArr(entidlist)
+			sortprice := common.Float64All(m["sortprice"])
+			entLen := len(entArr)
+			switch entLen {
+			case 1:
+				entArrMap[entArr[0]] = sortprice
+				//winnerKeyMap[entArr[0]]++
+			case 0:
+			default:
+				assessedAmount := sortprice / common.Float64All(entLen)
+				for _, m2 := range entArr {
+					//winnerKeyMap[m2]++
+					entArrMap[m2] = assessedAmount
+				}
+			}
+		}
+	}
+	log.Println("采购单位-中标单位分析报告es查询耗时===", time.Since(t))
+	var thisBuyerWinnerRow BuyerWinnerRow
+	for name, object := range res {
+		bArr, err := object.MarshalJSON()
+		if len(bArr) == 0 || err != nil {
+			continue
+		}
+		if name == "buyer_amount_distribution" {
+			if json.Unmarshal(bArr, &thisBuyerWinnerRow.BuyerAmountDistribution) != nil {
+				continue
+			}
+		} else if name == "buyer_count_top3" {
+			if json.Unmarshal(bArr, &thisBuyerWinnerRow.BuyerCountTop3) != nil {
+				continue
+			}
+		} else if name == "buyer_amount_top3" {
+			if json.Unmarshal(bArr, &thisBuyerWinnerRow.BuyerAmountTop3) != nil {
+				continue
+			}
+		} else if name == "winner_amount_distribution" {
+			if json.Unmarshal(bArr, &thisBuyerWinnerRow.WinnerAmountDistribution) != nil {
+				continue
+			}
+		} else if name == "winner_count_top3" {
+			if json.Unmarshal(bArr, &thisBuyerWinnerRow.WinnerCountTop3) != nil {
+				continue
+			}
+		} else if name == "winner_amount_top3" {
+			if json.Unmarshal(bArr, &thisBuyerWinnerRow.WinnerAmountTop3) != nil {
+				continue
+			}
+		}
+	}
+	var winnerKeys []string
+	for _, v := range thisBuyerWinnerRow.BuyerCountTop3.Buckets {
+		for _, v1 := range v.SWinnerTop.Buckets {
+			winnerKeyMap[v1.Key]++
+		}
+	}
+	for _, v := range thisBuyerWinnerRow.BuyerAmountTop3.Buckets {
+		for _, v1 := range v.SWinnerTop.Buckets {
+			winnerKeyMap[v1.Key]++
+		}
+	}
+	for _, v := range thisBuyerWinnerRow.WinnerCountTop3.SWinnerCount {
+		winnerKeyMap[v.Key]++
+	}
+	for _, v := range thisBuyerWinnerRow.WinnerAmountTop3.SWinnerAmount {
+		winnerKeyMap[v.Key]++
+	}
+	for k := range winnerKeyMap {
+		winnerKeys = append(winnerKeys, k)
+	}
+	winnerName := GetEntNameByIds(winnerKeys)
+	//rMap := make(map[string]interface{})
+	var rMap = sync.Map{}
+	sy := sync.WaitGroup{}
+	sy.Add(2)
+	go BuyerAnalysis(thisBuyerWinnerRow, &rMap, winnerName, &sy, a.Offline == ValueOffline)
+	go WinningAnalysis(thisBuyerWinnerRow, &rMap, entArrMap, winnerName, &sy, a.Offline == ValueOffline)
+	sy.Wait()
+	log.Println("采购单位-中标单位分析报告程序计算耗时===", time.Since(t))
+	rMaps := make(map[string]interface{})
+	rMap.Range(func(key, value interface{}) bool {
+		rMaps[common.InterfaceToStr(key)] = value
+		return true
+	})
+	return rMaps
+}
+
+func (a *AnalysisEntity) GetPdfPageApi() (finalDate map[string]interface{}, err error) {
+	var (
+		wg   = sync.WaitGroup{}
+		lock = &sync.Mutex{}
+	)
+	finalDate = make(map[string]interface{})
+	for i := 1; i <= 5; i++ {
+		wg.Add(1)
+		go func(flag int) {
+			defer wg.Done()
+			mgoData, _ := a.GetMongoData(flag)
+			lock.Lock()
+			for s, i2 := range mgoData {
+				finalDate[s] = i2
+			}
+			lock.Unlock()
+		}(i)
+	}
+	wg.Wait()
+	return
+}

+ 224 - 0
marketanalysis/scalerefine.go

@@ -0,0 +1,224 @@
+package marketanalysis
+
+import "sort"
+
+const (
+	_ = iota
+	onlyTotal
+	onlyAmount
+	totalAndAmount
+
+	topWinnerLimit = 3   //细分市场展示前x个企业
+	topItemLimit   = 300 //返回前x组关键词
+)
+
+type scaleRefineData struct {
+	Data       []*scaleRefineRow
+	Total      simpleTotal
+	Amount     simpleSum
+	IdSwitch   map[string]string
+	ReturnData struct {
+		Overall             []map[string]interface{}
+		TotalTop, AmountTop []*returnItem
+	}
+	IsOffline bool
+}
+
+type simpleTotal struct {
+	Total int64 `json:"doc_count"`
+}
+
+type simpleSum struct {
+	Value float64 `json:"value"`
+}
+
+type scaleRefineRow struct {
+	Total          int64     `json:"doc_count"`
+	TotalProp      float64   `json:"total_prop"`
+	Amount         simpleSum `json:"project_amount"`
+	AmountProp     float64   `json:"amount_prop"`
+	WinnerTotalTop struct {
+		Buckets []*struct {
+			EntId   string  `json:"key"`
+			EntName string  `json:"entName"`
+			Total   int64   `json:"doc_count"`
+			Prop    float64 `json:"prop"`
+		} `json:"buckets"`
+	} `json:"winner_total_top"`
+	WinnerAmountTop struct {
+		Buckets []*struct {
+			EntId   string `json:"key"`
+			EntName string `json:"entName"`
+			Amount  struct {
+				Value float64 `json:"value"`
+			} `json:"refine_winner_amount"`
+			Prop float64 `json:"prop"`
+		} `json:"buckets"`
+	} `json:"winner_amount_top"`
+	ItemName   string `json:"itemName"`
+	UpdateTime int64  `json:"updateTime"`
+}
+
+type returnItem struct {
+	Name    string      `json:"name"`
+	Value   interface{} `json:"value"`
+	Prop    interface{} `json:"prop"`
+	TopList []*entValue `json:"topList"`
+}
+
+type entValue struct {
+	Id    string      `json:"id"`
+	Name  string      `json:"name"`
+	Value interface{} `json:"value"`
+	Prop  interface{} `json:"prop"`
+}
+
+// doIdSwitch 补充企业名称
+func (srd *scaleRefineData) doIdSwitch() {
+	if len(srd.IdSwitch) == 0 {
+		return
+	}
+	var ids []string
+	for id, _ := range srd.IdSwitch {
+		ids = append(ids, id)
+	}
+	srd.IdSwitch = GetEntNameByIds(ids)
+	for _, v := range srd.ReturnData.TotalTop {
+		var ents []*entValue
+		for _, vv := range v.TopList {
+			if !srd.IsOffline && len(ents) >= Top3 {
+				break
+			}
+			vv.Name, _ = srd.IdSwitch[vv.Id]
+			if vv.Name == "" {
+				continue
+			}
+			vv.Id = encodeId(vv.Id)
+			ents = append(ents, vv)
+		}
+		v.TopList = ents
+	}
+	for _, v := range srd.ReturnData.AmountTop {
+		var ents []*entValue
+		for _, vv := range v.TopList {
+			if !srd.IsOffline && len(ents) >= Top3 {
+				break
+			}
+			vv.Name, _ = srd.IdSwitch[vv.Id]
+			if vv.Name == "" {
+				continue
+			}
+			vv.Id = encodeId(vv.Id)
+			ents = append(ents, vv)
+		}
+		v.TopList = ents
+	}
+}
+
+// formatData 计算百分比&获取企业对应名称
+func (srd *scaleRefineData) formatData() {
+	for _, v := range srd.Data {
+		if srd.Total.Total == 0 {
+			v.TotalProp = 0
+		} else {
+			v.TotalProp = float64(v.Total) / float64(srd.Total.Total)
+		}
+		if srd.Amount.Value == 0 {
+			v.AmountProp = 0
+		} else {
+			v.AmountProp = v.Amount.Value / srd.Amount.Value
+		}
+
+		for _, vv := range v.WinnerTotalTop.Buckets {
+			if v.Total == 0 {
+				vv.Prop = 0
+			} else {
+				vv.Prop = float64(vv.Total) / float64(v.Total)
+			}
+		}
+		for _, vv := range v.WinnerAmountTop.Buckets {
+			if v.Amount.Value == 0 {
+				vv.Prop = 0
+			} else {
+				vv.Prop = vv.Amount.Value / v.Amount.Value
+			}
+		}
+	}
+	srd.ReturnData.Overall = srd.sortBy(totalAndAmount).getOverallData()
+	srd.ReturnData.AmountTop = srd.sortBy(onlyAmount).getFormatTop(topItemLimit, onlyAmount)
+	srd.ReturnData.TotalTop = srd.sortBy(onlyTotal).getFormatTop(topItemLimit, onlyTotal)
+}
+
+// getOverallData 获取分类总览数据
+func (srd *scaleRefineData) getOverallData() (rData []map[string]interface{}) {
+	for _, Value := range srd.Data {
+		rData = append(rData, map[string]interface{}{
+			"name":   Value.ItemName,
+			"total":  Value.Total,
+			"amount": Value.Amount.Value,
+		})
+	}
+	return
+}
+
+// getArrayFormatTop 获取前n个数据,并格式化
+// limit 获取前x条
+// flag 1仅返回数量统计 2仅返回金额统计
+func (srd *scaleRefineData) getFormatTop(limit, flag int) (rData []*returnItem) {
+	for index, Value := range srd.Data {
+		if index >= limit && limit > -1 {
+			return
+		}
+		thisRow := &returnItem{
+			Name: Value.ItemName,
+		}
+		if flag == 1 || flag == 3 { //返回数量相关数据
+			thisRow.Value = Value.Total
+			if flag != 3 {
+				for _, v := range Value.WinnerTotalTop.Buckets {
+					thisRow.TopList = append(thisRow.TopList, &entValue{
+						Id:    v.EntId,
+						Name:  v.EntName,
+						Value: v.Total,
+						Prop:  v.Prop,
+					})
+					srd.IdSwitch[v.EntId] = ""
+				}
+				thisRow.Prop = Value.TotalProp
+			}
+		}
+		if flag == 2 || flag == 3 { //返回金额相关数据
+			thisRow.Value = Value.Amount.Value
+			if flag != 3 {
+				for _, v := range Value.WinnerAmountTop.Buckets {
+					thisRow.TopList = append(thisRow.TopList, &entValue{
+						Id:    v.EntId,
+						Name:  v.EntName,
+						Value: v.Amount.Value,
+						Prop:  v.Prop,
+					})
+					srd.IdSwitch[v.EntId] = ""
+				}
+				thisRow.Prop = Value.TotalProp
+			}
+		}
+		rData = append(rData, thisRow)
+	}
+	return
+}
+
+// getScaleRefineAll 根据订阅词组创建时间排序
+func (srd *scaleRefineData) sortBy(flag int) *scaleRefineData {
+	sort.Slice(srd.Data, func(i, j int) bool {
+		switch flag {
+		case onlyTotal:
+			return srd.Data[i].Total > srd.Data[j].Total
+		case onlyAmount:
+			return srd.Data[i].Amount.Value > srd.Data[j].Amount.Value
+		case totalAndAmount:
+			return srd.Data[i].UpdateTime > srd.Data[j].UpdateTime
+		}
+		return false
+	})
+	return srd
+}