post_process.py 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. # -*- coding: utf-8 -*-
  2. """
  3. Phase 6 migration from interface/getAttributes.py.
  4. This module groups the post-processing helpers that refine the assembled
  5. ``prem`` structure (package-role-money) after the initial extraction:
  6. correcting role money against table totals, limiting amounts by industry
  7. thresholds, merging table-extracted prem into the existing one, rule-based
  8. role supplementation, conflict confirmation, package naming, single-source
  9. fixups, demand-driven prem expansion and candidate manager enrichment.
  10. Migrated functions:
  11. getPREMs, correct_rolemoney, limit_maximum_amount, update_prem,
  12. rule_add_role, confirm_prem, add_package_name, fix_single_source,
  13. demand_to_prem, add_manager
  14. """
  15. from __future__ import absolute_import
  16. import re
  17. import copy
  18. import json
  19. import uuid
  20. from decimal import Decimal
  21. from BiddingKG.dl.common.Utils import log, money_process, get_money_entity, getCurrent_date
  22. def getPREMs(list_sentences,list_entitys,list_articles,list_outlines,page_time,winter_scope):
  23. '''
  24. @param:
  25. list_sentence:所有文章的句子list
  26. list_entity:所有文章的实体list
  27. @return:list of dict which include文章的包-角色-实体名称-金额-联系人-联系电话
  28. '''
  29. from BiddingKG.dl.assembly.role_money import getPackageRoleMoney
  30. from BiddingKG.dl.assembly.time_attrs import getTimeAttributes
  31. from BiddingKG.dl.assembly.contacts import getProjectContacts
  32. result = []
  33. for list_sentence,list_entity,list_article,list_outline in zip(list_sentences,list_entitys,list_articles,list_outlines):
  34. RoleList = getPackageRoleMoney(list_sentence,list_entity,list_outline,winter_scope)
  35. result.append(dict({"prem": RoleList, "docid": list_article.doc_id},
  36. **getTimeAttributes(list_entity, list_sentence,page_time),
  37. **getProjectContacts(list_entity, list_sentence),
  38. **{"fingerprint": list_article.fingerprint,
  39. "match_enterprise": list_article.match_enterprise,
  40. "match_enterprise_type": list_article.match_enterprise_type,
  41. "process_time": getCurrent_date(),
  42. "attachmentTypes": list_article.attachmentTypes, "bidway": list_article.bidway}))
  43. # result.append(dict({"prem":RoleList,"docid":list_article.doc_id},**getOtherAttributes(list_entity),**getTimeAttributes(list_entity,list_sentence),
  44. # **{"fingerprint":list_article.fingerprint,"match_enterprise":list_article.match_enterprise,
  45. # "match_enterprise_type":list_article.match_enterprise_type,"process_time":getCurrent_date(),
  46. # "attachmentTypes":list_article.attachmentTypes, "bidway": list_article.bidway}))
  47. return result
  48. def correct_rolemoney(docid, prem, total_product_money, total_budget, list_articles): # 2022/9/26修改为 中标金额小于表格单价数量合计总金额十分之一时替换
  49. '''
  50. 最后根据表格提取的单价数量合计对比更新中标金额,或中标金额为0全文只有一个总价或合计时,作为中标金额
  51. :param docid: 公告编号
  52. :param prem: 列表
  53. :param total_product_money: 表格统计金额
  54. :param total_budget: 表格合计预算金额
  55. :param list_articles: 文章对象
  56. :return:
  57. '''
  58. if '##attachment##' in list_articles[0].content:
  59. content, attachment = list_articles[0].content.split('##attachment##')
  60. if len(content) < 200:
  61. content += attachment
  62. else:
  63. content = list_articles[0].content
  64. if len(re.findall('win_tenderer|second_tenderer|third_tenderer', str(prem[0]['prem'])))==1:# and re.search('(中标|成交|合同|投标))?(总?金额|[报总]?价)((万?元))?:', content) == None: # 只有一个中标角色且没有明确中标金额表达的
  65. if total_product_money>0 and total_product_money<2000000:
  66. for value in prem[0]['prem'].values():
  67. ree_money = float(value['tendereeMoney'])
  68. for l in value['roleList']:
  69. try:
  70. # if l[0] == 'win_tenderer' and float(l[2])<total_product_money:
  71. # l[2] = total_product_money
  72. # log('修改中标金额为所有产品总金额')
  73. # if l["role_name"] == 'win_tenderer' and float(l["role_money"]['money']) == 0 and float(l["role_money"]['money'])<total_product_money/10:
  74. if l["role_name"] == 'win_tenderer' and (float(l["role_money"]['money']) == 0 or(float(l["role_money"]['money'])*2<total_product_money<500000) or (float(l["role_money"]['money'])<ree_money/2 and float(l["role_money"]['money'])<total_product_money<ree_money)): # 改为小于一半招标金额或为0时替换为合计金额
  75. l["role_money"]['money'] = round(total_product_money, 2)
  76. log('修改中标金额为产品总金额: %s, docid:%s' % (round(total_product_money, 2), docid))
  77. except Exception as e:
  78. print('表格产品价格修正中标价格报错:%s'%e)
  79. elif (len(re.findall('合计', content)) == 1 or len(re.findall('总价', content)) == 1):
  80. ser = re.search('(?P<header>合计((万?元))?:)(?P<money>[\d,.]+(万?元)?)', content) if len(re.findall('合计', content)) == 1 else re.search('(?P<header>总价((万?元))?:)(?P<money>[\d,.]+(万?元)?)', content)
  81. if ser:
  82. money_text = ser.group('money')
  83. header = ser.group('header')
  84. money, money_unit = money_process(money_text, header)
  85. if 100<money<8000000:
  86. for value in prem[0]['prem'].values():
  87. for l in value['roleList']:
  88. try: # 如果原中标金额为0 或 金额小于合计金额0.1倍且正文没中标金额关键词 替换为 合计金额
  89. if l["role_name"] == 'win_tenderer' and (float(l["role_money"]['money'])==0 or (float(l["role_money"]['money']) < money / 10 and re.search('(中标|成交|合同)(总?金额|[单报总]?价)', content) == None)):
  90. l["role_money"]['money'] = str(money)
  91. l["role_money"]['money_unit'] = money_unit
  92. log('修改中标金额为总价或合计金额: %s, docid:%s'%(money, docid))
  93. except Exception as e:
  94. print('修正中标价格报错:%s' % e)
  95. if 100 < total_budget < 2000000 and len(prem[0]['prem']) == 1 and 'Project' in prem[0]['prem'] and float(prem[0]['prem']['Project']["tendereeMoney"]) < total_budget:
  96. prem[0]['prem']['Project']["tendereeMoney"] = round(total_budget, 2)
  97. log('修改招标金额为表格合计预算: %s, docid:%s' % (round(total_budget, 2), docid))
  98. def limit_maximum_amount(dic, list_entity):
  99. '''
  100. 通过关键词、行业、公告类别等设置最高最低角色金额
  101. :param dic: 最终返回所有字段结果字典
  102. :param list_entity: 实体列表
  103. :return:
  104. '''
  105. indu_amount = {
  106. '计算机设备': 200000000,
  107. '办公设备': 100000000,
  108. '家具用具': 500000000,
  109. '办公消耗用品及类似物品': 100000000,
  110. '日杂用品': 100000000,
  111. '餐饮业': 1000000000,
  112. '物业管理': 1000000000,
  113. '工程技术与设计服务': 1000000000,
  114. '工程评价服务': 100000000,
  115. '其他工程服务': 100000000,
  116. '工程监理服务': 100000000,
  117. '工程造价服务': 100000000,
  118. '会计、审计及税务服务': 100000000,
  119. '其他专业咨询与调查': 100000000
  120. }
  121. title = dic.get('doctitle_refine', '')
  122. name = dic.get('name', '')
  123. product = ','.join(dic.get('product', []))
  124. text = "%s;%s;%s"%(title, name, product)
  125. doctype = dic.get('docchannel', {}).get('doctype', '') # 公告类型
  126. industry = dic['industry'].get('class_name', '')
  127. category = dic['industry'].get('class', '') # 行业门类
  128. moneys = [float(it.entity_text) for it in list_entity if it.entity_type=='money' and re.search('^\d+(\.\d+)?', it.entity_text) and 5000<float(it.entity_text)<5000000]
  129. maximum_amount = 10000000000
  130. minximum_amount = 100
  131. if re.search('监理|造价咨询|设计|勘察|招标代理中介服务|工程审计', text) and re.search('施工|总承包|ppp|PPP', text.replace('施工监理', '监理'))==None:
  132. # print('监理设计等限额')
  133. maximum_amount = 1000000000
  134. minximum_amount = 200
  135. elif re.search('施工|总承包|ppp|PPP|公路|道路|桥梁|铁路|土地使用权|地块|棚改|征地拆迁|棚户区改造|土地征收|建设用地|社会保险', text) or category in ['金融业', '建筑业'] or doctype == '土地矿产':
  136. # print('施工、铁路等限额')
  137. if industry in ['科研、医疗、教育用房', '住宅、商业用房', '场馆、站港用房','工业、生产用房','专业施工']:
  138. maximum_amount = 20000000000
  139. minximum_amount = 200
  140. elif industry in ['修缮工程', '电气安装', '管道和设备安装', '建筑装饰和装修业', '建筑物拆除和场地准备活动']:
  141. maximum_amount = 10000000000
  142. minximum_amount = 100
  143. else:
  144. maximum_amount = 50000000000
  145. minximum_amount = 500
  146. elif re.search('(办公|体育)(用品|设备|器材)|耗材|打印机|复印机|打印纸|粉盒|墨粉|复印纸|网上超市|电子卖场|家电|配电箱采购|配件|备件', text):
  147. # print('商品采购限额')
  148. maximum_amount = 80000000
  149. minximum_amount = 10
  150. elif re.search('修理|维修|(安保|保安|安全|保洁|物业|后勤|管理|代理|中介|印刷)服务', text):
  151. # print('维修限额')
  152. maximum_amount = 50000000
  153. elif re.search('(速递|快递|邮政|邮寄)(物流)?服务', text):
  154. # print('快递限额')
  155. maximum_amount = 80000000
  156. minximum_amount = 10
  157. elif industry in indu_amount:
  158. maximum_amount = indu_amount[industry]
  159. # print('maximum_amount:', maximum_amount)
  160. for value in dic['prem'].values():
  161. for l in value['roleList']:
  162. if l["role_name"] in ['win_tenderer', 'second_tenderer', 'third_tenderer']:
  163. # date = float(re.search('(\d+)天', l.get('serviceTime', '')).group(1)) if re.search('(\d+)天', l.get('serviceTime', '')) else 0
  164. serviceTime_dict = l.get('serviceTime', dict())
  165. serviceTime_dict = serviceTime_dict if serviceTime_dict else dict()
  166. date = serviceTime_dict.get("service_days",0)
  167. if 0 < date < 180 and float(l["role_money"]['money']) > 10000000000: # 工期小于180天且金额大于百亿的,错误
  168. l["role_money"]['money'] = str(Decimal(l["role_money"]['money']) / 10000)
  169. # print('工期纠正百亿以上金额 ')
  170. elif float(l["role_money"]['money']) > maximum_amount:
  171. flag = 1
  172. for money in moneys:
  173. if float(l["role_money"]['money'])/money == 10000 and l['role_money']['money_unit'] == '万元':
  174. l["role_money"]['money'] = str(Decimal(l["role_money"]['money']) / 10000)
  175. # print('万倍关系纠正连接金额')
  176. flag = 0
  177. break
  178. if flag and l["role_money"]['money_unit'] == '万元' or re.search('^\d{11,}(\.0)?$', str(l["role_money"]['money'])):
  179. l["role_money"]['money'] = str(Decimal(l["role_money"]['money']) / 10000)
  180. # print('行业限额纠正连接金额')
  181. elif industry in ['餐饮业', '物业管理'] and maximum_amount == indu_amount[industry]:
  182. l["role_money"]['money'] = str(Decimal(l["role_money"]['money']) / 10000)
  183. # elif flag and l["role_money"]['money_unit'] == '元':
  184. # l["role_money"]['money'] = 0
  185. elif 0<float(l["role_money"]['money']) < minximum_amount:
  186. if l["role_money"]['money_unit'] == '元' and re.search('^\d{1,2}\.\d{4,6}$', str(l["role_money"]['money'])):
  187. # print('单位元小金额且格式类似万元的乘以万倍')
  188. l["role_money"]['money'] = str(Decimal(l["role_money"]['money']) * 10000)
  189. # else: # 20241011 取消小于最低金额改为0 避免小金额不提取 例:520248605
  190. # # print('中标金额小于限额:%d元 去除' % minximum_amount)
  191. # l["role_money"]['money'] = 0
  192. if float(value['tendereeMoney']) > maximum_amount:
  193. flag = 1
  194. for money in moneys:
  195. if float(value['tendereeMoney'])/money == 10000 and l['role_money']['money_unit'] == '万元':
  196. value['tendereeMoney'] = str(Decimal(value['tendereeMoney'])/10000)
  197. # print('万倍关系纠正连接金额')
  198. flag = 0
  199. break
  200. if (flag and value['tendereeMoneyUnit'] == '万元' or re.search('^\d{11,}(\.0)?$', str(value['tendereeMoney']))) and float(value['tendereeMoney']) > maximum_amount*100: #2024/5/23 改为单位万元且超过限额100倍才除一万,避免不合理纠正 比如 174255856 项目(系统)一亿变一万
  201. value['tendereeMoney'] = str(Decimal(value['tendereeMoney']) / 10000)
  202. # print('行业限额纠正连接金额')
  203. elif industry in ['餐饮业', '物业管理'] and maximum_amount == indu_amount[industry]:
  204. value['tendereeMoney'] = str(Decimal(value['tendereeMoney']) / 10000)
  205. # elif flag and value['tendereeMoneyUnit'] == '元':
  206. # value['tendereeMoney'] = 0
  207. elif 0<float(value['tendereeMoney']) < minximum_amount:
  208. if value['tendereeMoneyUnit'] == '元' and re.search('^\d{1,2}\.\d{4,6}$', str(value['tendereeMoney'])):
  209. # print('单位元小金额且格式类似万元的乘以万倍')
  210. value['tendereeMoney'] = str(Decimal(value['tendereeMoney']) * 10000)
  211. # else: # 20241011 取消小于最低金额改为0 避免小金额不提取 例:520248605
  212. # # print('招标金额小于限额:%d元 去除' % minximum_amount)
  213. # value['tendereeMoney'] = 0
  214. def update_prem(old_prem, new_prem, in_attachment=False):
  215. '''
  216. 根据新旧对比,更新数据
  217. :param old_prem:
  218. :param new_prem: 表格提取的要素
  219. :return:
  220. '''
  221. if len(new_prem) >= 1 :
  222. '''如果表格提取的包大于2,原来的包比表格提取的包多则删除原来多余的包,以表格的为准 20250528补充条件表格提取需有中标人避免 626464296 替换错误'''
  223. if len(new_prem) >= 2 and 'win_tenderer' in json.dumps(new_prem) and (len(new_prem)<len(old_prem) <= len(new_prem)*2 or set(old_prem)&set(new_prem)==set()): # 修复类似443925411 标的+标包才算标段号
  224. del_k = []
  225. for k in old_prem:
  226. if k not in new_prem and k != 'Project':
  227. del_k.append(k)
  228. for k in del_k:
  229. old_prem.pop(k)
  230. if len(old_prem) > len(new_prem) and len(new_prem)>=1 and in_attachment==False: # 如果表格有提取,非表格包数比表格提取多,去掉非表格在附件里提取的包
  231. del_k = []
  232. for k in old_prem:
  233. if 'in_attachment' in old_prem[k] and old_prem[k]['in_attachment'] and k not in new_prem and k != 'Project':
  234. del_k.append(k)
  235. for k in del_k:
  236. old_prem.pop(k)
  237. if in_attachment and len(new_prem) < len(old_prem)*2: # 附件表格提取的,原来提取有中标人,停止替换
  238. for v in old_prem.values():
  239. for d in v['roleList']:
  240. if d['role_name'] in ['win_tenderer', 'pre_win_tenderer']:
  241. return 0
  242. # if len(new_prem) > len(old_prem) and [k for k in new_prem if '自增' not in k] == []: # 如果表格提取包号都为自增编号且包数大于非表格提取,不进行更新 例 244355092 281854766
  243. # return None
  244. if len(old_prem) >= 2 and len(new_prem) == 1 and ('Project' in new_prem or set(new_prem)&set(old_prem)==set()): # 如果表格提取包为Project,非表格提取两个包且一个包为Project,把表格提取合并到非Project包
  245. # k = list(old_prem.keys()-set(['Project']))[0]
  246. k = sorted((old_prem.keys()-set(['Project'])), key=lambda x: x)[0]
  247. k_new = list(new_prem.keys())[0]
  248. new_prem[k] = new_prem.pop(k_new)
  249. elif len(old_prem) == 1 and len(new_prem) == 1 and 'Project' not in old_prem and set(new_prem)&set(old_prem)==set(): # 如果表格提取包与非表格提取都是一个包且不同,把表格提取包名替换为非表格包名
  250. k = list(old_prem.keys()-set(['Project']))[0]
  251. k_new = list(new_prem.keys())[0]
  252. new_prem[k] = new_prem.pop(k_new)
  253. if len(new_prem) == len(old_prem) == 1 and 'Project' not in new_prem and 'Project' in old_prem: # 如果表格提取到包号,非表格没提取到,合并到Project
  254. k = list(new_prem.keys())[0]
  255. new_prem['Project'] = new_prem.pop(k)
  256. multi_tendereeMoney = [] # 多包招标金额
  257. for k, v in new_prem.items():
  258. if k == 'Project':
  259. if 'Project' in old_prem:
  260. tmp_l = [] # 保存新旧同时包含的角色
  261. if v.get('code', "") != "":
  262. old_prem['Project']['code'] = v.get('code', "")
  263. if v.get('name', "") != "":
  264. old_prem['Project']['name'] = v.get('name', "")
  265. for d in old_prem['Project']['roleList']:
  266. for d2 in v['roleList']:
  267. if d['role_name'] == d2['role_name']: # 同时包含的角色用表格的替换
  268. tmp_l.append(d2)
  269. if d2['role_text'] != "":
  270. d['role_text'] = d2['role_text']
  271. if d2.get('serviceTime', '') != "":
  272. d['serviceTime'] = d2['serviceTime']
  273. if float(d2['role_money']['money']) != 0: # 如果表格提取的金额不为0才替换
  274. d['role_money']['money'] = d2['role_money']['money']
  275. d['role_money']['money_unit'] = d2['role_money']['money_unit']
  276. for k in set(d2)-set(d): # 把表格提取加的属性补充过来,比如:multi_winner other_winner_dic等
  277. if d2[k]:
  278. d[k] = d2[k]
  279. for d2 in v['roleList']:
  280. if d2 not in tmp_l: # 把新预测有,旧没有的角色添加上去
  281. old_prem['Project']['roleList'].append(d2)
  282. if float(old_prem['Project'].get('tendereeMoney', 0)) < float(new_prem['Project'].get('tendereeMoney', 0)) < 100000000:
  283. old_prem['Project']['tendereeMoney'] = new_prem['Project']['tendereeMoney'] # 20240508 修复 464187225 表格提取纠正招标金额错误
  284. else:
  285. old_prem[k] = v
  286. else:
  287. if v['tendereeMoney'] != 0:
  288. multi_tendereeMoney.append(v['tendereeMoney'])
  289. if k.startswith('自增'): # 表格提取的没找到包号 按行数添加包号,前面加自增,例 自增1
  290. k = k[2:]
  291. if k not in old_prem: # 新有旧没有的包直接添加
  292. old_prem[k] = v
  293. else:
  294. tmp_l = [] # 保存新旧同时包含的角色
  295. if v.get('code', "") != "":
  296. old_prem[k]['code'] = v.get('code', "")
  297. if v.get('name', "") != "":
  298. old_prem[k]['name'] = v.get('name', "")
  299. for d in old_prem[k]['roleList']:
  300. for d2 in v['roleList']:
  301. if d['role_name'] == d2['role_name']:
  302. tmp_l.append(d2)
  303. if d2['role_text'] != "":
  304. d['role_text'] = d2['role_text']
  305. if d2.get('serviceTime', '') != "":
  306. d['serviceTime'] = d2['serviceTime']
  307. if float(d2['role_money']['money']) != 0: # 如果表格提取的金额不为0才替换
  308. d['role_money']['money'] = d2['role_money']['money']
  309. d['role_money']['money_unit'] = d2['role_money']['money_unit']
  310. for k2 in set(d2)-set(d): # 把表格提取加的属性补充过来,比如:multi_winner other_winner_dic等
  311. if d2[k2]:
  312. d[k2] = d2[k2]
  313. for d2 in v['roleList']:
  314. if d2 not in tmp_l: # 把新预测有,旧没有的角色添加上去
  315. old_prem[k]['roleList'].append(d2)
  316. if float(old_prem[k].get('tendereeMoney', 0)) < float(v.get('tendereeMoney', 0)) < 100000000:
  317. old_prem[k]['tendereeMoney'] = v['tendereeMoney'] # 2024/05/24 使用表格招标金额
  318. if multi_tendereeMoney and 'Project' in old_prem and float(old_prem['Project']['tendereeMoney'])!=0: # 表格提取到多标段招标金额,去掉Project包招标金额
  319. old_prem['Project']['tendereeMoney'] = 0
  320. tenderee_l = [d2['role_text'] for v in old_prem.values() for d2 in v['roleList'] if d2['role_name']=='tenderee']
  321. winner_l = [d2['role_text'] for v in old_prem.values() for d2 in v['roleList'] if d2['role_name']=='win_tenderer']
  322. if set(tenderee_l) & set(winner_l): # 删除与中标人冲突的招标人
  323. for k in old_prem:
  324. old_prem[k]['roleList'] = [d for d in old_prem[k]['roleList'] if
  325. not(d['role_name'] == 'tenderee' and d['role_text'] in winner_l)]
  326. # print('删除与中标人冲突的招标人')
  327. # return old_prem
  328. def rule_add_role(docid, prem, channel, content, web_source_no, nlp_enterprise):
  329. def add_role(ent_name, role_type, prem, money=0):
  330. if 'Project' in prem:
  331. prem['Project']['roleList'].append(
  332. {
  333. "address": "",
  334. "linklist": [],
  335. "role_money": {
  336. "discount_ratio": "",
  337. "downward_floating_ratio": "",
  338. "floating_ratio": "",
  339. "money": money,
  340. "money_unit": ""
  341. },
  342. "role_name": role_type,
  343. "role_prob": 0.6,
  344. "role_text": ent_name,
  345. "rule_add_role": True,
  346. "serviceTime": ""
  347. }
  348. )
  349. else:
  350. prem['Project'] = {
  351. "code": "",
  352. "name": "",
  353. "roleList": [
  354. {
  355. "address": "",
  356. "linklist": [
  357. ],
  358. "role_money": {
  359. "discount_ratio": "",
  360. "downward_floating_ratio": "",
  361. "floating_ratio": "",
  362. "money": money,
  363. "money_unit": ""
  364. },
  365. "role_name": role_type,
  366. "role_prob": 0.6,
  367. "role_text": ent_name,
  368. "rule_add_role": True,
  369. "serviceTime": ""
  370. }
  371. ],
  372. "tendereeMoney": 0,
  373. "tendereeMoneyUnit": "",
  374. "uuid": str(uuid.uuid4())
  375. }
  376. if channel['docchannel']['docchannel'] in ['招标公告', '中标信息'] and re.search('"role_name": "tenderee"',json.dumps(prem)) == None:
  377. match = re.search('(招标|采购|招商)(人|方|商|单位|部门)(信息[,:]?)?(名称)?((甲方))?:(?P<name>[\w()—-]{4,35})([,。]|$)', content)
  378. match2 = re.search('(招标|采购|招商)(人|方|商|单位|部门)(信息[,:]?)?(名称)?((甲方))?:(?P<name>[\w()—-、]{25,55}(局|人民政府|委员会|执法队))?)([,。]|$)', content)
  379. if match:
  380. ent_name = match.group('name')
  381. if re.search('测试|演示|某|\d号|\*|XX', ent_name)==None and re.search('^(\w{1,5}[省市县区]|乌鲁木齐)[\w()]{2,25}([厂店铺市场行部城室馆中心站处社会狱所园关局司署段厅院队小学村]|社区)((个体工商户)?|(普通合伙)?)?$',
  382. ent_name): # or is_enterprise_exist(ent_name)
  383. log('规则补充招标人角色:%s,docid:%s'%(ent_name, docid))
  384. add_role(ent_name, "tenderee", prem)
  385. elif match2:
  386. ent_name = match2.group('name')
  387. log('规则补充招标人角色:%s,docid:%s' % (ent_name, docid))
  388. add_role(ent_name, "tenderee", prem)
  389. elif web_source_no == 'DX000752' and len(nlp_enterprise)==1 and re.search('更多信息点击报价地址', content): # 修复 628311260
  390. ent_name = nlp_enterprise[0]
  391. add_role(ent_name, "tenderee", prem)
  392. if channel['docchannel']['docchannel'] == '中标信息' and re.search('"role_name": "win_tenderer"',json.dumps(prem)) == None:
  393. match = re.search('((中标|中选|成交))?(人|方|供应商|服务商|单位|部门|企业)|(拟定|[,。])供应商)(信息[,:]?)?(名称)?((乙方))?:(?P<name>[\w()—-]{4,35})([,。]|$)',content)
  394. if match:
  395. ent_name = match.group('name')
  396. if re.search('测试|演示|某|\d号|\*|XX', ent_name)==None and re.search('^(\w{1,5}[省市县区]|乌鲁木齐)[\w()]{2,25}[厂店铺市场行部城室馆中心站处社会狱所园关局署段厅院队小学]((个体工商户)?|(普通合伙)?)?$',
  397. ent_name): # or is_enterprise_exist(ent_name)
  398. b_idx = match.end()
  399. idx_ = content[b_idx:].find('。')
  400. e_idx = idx_ if idx_ > 0 else 200
  401. text = content[b_idx:b_idx+e_idx]
  402. money_list, _ = get_money_entity(text)
  403. money = 0
  404. for money_tup in money_list:
  405. entity_text, b, e, unit, notes = money_tup
  406. if re.search('(中标|成交|中选|投标|合同)总?(价格?|金额)', text[max(0, b-10):b]):
  407. money = float(entity_text)
  408. break
  409. log('规则补充中标人角色:%s, 金额:%s,docid:%s'%(ent_name, money, docid))
  410. add_role(ent_name, "win_tenderer", prem, money)
  411. def confirm_prem(docid, prem, channel_dic, content, is_deposit_project=False, total_tendereeMoney=0, all_tenderer=set()):
  412. '''
  413. 规则检查纠正prem,如果Project包中标人在其他包中标人,去掉project包中标角色;如果有其他包中标人,去掉roleList为空的包;
  414. :param prem: prem 字段字典
  415. :return:
  416. '''
  417. if len(prem) > 1: # 表格提取到中标人的,去掉project包中标人
  418. pro_winner = set()
  419. other_winner = set()
  420. other_winner_prob = 0
  421. pro_winner_prob = 0
  422. empty_roleList = []
  423. for k in prem:
  424. prem[k]['uuid'] = str(uuid.uuid4()) # 20240627 每个包都添加uuid
  425. if prem[k]['roleList'] == []:
  426. empty_roleList.append(k)
  427. for d in prem[k]['roleList']:
  428. if d['role_name'] in ['win_tenderer', 'pre_win_tenderer', 'second_tenderer','third_tenderer']:
  429. if k == 'Project':
  430. pro_winner.add(d['role_text'])
  431. if 'win_tenderer_joint' in d:
  432. pro_winner.update(set(d['win_tenderer_joint'].split(',')))
  433. if 'multi_winner' in d:
  434. pro_winner.update(set(d['multi_winner'].split(',')))
  435. if d['role_name'] == 'win_tenderer' and d.get('role_prob', 0)>0.6:
  436. pro_winner_prob = d.get('role_prob', 0)
  437. else:
  438. other_winner.add(d['role_text'])
  439. if 'win_tenderer_joint' in d:
  440. other_winner.update(set(d['win_tenderer_joint'].split(',')))
  441. if 'multi_winner' in d:
  442. other_winner.update(set(d['multi_winner'].split(',')))
  443. if d['role_name'] == 'win_tenderer' and d.get('role_prob', 0)>0.6:
  444. other_winner_prob = d.get('role_prob', 0)
  445. if pro_winner!=set() and (pro_winner & other_winner != set() or other_winner_prob>pro_winner_prob): # 如果默认包与其他包中标人重复或其他包中标人概率比默认包大,删除默认包中标人
  446. prem['Project']['roleList'] = [d for d in prem['Project']['roleList'] if
  447. d['role_name'] not in ['win_tenderer', 'second_tenderer',
  448. 'third_tenderer']]
  449. elif other_winner_prob<pro_winner_prob and len(prem)==2: # 两个包情况,如果默认包中标人概率比其他包大,删除其他包
  450. rm_k = [k for k in prem if k != 'Project']
  451. for k in rm_k:
  452. prem.pop(k)
  453. if other_winner and channel_dic['docchannel']['docchannel'] in ['中标信息', '候选人公示', '合同公告']:
  454. for k in empty_roleList:
  455. prem.pop(k)
  456. elif "Project" in prem:
  457. prem['Project']['uuid'] = str(uuid.uuid4())
  458. if len(prem) == 2:
  459. del_k = [k for k,v in prem.items() if v.get('roleList', [])==[] and v.get('tendereeMoney', 0)==0 and v.get('unit_tendereeMoney', 0)==0] # 20250310 删除掉没有角色且招标金额为0 的包
  460. for k in del_k:
  461. prem.pop(k)
  462. # print('删除掉没有角色且招标金额为0 的包', k)
  463. if is_deposit_project and float(total_tendereeMoney)!=0 and len(prem)==1: #20241107 存款类项目有总投资没招标金额且只有一个标段,把总投资作招标金额
  464. for k in prem:
  465. if float(prem[k]['tendereeMoney'])==0:
  466. prem[k]['tendereeMoney'] = total_tendereeMoney
  467. # 采购人:某单位 等去掉tenderee
  468. if "Project" in prem and re.search('(招标|采购|招商)(人|商|单位|部门)(信息[,:]?)?(名称)?((甲方))?:[\u4e00-\u9fa5]{,3}某+(单位|部)', content):
  469. for d in prem['Project']['roleList']:
  470. if d['role_name'] == 'tenderee' and d.get('role_prob', 0)<0.7: # 581195772 小于0.7才删除,附件明确表达可能0.7多
  471. log('规则去除文中包含“采购人:某单位”等概率小于0.7的招标人:%s,docid:%s'%(d.get('role_text', ''), docid))
  472. prem['Project']['roleList'].remove(d)
  473. if "Project" in prem and re.search('(招标|采购|招商)(人|商|单位|部门)(信息[,:]?)?(名称)?((甲方)?)?:zycgr\d+', content): # 修复 中国政府采购网 不公布具体名称 例:716907959
  474. for d in prem['Project']['roleList']:
  475. if d['role_name'] == 'tenderee' and d.get('role_prob', 0)<0.7: # 581195772 小于0.7才删除,附件明确表达可能0.7多
  476. log('规则去除文中包含“采购人:zycgr”等概率小于0.7的招标人:%s,docid:%s'%(d.get('role_text', ''), docid))
  477. prem['Project']['roleList'].remove(d)
  478. if len(prem) > 10:
  479. log('prem包数量大于10,当前包数量:%d, docid:%s'%(len(prem), docid))
  480. # 验证中标人招标人是否冲突
  481. tenderee = ""
  482. tenderee_prob = 0.5
  483. win_channel = True if channel_dic['docchannel']['docchannel'] in ['中标信息', '候选人公示', '合同公告'] else False
  484. win_tenderer_set = set()
  485. tenderee_or_agency = set()
  486. agency = ''
  487. for k, v in prem.items():
  488. win_money = 0
  489. for d in v.get('roleList', []):
  490. if d.get('role_name', '') == 'tenderee' and k == 'Project':
  491. tenderee = d['role_text']
  492. tenderee_prob = d.get('role_prob', 0.5)
  493. elif d.get('role_name', '') == 'agency' and k == 'Project':
  494. agency = d['role_text']
  495. elif d.get('role_name', '') in ['win_tenderer', 'pre_win_tenderer', 'second_tenderer', 'third_tenderer']:
  496. win_tenderer_set.add(d['role_text'])
  497. if d.get('role_name', '') in ['win_tenderer', 'pre_win_tenderer'] and float(d.get('role_money', {}).get('money', 0)):
  498. win_money = float(d.get('role_money', {}).get('money', 0))
  499. if d.get('role_name') in ['tenderee', 'agency']:
  500. tenderee_or_agency.add(d['role_text'])
  501. elif d.get('role_name') in ['win_tenderer', 'pre_win_tenderer', 'second_tenderer', 'third_tenderer']:
  502. if d['role_text'] not in all_tenderer:
  503. all_tenderer.add(d['role_text'])
  504. if 'multi_winner' in d:
  505. for item in d['multi_winner'].split(','):
  506. if item not in all_tenderer:
  507. all_tenderer.add(item)
  508. if 'win_tenderer_joint' in d:
  509. for item in d['win_tenderer_joint'].split(','):
  510. if item not in all_tenderer:
  511. all_tenderer.add(item)
  512. # 招标金额小于中标金额的, 去掉招标金额
  513. if win_money and 0 < float(v.get('tendereeMoney', 0))*1.1 < win_money:
  514. log('招标金额比中标金额小,去除招标金额:%d, %d ,docid:%s'%(float(v.get('tendereeMoney', 0)), win_money, docid))
  515. v['tendereeMoney'] = 0
  516. # 招标单位和代理机构在同一个公告里是一致的,就只保留招标单位
  517. if tenderee and tenderee == agency:
  518. prem['Project']['roleList'] = [it for it in prem['Project']['roleList'] if it.get('role_name', '') != 'agency']
  519. if tenderee != '' and len(win_tenderer_set) > 0 and (tenderee in ' '.join(win_tenderer_set) or [it for it in win_tenderer_set if it in tenderee]):
  520. conflict_short = [it for it in win_tenderer_set if it in tenderee] # 名称重叠冲突角色
  521. conflict_long = [it for it in win_tenderer_set if tenderee in it] # 名称重叠冲突角色 解决 751037129 采购人:贵州盘江精煤股份有限公司,承办单位:贵州盘江精煤股份有限公司物资管理分公司,
  522. remove_packages = [] # 待删除空包
  523. for k, v in prem.items():
  524. if v.get('roleList', []):
  525. if tenderee_prob >= 0.8 or not win_channel:
  526. v['roleList'] = [it for it in v['roleList'] if it.get('role_text', '') not in conflict_short + conflict_long]
  527. log('去掉冲突角色的中标角色, 中标人:%s, 招标人:%s, docid:%s'%(' '.join(conflict_short + conflict_long), tenderee, docid))
  528. elif conflict_short:
  529. v['roleList'] = [it for it in v['roleList'] if it.get('role_text', '') not in conflict_short]
  530. log('去掉冲突角色的中标角色, 中标人:%s, 招标人:%s, docid:%s' % (' '.join(conflict_short), tenderee, docid))
  531. elif conflict_long:
  532. v['roleList'] = [it for it in v['roleList'] if it.get('role_name', '')!='tenderee']
  533. log('去掉冲突角色的招标角色, 中标人:%s, 招标人:%s, docid:%s' % (' '.join(conflict_long), tenderee, docid))
  534. else:
  535. v['roleList'] = [it for it in v['roleList'] if
  536. it.get('role_text', '') not in conflict_short + conflict_long and it.get('role_name', '')!='tenderee']
  537. log('去掉冲突角色的招标中标角色, 中标人:%s, 招标人:%s, docid:%s' % (
  538. ' '.join(conflict_short + conflict_long), tenderee, docid))
  539. # if re.search('政府|医院|学院|学校|中学|小学|大学|中心|幼儿园|保健院|党校|研究院|血站|分校|银行|红十字会|防治院|研究所', tenderee):
  540. # v['roleList'] = [it for it in v['roleList'] if it.get('role_name', '')=='tenderee' and it.get('role_prob', 1) > 0.6 or (
  541. # it.get('role_name', '')!='tenderee' and tenderee not in it.get('role_text', '') and it.get('role_text', '') not in tenderee)]
  542. # log('去掉招标中标冲突的中标角色,招标人:%s, docid:%s'%(tenderee, docid))
  543. # else:
  544. # v['roleList'] = [it for it in v['roleList'] if it.get('role_name', '') != 'tenderee' or (
  545. # it.get('role_name', '') == 'tenderee' and tenderee not in it.get('role_text', '') and it.get('role_text', '') not in tenderee)]
  546. # log('去掉招标中标冲突的招标角色,招标人:%s, docid:%s' % (tenderee, docid))
  547. if not v.get('roleList', []) and not v.get("tendereeMoney", 0) and not v.get('unit_tendereeMoney', 0):
  548. remove_packages.append(k)
  549. for k in remove_packages:
  550. prem.pop(k)
  551. return {'candidate': ','.join(all_tenderer-tenderee_or_agency)}
  552. def add_package_name(prem, list_entity, product_list, name):
  553. '''
  554. 通过产品、项目名称,补充各标段包名,如果标段无包名,标段后紧接产品,把产品作为包名;如果标段数少于等于2且包名为空,补充项目名称为包名
  555. :param prem:
  556. :param list_entity:
  557. :param product_list:
  558. :param name:
  559. :return:
  560. '''
  561. if len(prem)>2 and len(product_list)>2:
  562. ent_l = []
  563. for entity in list_entity:
  564. if entity.entity_type in ['product', 'package']:
  565. ent_l.append((entity.entity_type, entity.entity_text, entity.sentence_index, entity.wordOffset_begin, entity.wordOffset_end, entity.in_attachment))
  566. ent_l.sort(key=lambda x: [x[2],x[3]])
  567. i = 0
  568. pk_dic = {}
  569. while i < len(ent_l)-1:
  570. ty1, ent1, s1, b1, e1, in_att1 = ent_l[i]
  571. ty2, ent2, s2, b2, e2, in_att2 = ent_l[i+1]
  572. if in_att1 == in_att2 and ty1 == 'package' and ty2 == 'product' and s1 == s2 and 0<b2-e1<3:
  573. pk_dic[ent1] = ent2
  574. i += 1
  575. if len(pk_dic) > 1:
  576. for k, v in prem.items():
  577. if k in pk_dic and v.get('name', '') == '':
  578. v['name'] = pk_dic[k]
  579. elif name != '' and len(prem)<=2: # 20241129 小于等于两个包且无包名称,取项目名称
  580. for k in prem:
  581. if prem[k].get('name', '') == '':
  582. prem[k]['name'] = name
  583. def fix_single_source(prem, channel_dic, original_docchannel):
  584. if prem.get('bidway', '') == '单一来源' and channel_dic['docchannel']['docchannel'] == '招标公告' and original_docchannel==52:
  585. for l in prem['prem'].values():
  586. for d in l['roleList']:
  587. if d['role_name'] == "win_tenderer":
  588. d['role_name'] = 'pre_win_tenderer'
  589. def demand_to_prem(demand, prem):
  590. if len(demand.get('data', [])) > len(prem):
  591. i = 1
  592. for d in demand.get('data', []):
  593. d['demand_id'] = i
  594. if d.get('project_name', '') != '' and d.get('budget', '') != '':
  595. if d.get('project_name', '') not in prem:
  596. prem[d.get('project_name', '')] = {
  597. 'demand_id': i,
  598. 'code': '',
  599. 'name': d.get('project_name', ''),
  600. 'roleList': [],
  601. 'tendereeMoney': d.get('budget', ''),
  602. 'tendereeMoneyUnit': ""
  603. }
  604. else:
  605. prem[d.get('project_name', '')+'_%d'%i] = {
  606. 'demand_id': i,
  607. 'code': '',
  608. 'name': d.get('project_name', ''),
  609. 'roleList': [],
  610. 'tendereeMoney': d.get('budget', ''),
  611. 'tendereeMoneyUnit': ""
  612. }
  613. i += 1
  614. def add_manager(docid, prem, list_entitys, list_sentences):
  615. '''
  616. 补充候选人的 项目负责人、经理
  617. :param prem:
  618. :param list_entitys:
  619. :param list_sentences:
  620. :return:
  621. '''
  622. if 'win_tenderer' in str(prem): # 有中标人才做下面操作
  623. managers = set() # 项目经理,项目负责人
  624. roles = set() # 候选人
  625. ent_list = [] # 实体列表
  626. role_person_dic = {}
  627. for entity in list_entitys[0]:
  628. if entity.entity_type == 'person':
  629. b = entity.wordOffset_begin
  630. s_index = entity.sentence_index
  631. sentance_text = list_sentences[0][s_index].sentence_text
  632. if re.search('(项目负责人|项目总工)([:,]?姓名)?:$', sentance_text[max(0, b - 12):b]):
  633. managers.add(entity.entity_text)
  634. ent_list.append(('person', s_index, b, entity.entity_text, 'leader'))
  635. elif re.search('(项目经理)([:,]?姓名)?:$', sentance_text[max(0, b - 12):b]):
  636. managers.add(entity.entity_text)
  637. ent_list.append(('person', s_index, b, entity.entity_text, 'manager'))
  638. elif entity.entity_type in ['org', 'company']:
  639. b = entity.wordOffset_begin
  640. s_index = entity.sentence_index
  641. lb = entity.label
  642. if lb in [2, 3, 4]:
  643. roles.add(entity.entity_text)
  644. ent_list.append(('org', s_index, b, entity.entity_text, lb))
  645. ent_list.sort(key = lambda x: [x[1], x[2]])
  646. if len(roles) > 0 and len(managers) > 0:
  647. for i in range(len(ent_list)):
  648. ent = ent_list[i]
  649. if ent[0] == 'org':
  650. for j in range(i + 1, len(ent_list)):
  651. ent2 = ent_list[j]
  652. if ent2[0] == 'person' and 0 <= ent2[1] - ent[1] < 4:
  653. if ent[3] not in role_person_dic:
  654. role_person_dic[ent[3]] = []
  655. role_person_dic[ent[3]].append(ent2)
  656. elif ent2[0] == 'org':
  657. break
  658. if role_person_dic:
  659. for v in prem.values():
  660. for d in v['roleList']:
  661. if d['role_name'] in ['win_tenderer', 'pre_win_tenderer', 'second_tenderer', 'third_tenderer']:
  662. ent = ""
  663. if d['role_text'] in role_person_dic:
  664. ent = d['role_text']
  665. elif'win_tenderer_joint' in d:
  666. for name in d['win_tenderer_joint'].split(','):
  667. if name in role_person_dic:
  668. ent = name
  669. if ent != "":
  670. for per in role_person_dic[ent]:
  671. if per[4] not in d:
  672. d[per[4]] = per[3]
  673. log('规则补充候选人项目经理:%s, docid:%s'%(per[3], docid))
  674. __all__ = ["getPREMs", "correct_rolemoney", "limit_maximum_amount", "update_prem", "rule_add_role", "confirm_prem", "add_package_name", "fix_single_source", "demand_to_prem", "add_manager"]