Browse Source

fix:定制化分析报告查询

duxin 2 years ago
parent
commit
b403723f94
4 changed files with 436 additions and 0 deletions
  1. 119 0
      common/commonSearch.go
  2. 180 0
      common/customizedAnalysis.go
  3. 99 0
      common/marketAnalysisEntity.go
  4. 38 0
      util/aggsSearchUtil.go

+ 119 - 0
common/commonSearch.go

@@ -0,0 +1,119 @@
+package common
+
+import (
+	qutil "app.yhyue.com/moapp/jybase/common"
+	"fmt"
+	"strings"
+	"time"
+)
+
+// GetCommonQuerySqlWithAggs 此方法用于聚合查询
+func (mae *MarketAnalysisEntity) GetCommonQuerySqlWithAggs() string {
+	return fmt.Sprintf(mae.GetCommonQuerySql(), `,"aggs":{%s},"size":0`)
+}
+
+// GetCommonQuerySql 公共筛选
+func (mae *MarketAnalysisEntity) GetCommonQuerySql() string {
+	var musts, bools []string
+	//时间
+	musts = append(musts, fmt.Sprintf(`{"range":{"firsttime":{"gte":%d,"lte":%d}}}`, time.Now().AddDate(-1, 0, 0), time.Now().Unix()))
+	//地区
+	/*if len(mae.FormatParam.Area) > 0 || len(mae.FormatParam.City) > 0 {
+		var areaCity []string
+		if len(mae.FormatParam.Area) > 0 {
+			areaCity = append(areaCity, fmt.Sprintf(`{"terms":{"area":["%s"]}}`, strings.Join(mae.FormatParam.Area, `","`)))
+		}
+		if len(mae.FormatParam.City) > 0 {
+			areaCity = append(areaCity, fmt.Sprintf(`{"terms":{"city":["%s"]}}`, strings.Join(mae.FormatParam.City, `","`)))
+		}
+		musts = append(musts, fmt.Sprintf(`{"bool":{"should":[%s],"minimum_should_match": 1}}`, strings.Join(areaCity, ",")))
+	}
+	//行业
+	if len(mae.FormatParam.Industry) > 0 {
+		musts = append(musts, fmt.Sprintf(`{"terms":{"subscopeclass":["%s"]}}`, strings.Join(mae.FormatParam.Industry, `","`)))
+	}
+	//类型
+	if len(mae.FormatParam.BuyerClass) > 0 {
+		musts = append(musts, fmt.Sprintf(`{"terms":{"buyerclass":["%s"]}}`, strings.Join(mae.FormatParam.BuyerClass, `","`)))
+	}*/
+	//分析报告中标状态限制
+	musts = append(musts, fmt.Sprintf(query_bool_must, PSearch_DecMust))
+	//订阅词
+	for _, v := range getAllKeywordArr(mae.FormatParam.KeysItems) {
+		if sql := getKeyWordSql(v); sql != "" {
+			bools = append(bools, sql)
+		}
+	}
+
+	return fmt.Sprintf(`{"query":{"bool":{"must":[%s],"should":[%s],"minimum_should_match": %d}}%s}`, strings.Join(musts, ","), strings.Join(bools, ","), qutil.If(len(bools) > 0, 1, 0).(int), "%s")
+}
+
+// getAllKeywordArr 获取所有匹配词
+func getAllKeywordArr(res []keyWordGroup) (rData []viewKeyWord) {
+	for _, kwg := range res {
+		rData = append(rData, getGroupKeywordArr(kwg.A_Key)...)
+	}
+	return
+}
+
+// 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
+}
+
+func getKeyWordSql(v viewKeyWord) string {
+	var shoulds, must_not []string
+	//附加词
+	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(query_bool_must_and, strings.Join(shoulds, ","), notStr)
+	}
+	return ""
+}

+ 180 - 0
common/customizedAnalysis.go

@@ -0,0 +1,180 @@
+package common
+
+import (
+	"encoding/json"
+	"fmt"
+	"leadGeneration/util"
+	"strings"
+)
+
+var Analysis = []string{"<10万", "10万-50万", "50万-100万", "100万-500万", "500万-1000万", "1000万-1亿", "≥1亿"}
+
+func (mae *MarketAnalysisEntity) PotentialCustomizeAnalysis() map[string]interface{} {
+	var aggs []string
+	aggs = append(aggs, aggs_buyerclass, buyer_procurement_scale, winner_procurement_scale)
+	finalSql := fmt.Sprintf(mae.GetCommonQuerySqlWithAggs(), strings.Join(aggs, ","))
+	res, _ := util.GetAggs("projectset", "projectset", finalSql)
+	if res == nil || len(res) == 0 {
+		return nil
+	}
+	thisRow := Aggregation{}
+	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.ProjectAmount) != nil {
+				continue
+			}
+		} else if name == "buyer_amount_distribution" {
+			if json.Unmarshal(bArr, &thisRow.BuyerAmountDistribution) != nil {
+				continue
+			}
+		} else if name == "winner_amount_distribution" {
+			if json.Unmarshal(bArr, &thisRow.WinnerAmountDistribution) != nil {
+				continue
+			}
+		} else if name == "buyerclass_scale" {
+			if json.Unmarshal(bArr, &thisRow.BuyerclassScale) != nil {
+				continue
+			}
+		}
+	}
+	rMap := make(map[string]interface{})
+	CustomerDistribute(thisRow, rMap) //客户分布
+	BuyerAnalysis(thisRow, rMap)      //采购单位分布
+	WinningAnalysis(thisRow, rMap)    //中标单位分布
+	return rMap
+}
+
+// CustomerDistribute 客户分布
+func CustomerDistribute(thisRow Aggregation, rMap map[string]interface{}) {
+	var data []map[string]interface{}
+	for _, v := range thisRow.BuyerclassScale.Buckets {
+		rM := map[string]interface{}{}
+		rM["buyclass"] = v.Area
+		rM["total"] = v.AreaTotal
+		rM["amount"] = v.BuyclassAmount.Value
+		data = append(data, rM)
+	}
+	rMap["customer_scale"] = data
+	return
+}
+
+// BuyerAnalysis 采购单位分布
+func BuyerAnalysis(thisBuyerRow Aggregation, rMap map[string]interface{}) {
+	type buyer struct {
+		Name        string      `json:"key"`
+		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 count_b int
+	for _, v := range buyerA {
+		count_b = count_b + v.Count
+	}
+
+	for _, v := range Analysis {
+		var data buyer
+		data.Name = v
+		if vlu, ok := buyerA[v]; ok && count_b != 0 {
+			data.TotalNumber = float64(vlu.Count) / float64(count_b)
+		}
+		if vlu, ok := buyerA[v]; ok && thisBuyerRow.ProjectAmount.Value != 0 {
+			data.TotalAmount = vlu.Amount / thisBuyerRow.ProjectAmount.Value
+		}
+		buyerMap = append(buyerMap, data)
+	}
+
+	rMap["buyer_time_distribution"] = buyerMap
+}
+
+// WinningAnalysis 中标单位分布
+func WinningAnalysis(thisWinnerRow Aggregation, rMap map[string]interface{}) {
+	type s_Winner struct {
+		Name        string      `json:"key"`
+		TotalAmount interface{} `json:"total_amount"`
+		TotalNumber interface{} `json:"total_number"`
+	}
+	//中标单位-中标规模分布
+	winnerA := make(map[string]*distributionTrend)
+	for _, v := range thisWinnerRow.WinnerAmountDistribution.Buckets {
+		amountDistribution(v.Amount.Value, winnerA)
+	}
+	var count_b int
+	for _, v := range winnerA {
+		count_b = count_b + v.Count
+	}
+	buyerMap := []interface{}{}
+	for _, v := range Analysis {
+		var data s_Winner
+		data.Name = v
+		if vlu, ok := winnerA[v]; ok && count_b != 0 {
+			data.TotalNumber = float64(vlu.Count) / float64(count_b)
+		}
+		if vlu, ok := winnerA[v]; ok && thisWinnerRow.ProjectAmount.Value != 0 {
+			data.TotalAmount = vlu.Amount / thisWinnerRow.ProjectAmount.Value
+		}
+		buyerMap = append(buyerMap, data)
+	}
+
+	rMap["winner_time_distribution"] = buyerMap
+}
+
+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++
+	}
+
+}

+ 99 - 0
common/marketAnalysisEntity.go

@@ -0,0 +1,99 @@
+package common
+
+const (
+	//客户分布
+	aggs_buyerclass = `"buyerclass_scale":{"terms":{"field":"buyerclass","exclude":["其它",""]},"aggs":{"buyerclass_amount":{"sum":{"field":"sortprice"}},"buyerclass_total":{"filter":{}}}}`
+	//采购单位分布
+	buyer_procurement_scale = `"project_amount":{"sum":{"field":"sortprice"}},"buyer_amount_distribution": {"terms": {"field": "buyer","size": 0},"aggs": {"amount": {"sum": {"field": "sortprice"}}}}`
+	//中标单位分布
+	winner_procurement_scale = `"winner_amount_distribution": {"terms": {"field": "entidlist","size": 0},"aggs": {"amount": {"sum": {"field": "sortprice"}}}}`
+
+	PSearch_DecMust     = `"bidstatus": ["中标","成交","合同","单一"]`
+	query_bool_must     = `{"terms": {%s}}`
+	query_bool_must_and = `{"bool": {"must": [%s]%s}}`
+	localMultiMatch     = `{"multi_match": {"query": %s,"type": "phrase", "fields": ["purchasing","pname"]}}`
+)
+
+type Aggregation struct {
+	ProjectAmount struct {
+		Value float64 `json:"value"`
+	} `json:"project_amount"`
+	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"`
+
+	WinnerAmountDistribution struct {
+		Buckets []struct {
+			Key      string `json:"key"`
+			DocCount int    `json:"doc_count"`
+			Amount   struct {
+				Value float64 `json:"value"`
+			} `json:"amount"`
+		} `json:"buckets"`
+	} `json:"winner_amount_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"`
+}
+
+type MarketAnalysisEntity struct {
+	//MgoRecordId string
+	//BaseParam   AnalysisRequestParam
+	FormatParam AnalysisRequestFormat
+	//UId, Pid    string
+}
+
+// AnalysisRequestFormat 格式化后参数
+type AnalysisRequestFormat struct {
+	KeysItems []keyWordGroup
+	//Area, City   []string //省份城市
+	//STime, ETime int64    //开始结束时间
+	//Industry     []string //行业
+	//BuyerClass   []string //采购单位类型
+}
+
+// keyWordGroup 订阅词结构体
+type keyWordGroup struct {
+	A_Key      []viewKeyWord `json:"a_key"`
+	ItemName   string        `json:"s_item"`
+	UpdateTime int64         `json:"updatetime"`
+}
+
+type viewKeyWord struct {
+	Keyword  []string `json:"key"`       //关键词
+	Appended []string `json:"appendkey"` //附加词
+	Exclude  []string `json:"notkey"`    //排除词
+	MatchWay int      `json:"matchway"`  //匹配模式
+}
+
+// AnalysisRequestParam 接口原请求参数
+type AnalysisRequestParam struct {
+	KeysItemsStr   string //分析内容【字符串】结构和o_member_jy.a_items保持一致
+	RangeTime      string //时间【字符串】 时间戳开始-结束时间戳
+	RangeTimeExtra string //时间【字符串】前段回显使用
+	Area           string //省份【对象字符串】
+	Industry       string //行业【对象字符串】
+	BuyerClass     string //采购单位类型【字符串】多个采购单位类型用逗号拼接
+}
+
+type distributionTrend struct {
+	Key    string
+	Count  int
+	Amount float64
+}

+ 38 - 0
util/aggsSearchUtil.go

@@ -0,0 +1,38 @@
+package util
+
+import (
+	util "app.yhyue.com/moapp/jybase/common"
+	elastic "app.yhyue.com/moapp/jybase/esv1"
+	"log"
+	"runtime"
+)
+
+// GetAggs 聚合查询
+func GetAggs(index, itype, query string) (aggs elastic.Aggregations, count int64) {
+	defer util.Catch()
+	client := elastic.GetEsConn()
+	defer func() {
+		go elastic.DestoryEsConn(client)
+	}()
+	if client != nil {
+		defer func() {
+			if r := recover(); r != nil {
+				log.Println("[E]", r)
+				for skip := 1; ; skip++ {
+					_, file, line, ok := runtime.Caller(skip)
+					if !ok {
+						break
+					}
+					go log.Printf("%v,%v\n", file, line)
+				}
+			}
+		}()
+		searchResult, err := client.Search().Index(index).Type(itype).Source(query).Do()
+		if err != nil {
+			log.Println("从ES查询出错", err.Error())
+		}
+		count = searchResult.Hits.TotalHits
+		aggs = searchResult.Aggregations
+	}
+	return
+}