project_new.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. package main
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "github.com/olivere/elastic/v7"
  8. util "jygit.jydev.jianyu360.cn/data_processing/common_utils"
  9. "log"
  10. "sort"
  11. "strconv"
  12. //"sort"
  13. "strings"
  14. "time"
  15. )
  16. func SearchProjectFullScoring(client *elastic.Client, target InputData, projectName, areacode, publish string, targetScore float64) ([]map[string]interface{}, error) {
  17. seenIDs := make(map[string]*elastic.SearchHit)
  18. province, city := "", ""
  19. if areacode != "" {
  20. code := areacode[:6]
  21. where := map[string]interface{}{
  22. "code": code,
  23. }
  24. res, _ := MgoQY.FindOne("address_new_2020", where)
  25. province = util.ObjToString((*res)["province"])
  26. city = util.ObjToString((*res)["city"])
  27. }
  28. target.Area = province
  29. target.City = city
  30. fmt.Println(province, city)
  31. projectName = RemoveInvisibleChars(projectName)
  32. projectName = FilterGeneric(projectName)
  33. // 1. 精准查询
  34. preciseHits, err := searchPrecise(client, projectName, province, city, publish, 20)
  35. if err != nil {
  36. return nil, err
  37. }
  38. for _, hit := range preciseHits {
  39. if _, exists := seenIDs[hit.Id]; !exists {
  40. seenIDs[hit.Id] = hit
  41. }
  42. }
  43. // 2. 分词查询(
  44. tokenHits, err := searchByToken(client, projectName, province, city, publish, 20)
  45. if err != nil {
  46. return nil, err
  47. }
  48. for _, hit := range tokenHits {
  49. if _, exists := seenIDs[hit.Id]; !exists {
  50. seenIDs[hit.Id] = hit
  51. }
  52. }
  53. // 3. common 查询
  54. commonHits, err := searchCommon(client, projectName, province, city, publish, 10)
  55. if err != nil {
  56. return nil, err
  57. }
  58. for _, hit := range commonHits {
  59. if _, exists := seenIDs[hit.Id]; !exists {
  60. seenIDs[hit.Id] = hit
  61. }
  62. }
  63. // 4. 合并 + 打分增强
  64. var results []map[string]interface{}
  65. //nameScore := computeNameScore(projectName)
  66. var allCandidates = []EsDocument{}
  67. for id, hit := range seenIDs {
  68. var doc map[string]interface{}
  69. if err := json.Unmarshal(hit.Source, &doc); err != nil {
  70. continue
  71. }
  72. // 从 Mongo 读取 detail 字段用于后续 buyer 过滤
  73. bidd, _ := MgoB.FindById("bidding", id, nil)
  74. detail := util.ObjToString((*bidd)["detail"])
  75. doc["detail"] = detail
  76. /**
  77. "id", "title", "projectname", "projectcode", "bidamount", "area", "city",
  78. "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel",
  79. "s_winner", "winnertel", "agency", "publishtime"
  80. */
  81. candidate := EsDocument{
  82. Id: util.ObjToString(doc["id"]),
  83. Title: util.ObjToString(doc["title"]),
  84. Projectname: util.ObjToString(doc["projectname"]),
  85. Toptype: util.ObjToString(doc["toptype"]),
  86. Subtype: util.ObjToString(doc["subtype"]),
  87. Area: util.ObjToString(doc["area"]),
  88. City: util.ObjToString(doc["city"]),
  89. Buyer: util.ObjToString(doc["buyer"]),
  90. SWinner: util.ObjToString(doc["s_winner"]),
  91. Bidamount: util.Float64All(doc["bidamount"]),
  92. Publishtime: util.Int64All(doc["publishtime"]),
  93. Agency: util.ObjToString(doc["agency"]),
  94. WinnerTel: util.ObjToString(doc["winnertel"]),
  95. BuyerTel: util.ObjToString(doc["buyertel"]),
  96. BuyerPerson: util.ObjToString(doc["buyerperson"]),
  97. Budget: util.Float64All(doc["budget"]),
  98. }
  99. score := calculateConfidenceScore(target, candidate)
  100. if targetScore > 0 {
  101. if score >= targetScore {
  102. candidate.Score, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", score), 64)
  103. allCandidates = append(allCandidates, candidate)
  104. }
  105. } else {
  106. candidate.Score, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", score), 64)
  107. allCandidates = append(allCandidates, candidate)
  108. }
  109. }
  110. // 排序(降序)
  111. sort.SliceStable(allCandidates, func(i, j int) bool {
  112. return allCandidates[i].Score > allCandidates[j].Score
  113. })
  114. for _, doc := range allCandidates {
  115. item := map[string]interface{}{
  116. "id": doc.Id,
  117. "title": doc.Title,
  118. "projectname": doc.Projectname,
  119. "projectcode": doc.ProjectCode,
  120. "toptype": doc.Toptype,
  121. "subtype": doc.Subtype,
  122. "area": doc.Area,
  123. "city": doc.City,
  124. "buyer": doc.Buyer,
  125. "budget": doc.Budget,
  126. "bidamount": doc.Bidamount,
  127. "winner": doc.Winner,
  128. "detail": doc.Detail,
  129. "publishtime": doc.Publishtime,
  130. "agency": doc.Agency,
  131. "s_winner": doc.SWinner,
  132. "winnertel": doc.WinnerTel,
  133. "buyertel": doc.BuyerTel,
  134. "buyerperson": doc.BuyerPerson,
  135. "score": doc.Score,
  136. }
  137. results = append(results, item)
  138. }
  139. return results, nil
  140. }
  141. func searchPrecise(client *elastic.Client, projectName, province, city, publish string, maxResults int) ([]*elastic.SearchHit, error) {
  142. fieldsToTry := []string{"projectname.pname", "title", "detail"}
  143. filtersToTry := [][]elastic.Query{
  144. {elastic.NewTermsQuery("subtype", "中标", "成交", "合同", "单一")},
  145. {elastic.NewTermsQuery("toptype", "招标", "预告", "采购意向")},
  146. {elastic.NewTermsQuery("toptype", "拟建")},
  147. }
  148. var allResults []*elastic.SearchHit
  149. seenIDs := make(map[string]bool)
  150. // 解析发布时间
  151. var t time.Time
  152. var err error
  153. if publish != "" {
  154. t, err = time.Parse("200601", publish)
  155. if err != nil {
  156. log.Println("时间解析失败:", err)
  157. }
  158. }
  159. for _, field := range fieldsToTry {
  160. var dateRangeStart, dateRangeEnd int64
  161. if !t.IsZero() {
  162. if field == "detail" {
  163. dateRangeStart, dateRangeEnd = getYearRange(t, 60)
  164. } else {
  165. dateRangeStart, dateRangeEnd = getYearRange(t, 36)
  166. }
  167. }
  168. for _, filters := range filtersToTry {
  169. var queries []*elastic.BoolQuery
  170. if field == "detail" {
  171. // detail 只加时间 + filter
  172. query := elastic.NewBoolQuery()
  173. if province != "" {
  174. query = query.Must(elastic.NewTermQuery("area", province))
  175. }
  176. query = query.Must(elastic.NewMultiMatchQuery(projectName, field).Type("phrase"))
  177. query = query.Filter(filters...)
  178. if !t.IsZero() {
  179. query = query.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  180. }
  181. queries = append(queries, query)
  182. } else {
  183. // 1. 省 + 市 查询
  184. if province != "" && city != "" {
  185. query1 := elastic.NewBoolQuery()
  186. query1 = query1.Must(elastic.NewMultiMatchQuery(projectName, field).Type("phrase"))
  187. query1 = query1.Must(elastic.NewTermQuery("area", province))
  188. query1 = query1.Must(elastic.NewTermQuery("city", city))
  189. query1 = query1.Filter(filters...)
  190. if !t.IsZero() {
  191. query1 = query1.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  192. }
  193. queries = append(queries, query1)
  194. }
  195. // 2. 仅省份(城市为空或不同)
  196. if province != "" {
  197. query2 := elastic.NewBoolQuery()
  198. query2 = query2.Must(elastic.NewMultiMatchQuery(projectName, field).Type("phrase"))
  199. query2 = query2.Must(elastic.NewTermQuery("area", province))
  200. query2 = query2.Filter(filters...)
  201. if !t.IsZero() {
  202. query2 = query2.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  203. }
  204. queries = append(queries, query2)
  205. }
  206. //// 3. 不限制省市
  207. //if province == "" {
  208. // query3 := elastic.NewBoolQuery()
  209. // query3 = query3.Must(elastic.NewMultiMatchQuery(projectName, field).Type("phrase"))
  210. // query3 = query3.Filter(filters...)
  211. // if !t.IsZero() {
  212. // query3 = query3.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  213. // }
  214. // queries = append(queries, query3)
  215. //}
  216. }
  217. for _, query := range queries {
  218. fetchFields := elastic.NewFetchSourceContext(true).Include(
  219. "id", "title", "projectname", "projectcode", "bidamount", "area", "city",
  220. "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel",
  221. "s_winner", "winnertel", "agency", "publishtime")
  222. searchResult, err := client.Search().
  223. Index("bidding").
  224. Query(query).
  225. Size(maxResults).
  226. FetchSourceContext(fetchFields).
  227. Do(context.Background())
  228. if err != nil {
  229. return nil, err
  230. }
  231. for _, hit := range searchResult.Hits.Hits {
  232. if !seenIDs[hit.Id] {
  233. seenIDs[hit.Id] = true
  234. allResults = append(allResults, hit)
  235. }
  236. }
  237. }
  238. }
  239. // detail 的命中足够就提前结束
  240. if field == "detail" && len(allResults) > maxResults {
  241. break
  242. }
  243. }
  244. return allResults, nil
  245. }
  246. func searchByToken(client *elastic.Client, projectName, province, city, publish string, maxResults int) ([]*elastic.SearchHit, error) {
  247. fieldsToTry := []string{"projectname.pname", "title", "detail"}
  248. filtersToTry := [][]elastic.Query{
  249. {elastic.NewTermsQuery("subtype", "中标", "成交", "合同", "单一")},
  250. {elastic.NewTermsQuery("toptype", "招标", "预告", "采购意向")},
  251. {elastic.NewTermsQuery("toptype", "拟建")},
  252. }
  253. // 解析时间
  254. var t time.Time
  255. var err error
  256. if publish != "" {
  257. t, err = time.Parse("200601", publish)
  258. if err != nil {
  259. log.Println("时间解析失败:", err)
  260. }
  261. }
  262. // 分词处理
  263. analyzeResp, err := client.IndexAnalyze().
  264. Index("bidding").
  265. Analyzer("ik_smart").
  266. Text(projectName).
  267. Do(context.Background())
  268. if err != nil {
  269. return nil, err
  270. }
  271. var tokens []string
  272. for _, token := range analyzeResp.Tokens {
  273. tokens = append(tokens, token.Token)
  274. }
  275. if len(tokens) == 0 {
  276. return nil, fmt.Errorf("no tokens found from ik_smart")
  277. }
  278. queryText := strings.Join(tokens, " ")
  279. // 指定返回字段
  280. fetchFields := elastic.NewFetchSourceContext(true).Include(
  281. "id", "title", "projectname", "projectcode", "bidamount", "area", "city",
  282. "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel",
  283. "s_winner", "winnertel", "agency", "publishtime")
  284. var allHits []*elastic.SearchHit
  285. seen := make(map[string]bool)
  286. for _, field := range fieldsToTry {
  287. var dateRangeStart, dateRangeEnd int64
  288. if !t.IsZero() {
  289. if field == "detail" {
  290. dateRangeStart, dateRangeEnd = getYearRange(t, 60)
  291. } else {
  292. dateRangeStart, dateRangeEnd = getYearRange(t, 36)
  293. }
  294. }
  295. for _, filters := range filtersToTry {
  296. var queries []*elastic.BoolQuery
  297. if field == "detail" {
  298. query := elastic.NewBoolQuery()
  299. if province != "" {
  300. query = query.Must(elastic.NewTermQuery("area", province))
  301. }
  302. //query = query.Must(elastic.NewMatchQuery(field, queryText)).
  303. // Filter(filters...)
  304. query = query.
  305. Must(
  306. elastic.NewMultiMatchQuery(queryText, field).
  307. MinimumShouldMatch("100%"),
  308. ).
  309. Filter(filters...)
  310. if !t.IsZero() {
  311. query = query.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  312. }
  313. queries = append(queries, query)
  314. } else {
  315. // 省+市
  316. if province != "" && city != "" {
  317. q := elastic.NewBoolQuery().
  318. Must(elastic.NewMatchQuery(field, queryText)).
  319. Must(elastic.NewTermQuery("area", province)).
  320. Must(elastic.NewTermQuery("city", city)).
  321. Filter(filters...)
  322. if !t.IsZero() {
  323. q = q.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  324. }
  325. queries = append(queries, q)
  326. }
  327. // 仅省
  328. if province != "" {
  329. q := elastic.NewBoolQuery().
  330. Must(elastic.NewMatchQuery(field, queryText)).
  331. Must(elastic.NewTermQuery("area", province)).
  332. Filter(filters...)
  333. if !t.IsZero() {
  334. q = q.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  335. }
  336. queries = append(queries, q)
  337. }
  338. //// 无省市
  339. //q := elastic.NewBoolQuery().
  340. // Must(elastic.NewMatchQuery(field, queryText)).
  341. // Filter(filters...)
  342. //if !t.IsZero() {
  343. // q = q.Must(elastic.NewRangeQuery("publishtime").Gte(dateRangeStart).Lt(dateRangeEnd))
  344. //}
  345. //queries = append(queries, q)
  346. }
  347. for _, query := range queries {
  348. searchResult, err := client.Search().
  349. Index("bidding").
  350. Query(query).
  351. Size(maxResults).
  352. FetchSourceContext(fetchFields).
  353. Do(context.Background())
  354. if err != nil {
  355. continue
  356. }
  357. for _, hit := range searchResult.Hits.Hits {
  358. if !seen[hit.Id] {
  359. seen[hit.Id] = true
  360. allHits = append(allHits, hit)
  361. if len(allHits) >= maxResults {
  362. return allHits, nil
  363. }
  364. }
  365. }
  366. }
  367. }
  368. if field == "detail" && len(allHits) >= maxResults {
  369. break
  370. }
  371. }
  372. return allHits, nil
  373. }
  374. func searchCommon(client *elastic.Client, projectName, province, city, publish string, maxResults int) ([]*elastic.SearchHit, error) {
  375. fields := []string{"projectname.pname", "title", "detail"}
  376. var t time.Time
  377. var err error
  378. if publish != "" {
  379. t, err = time.Parse("200601", publish)
  380. if err != nil {
  381. log.Println("时间解析失败:", err)
  382. }
  383. }
  384. var allHits []*elastic.SearchHit
  385. seen := make(map[string]bool)
  386. fetchFields := elastic.NewFetchSourceContext(true).Include(
  387. "id", "title", "projectname", "projectcode", "bidamount", "area", "city",
  388. "toptype", "subtype", "buyer", "budget", "buyerperson", "buyertel",
  389. "s_winner", "winnertel", "agency", "publishtime")
  390. for _, field := range fields {
  391. var dateRangeStart, dateRangeEnd int64
  392. if !t.IsZero() {
  393. if field == "detail" {
  394. dateRangeStart, dateRangeEnd = getYearRange(t, 60)
  395. } else {
  396. dateRangeStart, dateRangeEnd = getYearRange(t, 36)
  397. }
  398. }
  399. var queries []map[string]interface{}
  400. commonQuery := func(f string, boost float64) map[string]interface{} {
  401. return map[string]interface{}{
  402. "common": map[string]interface{}{
  403. f: map[string]interface{}{
  404. "query": projectName,
  405. "cutoff_frequency": 0.01,
  406. "low_freq_operator": "and",
  407. "boost": boost,
  408. },
  409. },
  410. }
  411. }
  412. if field == "detail" {
  413. // 只做普通匹配 + 时间
  414. boolQuery := map[string]interface{}{}
  415. if province != "" {
  416. boolQuery = map[string]interface{}{
  417. "bool": map[string]interface{}{
  418. "must": []interface{}{
  419. commonQuery(field, 0.1),
  420. map[string]interface{}{"term": map[string]interface{}{"area": province}},
  421. },
  422. },
  423. }
  424. } else {
  425. boolQuery = map[string]interface{}{
  426. "bool": map[string]interface{}{
  427. "must": []interface{}{
  428. commonQuery(field, 0.1),
  429. },
  430. },
  431. }
  432. }
  433. if !t.IsZero() {
  434. boolQuery["bool"].(map[string]interface{})["filter"] = []interface{}{
  435. map[string]interface{}{
  436. "range": map[string]interface{}{
  437. "publishtime": map[string]interface{}{
  438. "gte": dateRangeStart,
  439. "lt": dateRangeEnd,
  440. },
  441. },
  442. },
  443. }
  444. }
  445. queries = append(queries, boolQuery)
  446. } else {
  447. // 1. 省+市
  448. if province != "" && city != "" {
  449. q := map[string]interface{}{
  450. "bool": map[string]interface{}{
  451. "must": []interface{}{
  452. commonQuery(field, 0.2),
  453. map[string]interface{}{"term": map[string]interface{}{"area": province}},
  454. map[string]interface{}{"term": map[string]interface{}{"city": city}},
  455. },
  456. },
  457. }
  458. if !t.IsZero() {
  459. q["bool"].(map[string]interface{})["filter"] = []interface{}{
  460. map[string]interface{}{
  461. "range": map[string]interface{}{
  462. "publishtime": map[string]interface{}{
  463. "gte": dateRangeStart,
  464. "lt": dateRangeEnd,
  465. },
  466. },
  467. },
  468. }
  469. }
  470. queries = append(queries, q)
  471. }
  472. // 2. 仅省
  473. if province != "" {
  474. q := map[string]interface{}{
  475. "bool": map[string]interface{}{
  476. "must": []interface{}{
  477. commonQuery(field, 0.2),
  478. map[string]interface{}{"term": map[string]interface{}{"area": province}},
  479. },
  480. },
  481. }
  482. if !t.IsZero() {
  483. q["bool"].(map[string]interface{})["filter"] = []interface{}{
  484. map[string]interface{}{
  485. "range": map[string]interface{}{
  486. "publishtime": map[string]interface{}{
  487. "gte": dateRangeStart,
  488. "lt": dateRangeEnd,
  489. },
  490. },
  491. },
  492. }
  493. }
  494. queries = append(queries, q)
  495. }
  496. //// 3. 不加省市
  497. //q := map[string]interface{}{
  498. // "bool": map[string]interface{}{
  499. // "must": []interface{}{
  500. // commonQuery(field, 0.2),
  501. // },
  502. // },
  503. //}
  504. //if !t.IsZero() {
  505. // q["bool"].(map[string]interface{})["filter"] = []interface{}{
  506. // map[string]interface{}{
  507. // "range": map[string]interface{}{
  508. // "publishtime": map[string]interface{}{
  509. // "gte": dateRangeStart,
  510. // "lt": dateRangeEnd,
  511. // },
  512. // },
  513. // },
  514. // }
  515. //}
  516. //queries = append(queries, q)
  517. }
  518. for _, q := range queries {
  519. // 编码 query 为 base64
  520. queryBytes, _ := json.Marshal(q)
  521. queryBase64 := base64.StdEncoding.EncodeToString(queryBytes)
  522. query := elastic.NewWrapperQuery(queryBase64)
  523. searchResult, err := client.Search().
  524. Index("bidding").
  525. Query(query).
  526. Size(maxResults).
  527. FetchSourceContext(fetchFields).
  528. Do(context.Background())
  529. if err != nil {
  530. log.Println("searchCommon 查询失败:", err)
  531. continue
  532. }
  533. for _, hit := range searchResult.Hits.Hits {
  534. if !seen[hit.Id] {
  535. seen[hit.Id] = true
  536. allHits = append(allHits, hit)
  537. if len(allHits) >= maxResults {
  538. return allHits, nil
  539. }
  540. }
  541. }
  542. }
  543. }
  544. return allHits, nil
  545. }
  546. // getYearRange calculates a date range of +/- 1 year from the base date.
  547. func getYearRange(baseDate time.Time, m int) (start, end int64) {
  548. endTime := baseDate.AddDate(0, m, 0)
  549. startTime := baseDate.AddDate(0, -m, 0)
  550. return startTime.Unix(), endTime.Unix()
  551. }