role.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. # -*- coding: utf-8 -*-
  2. """角色规则预测器。
  3. 按 ARCHITECTURE.md Phase 5 拆分建议,从 ``interface/predictor.py`` 迁出以下
  4. 角色/规则相关类与函数:
  5. - ``RoleRulePredictor`` — 角色规则预测(原 predictor.py 第 1613-2176 行)
  6. - ``RoleRuleFinalAdd`` — 正则补充最后一句实体角色(原 2178-2277 行)
  7. - ``TendereeRuleRecall`` — 招标人角色召回规则(原 2279-2557 行)
  8. - ``RoleGrade`` — 角色概率分级(原 2559-2952 行)
  9. - ``role_special_predictor`` — 站源特殊角色召回(原 9185-9198 行)
  10. 角色分类流程优化 Phase E 起,前三个召回类改为薄委托
  11. ``predictors/role_recall_engine.py::RoleRecallEngine``(规则外置 YAML +
  12. RoleContext 复用),类名与 ``predict`` 签名保持兼容。
  13. ``interface/predictor.py`` 仍 re-export 以上全部名称,老 import 不受影响。
  14. """
  15. from __future__ import absolute_import
  16. import re
  17. import json
  18. import threading
  19. from BiddingKG.dl.common.logging import log
  20. from BiddingKG.dl.common.context_utils import spanWindow, get_context
  21. from BiddingKG.dl.predictors._common import is_agency
  22. __all__ = [
  23. "RoleRulePredictor",
  24. "RoleRuleFinalAdd",
  25. "TendereeRuleRecall",
  26. "RoleGrade",
  27. "role_special_predictor",
  28. ]
  29. # ----------------------------------------------------------------------------
  30. # Phase E:三个召回类统一委托 RoleRecallEngine
  31. # ----------------------------------------------------------------------------
  32. # 原三个类各自内联正则、重复构建上下文的实现已合并到
  33. # ``predictors/role_recall_engine.py``:
  34. # - 规则外置:dl/rules/patterns/role_context.yaml(三组角色模式 + 金额召回)
  35. # 与 role_fallback.yaml(final_* 文末兜底、recall_* 招标人 7 级上下文链);
  36. # - 上下文复用:Phase A RoleContext(extract.py 入口构建一次,
  37. # prem / roleRule / tendereeRuleRecall 共享,contexts=None 时引擎自建)。
  38. # 此处保留旧类名与 predict 签名作为薄委托,保证 ``getPredictor`` 旧 key
  39. # (roleRule / roleRuleFinal / tendereeRuleRecall)与老 import 不变。
  40. # ----------------------------------------------------------------------------
  41. _ENGINE_LOCK = threading.Lock()
  42. _ENGINE = None
  43. def _get_recall_engine():
  44. """模块级引擎单例(惰性创建,线程安全;模式经 RuleLoader 编译缓存)。"""
  45. global _ENGINE
  46. if _ENGINE is None:
  47. with _ENGINE_LOCK:
  48. if _ENGINE is None:
  49. from BiddingKG.dl.predictors.role_recall_engine import RoleRecallEngine
  50. _ENGINE = RoleRecallEngine()
  51. return _ENGINE
  52. class RoleRulePredictor(object):
  53. """角色/金额上下文召回(原实现已合并至 RoleRecallEngine.predict_role_rule)。
  54. 兼容保留的旧属性/方法:``SET_NOT_TENDERER``、``_check_input``、
  55. ``ser_role``、``rule_predict``(ser_role/rule_predict 的 pattern_list
  56. 参数需传编译后的 Pattern,规则以 YAML 为准)。
  57. """
  58. def __init__(self):
  59. self._engine = _get_recall_engine()
  60. @property
  61. def SET_NOT_TENDERER(self):
  62. return self._engine.SET_NOT_TENDERER
  63. def _check_input(self, text, ignore=False):
  64. return self._engine._check_input(text, ignore)
  65. def ser_role(self, pattern_list, text, entity_text):
  66. return self._engine.ser_role(pattern_list, text, entity_text)
  67. def rule_predict(self, before, center, after, entity_text):
  68. return self._engine.rule_predict(before, center, after, entity_text)
  69. def predict(self, list_articles, list_sentences, list_entitys, list_codenames,
  70. channel_dic, title, on_value=0.5, all_winner=False, req_scope=[],
  71. deposit_project=False, contexts=None):
  72. """角色/金额上下文召回。
  73. :param contexts: build_role_contexts 预计算上下文(extract.py 入口已构建,
  74. 与 prem 共享);缺省时引擎内部自建(老调用方行为不变)。
  75. :return: all_tenderer 集合(所有召回的投标人实体文本)
  76. """
  77. return self._engine.predict_role_rule(
  78. list_articles, list_sentences, list_entitys, list_codenames,
  79. channel_dic, title, on_value=on_value, all_winner=all_winner,
  80. req_scope=req_scope, deposit_project=deposit_project,
  81. contexts=contexts)
  82. '''正则补充最后一句实体日期格式为招标或代理 2021/12/30'''
  83. class RoleRuleFinalAdd(object):
  84. """文末/全文格式兜底召回(原实现已合并至 RoleRecallEngine.predict_final_add,
  85. 规则外置到 role_fallback.yaml final_*)。"""
  86. def __init__(self):
  87. self._engine = _get_recall_engine()
  88. def predict(self, list_articles, list_sentences, list_entitys, list_codenames):
  89. return self._engine.predict_final_add(
  90. list_articles, list_sentences, list_entitys, list_codenames)
  91. # 招标人角色召回规则
  92. class TendereeRuleRecall(object):
  93. """招标人兜底召回(原实现已合并至 RoleRecallEngine.predict_recall,
  94. 规则外置到 role_fallback.yaml recall_*;原 entity_context_rule /
  95. subject_rule 对应引擎 _recall_entity_context / _recall_subject)。
  96. 兼容保留实例属性 ``get_tenderee``(最近一次 predict 是否召回招标人)。
  97. """
  98. def __init__(self):
  99. self._engine = _get_recall_engine()
  100. self.get_tenderee = False
  101. def predict(self, list_articles, list_sentences, list_entitys, list_codenames,
  102. contexts=None):
  103. """招标人兜底召回:7 级上下文链 + 公告主语规则。
  104. :param contexts: build_role_contexts 预计算上下文(可选,缺省自建)。
  105. """
  106. self.get_tenderee = self._engine.predict_recall(
  107. list_articles, list_sentences, list_entitys, list_codenames,
  108. contexts=contexts)
  109. return self.get_tenderee
  110. class RoleGrade():
  111. def __init__(self):
  112. self.tenderee_left_9 = "(?P<tenderee_left_9>(招标|采购|遴选|寻源|竞价|议价|比选|询比?价|比价|评选|谈判|邀标|邀请|洽谈|约谈|选取|抽取|抽选)(人|方|单位))"
  113. self.tenderee_center_8 = "(?P<tenderee_center_8>受.{5,20}委托)"
  114. self.tenderee_left_8 = "(?P<tenderee_left_8>(尊敬的供应商|项目法人|(需求|最终|发包|征集|甲|转让|出租|处置)(人|方|单位|组织|用户|业主|主体|部门|公司)))"
  115. self.tenderee_left_6 = "(?P<tenderee_left_6>(业主|建设|委托)(人|方|单位|组织|用户|业主|主体|部门|公司|企业)|业主|买方)"
  116. self.tenderee_left_5 = "(?P<tenderee_left_5>(发布)(人|方|单位|组织|用户|业主|主体|部门|公司|企业)|买方|发布机构|申报单位名称|委托人)"
  117. self.agency_left_9 = "(?P<agency_left_9>代理)"
  118. self.winTenderer_left_9 = "(?P<winTenderer_left_9>(中标|中选|中价|成交|竞得)|第[1一](名|候选)|排[名序]:1|名次:1)"
  119. self.winTenderer_left_8 = "(?P<winTenderer_left_8>(入选供应商|供货商|乙方|最[终后]选[择取]))" # 229435497 最后选择西平,县中原彩印有限公司,作为此项目中标供应商,
  120. self.winTenderer_left_6 = "(?P<winTenderer_left_6>(入围|承[接建包修做制担租销]))"
  121. self.winTenderer_right_9 = "(?P<winTenderer_right_9>^(为(中标|成交|中选)(人|单位|供应商|公司)|以\d+[\d.,]+万?元中标))"
  122. self.secondTenderer_left_9 = "(?P<secondTenderer_left_9>(第[二2](中标|中选|中价|成交)?候选(人|单位|供应商|公司)|第[二2](名|候选)|排[名序]:2|名次:2))"
  123. self.thirdTenderer_left_9 = "(?P<thirdTenderer_left_9>(第[三3](中标|中选|中价|成交)?候选(人|单位|供应商|公司)|第[三3](名|候选)|排[名序]:3|名次:3))"
  124. self.pattern_list = [self.tenderee_left_9,self.tenderee_center_8, self.tenderee_left_8,self.tenderee_left_6,self.tenderee_left_5,self.agency_left_9,
  125. self.winTenderer_left_9,self.winTenderer_left_8, self.winTenderer_right_9, self.winTenderer_left_6, self.secondTenderer_left_9, self.thirdTenderer_left_9] # 概率要由高到低 274941849
  126. self.ree_pattern = re.compile('^([((][一二三四五六七八九十\d]+[))]|[一二三四五六七八九十]+\s*[..、])凡?对本次(招标|采购|公告内容)提出询问,')
  127. self.win_pattern = re.compile(
  128. "^([((][一二三四五六七八九十\d]+[))]|[一二三四五六七八九十]+\s*[..、])[预拟]?((中标|中选|成交|(采购|招标|比选|比价|定标)结果)((成交))?(人|单位|供应商)?的?(基本|主要)?(信息|情况|概况|结果)(如下)?|[预拟]?(中标|中选|成交)((成交))?(人|供应商|单位)(名称)?(、地址)?([及和]\w{,2}(中标|投标)(价格|报价|金额))?(如下)?|中标公示单位|(采购|招标|比选|比价|定标)结果)[,:]?$")
  129. def _extract_outline_scope(self, outlines):
  130. """提取中标/招标大纲的范围"""
  131. bid_info = [] # 中标大纲范围
  132. ree_info = [] # 招标大纲范围
  133. for outline in outlines:
  134. text_, title_type, title_index, next_index, scope = outline
  135. if re.search(self.win_pattern, text_) and re.search('(未|没|是否)(中标|成交)|中标单位合同签订主体|业绩|提供', text_) == None:
  136. bid_info.append(scope)
  137. # log("提取的中标大纲:%s, docid:%s"%(text_, docid))
  138. elif re.search(self.ree_pattern, text_):
  139. ree_info.append(scope)
  140. # log("提取的招标大纲:%s, docid:%s"%(text_, docid))
  141. return bid_info, ree_info
  142. def _correct_low_prob_entities(self, agency_like_tenderee, org_tenderee, low_prob_agency,
  143. low_prob_tenderee, agency_l, low_prob_winner, all_tenderee_agency):
  144. """修正低概率实体的角色标签"""
  145. # 代理伪装招标人修正
  146. for entity in agency_like_tenderee:
  147. if not is_agency(entity.entity_text) or entity.entity_text in org_tenderee:
  148. entity.label = 0
  149. entity.values[entity.label] = 0.6
  150. else:
  151. entity.values[entity.label] = 0.5
  152. # 低概率代理→招标人(若在招标人列表)
  153. for entity in low_prob_agency:
  154. if entity.entity_text in org_tenderee:
  155. entity.label = 0
  156. entity.values[entity.label] = 0.6
  157. # 低概率招标人→代理(若在代理列表)
  158. for entity in low_prob_tenderee:
  159. if entity.entity_text in agency_l:
  160. entity.label = 1
  161. entity.values[entity.label] = 0.6
  162. # 低概率中标人→非角色(若在招标/代理列表)
  163. for entity in low_prob_winner:
  164. if entity.entity_text in all_tenderee_agency:
  165. entity.label = 5
  166. def _correct_winner_tenderee_conflict(self, org_winner, org_tenderee):
  167. if org_winner != []:
  168. flag = 0
  169. if org_tenderee != []:
  170. for ent in org_winner:
  171. if ent.entity_text in org_tenderee:
  172. # log('如果org中标人同时为招标人角色,降低中标概率:%s, %s' % (ent.entity_text, ent.label))
  173. ent.values[2] = 0.6
  174. flag = 1
  175. # if flag == 0 and company_winner != []: # 2024/04/18 注释掉 避免提取不到 273351465 供应商(乙方:湖南省第二测绘院
  176. # for ent in org_winner:
  177. # if ent.label == 2 and ent.values[2] > 0.6:
  178. # # log('如果同时包含org和company中标人,降低org中标人概率为0.6:%s, %s' % (ent.entity_text, ent.values[2]))
  179. # ent.values[2] = 0.6
  180. def _supplement_outline_entities(self,
  181. bid_info_company, ree_info_company, have_winner, have_ree,
  182. span, docid, sentences
  183. ):
  184. if have_winner == False and bid_info_company: # 优化 638075055 二、推荐中标候选人信息,邯郸市婷元紧固件制造有限公司第一名,
  185. for entity in bid_info_company:
  186. text = sentences[entity.sentence_index].sentence_text
  187. b = entity.wordOffset_begin
  188. e = entity.wordOffset_end
  189. if re.search('(单位(名称)?|投标(单位|人)(名称)?|公司名称):$', text[max(0, b-span-2):b]) and entity.label == 5:
  190. entity.label = 2
  191. entity.values[entity.label] = 0.55
  192. log('大纲规则补充中标人:%s, docid:%s'%(entity.entity_text, docid))
  193. elif entity.label == 0 and entity.values[entity.label]>0.5 and re.search('(采购|招标)(单位|人)', text[max(0, b-span-2):b])==None:
  194. entity.values[entity.label] = 0.5
  195. log('中标大纲下招标人概率降低:%s, docid:%s'%(entity.entity_text, docid))
  196. # elif len(bid_info_company) == 1 and entity.label != 2:
  197. # entity.label = 2
  198. # entity.values[entity.label] = 0.55
  199. # log('大纲规则补充中标人:%s, docid:%s' % (entity.entity_text, docid))
  200. if have_ree == False and ree_info_company: # 优化 638075055 二、推荐中标候选人信息,邯郸市婷元紧固件制造有限公司第一名,
  201. for entity in ree_info_company:
  202. text = sentences[entity.sentence_index].sentence_text
  203. b = entity.wordOffset_begin
  204. e = entity.wordOffset_end
  205. if re.search('(单位名称|([^\w]|^)名称):$', text[max(0, b-span-2):b]) and entity.label == 5:
  206. entity.label = 0
  207. entity.values[entity.label] = 0.55
  208. log('大纲规则补充招标人:%s, docid:%s'%(entity.entity_text, docid))
  209. break
  210. def _resolve_role_conflict(self, role_text_ent_dic,role_text_type_dic, docid):
  211. role_text_type_dic = {k: ' '.join(v) for k, v in role_text_type_dic.items() if len(v)>1 and '0' in v}
  212. if role_text_type_dic:
  213. log("同一个公司名称包含多个角色:%s, docid:%s"%(json.dumps(role_text_type_dic, ensure_ascii=False), docid))
  214. for k in role_text_type_dic: # 冲突角色取概率最大的类别
  215. if k in role_text_ent_dic:
  216. if max(role_text_ent_dic[k]['招标概率']) >= max(role_text_ent_dic[k]['中标概率']):
  217. log("角色冲突公司招标概率比中标概率大,去掉中标角色,公司名:%s, docid:%s" % (k, docid))
  218. for entity in role_text_ent_dic[k]['实体列表']:
  219. if entity.label in [2,3,4] and entity.values[entity.label] >= 0.5:
  220. entity.values[entity.label] = 0.4
  221. else:
  222. for entity in role_text_ent_dic[k]['实体列表']:
  223. if entity.label in [0] and entity.values[entity.label] >= 0.5:
  224. entity.values[entity.label] = 0.4
  225. log("角色冲突公司中标概率比招标概率大,去掉招标角色,公司名:%s, docid:%s" % (k, docid))
  226. def predict(self, list_sentences, list_entitys, docid, original_docchannel, title, span=15, min_prob=0.7, outlines=[]):
  227. '''
  228. 根据规则给角色分配不同等级概率;分三级:0.9-1,0.8-0.9,0.7-0.8;附件0.7-0.8,0.6-0.7,0.5-0.6
  229. 修改概率小于0.6的且在大数据代理集合里面的招标人为代理人
  230. :param list_articles:
  231. :param list_sentences:
  232. :param list_entitys:
  233. :param codeName:
  234. :return:
  235. '''
  236. role_text_type_dic = {}
  237. role_text_ent_dic = {}
  238. # 提取大纲范围
  239. bid_info, ree_info = self._extract_outline_scope(outlines)
  240. have_winner = False
  241. have_ree = False
  242. bid_info_company = []
  243. ree_info_company = []
  244. sentences = sorted(list_sentences[0], key=lambda x:x.sentence_index)
  245. role2id = {"tenderee": 0, "agency": 1, "winTenderer": 2, "secondTenderer": 3, "thirdTenderer": 4}
  246. org_winner = []
  247. company_winner = []
  248. org_tenderee = []
  249. agency_l = []
  250. agency_like_tenderee = [] # 类似招标人的代理人实体列表
  251. low_prob_agency = []
  252. low_prob_tenderee = []
  253. low_prob_winner = []
  254. all_tenderee_agency = []
  255. is_bid_record = True if re.search('开标记录', title[-8:]) else False
  256. is_result_change = True if re.search('(结果|中标|成交)[^\w]?(变更|更正)', title[-8:]) else False
  257. # 第一步:遍历实体,基础概率调整与分类
  258. for entity in list_entitys[0]:
  259. if entity.entity_type in ['org', 'company'] and entity.label == 1 and re.search('银行', entity.entity_text): # 20260528 银行不可能做代理 779393255
  260. entity.values[entity.label] = 0.49
  261. continue
  262. if entity.entity_type in ['org', 'company'] and entity.label in [0, 1, 2, 3, 4] and entity.values[entity.label]> min_prob:
  263. text = sentences[entity.sentence_index].sentence_text
  264. in_att = sentences[entity.sentence_index].in_attachment
  265. pre_prob = entity.values[entity.label] # 模型预测角色概率
  266. b = entity.wordOffset_begin
  267. e = entity.wordOffset_end
  268. not_found = 1
  269. if re.search('(乙方:甲方:|甲方((买方)?,|:)乙方((卖方)?)?:)$', text[max(0, b-span):b]):
  270. entity.label = 0 if entity.entity_type == 'org' else 5 # 修复 290777022 乙方:甲方: 重庆机场集团有限公司 错分为中标
  271. entity.values[entity.label] = 0.55
  272. continue
  273. elif re.search('(采购|招标)人(?或其?(采购|招标)?代理机构)?', text[max(0, b-span-2):b]): # 修复 275206588 招标人或其招标代理机构:(盖章)
  274. entity.label = 1 if is_agency(entity.entity_text) else 0
  275. entity.values[entity.label] = 0.7 if entity.values[entity.label] > 0.7 else 0.6
  276. if in_att:
  277. entity.values[entity.label] -= 0.05
  278. continue
  279. elif re.search('(采购|招标|询比?价|遴选|寻源|比选)机构(名称)?[是为:]+$', text[max(0, b-span):b]) and entity.label == 1:
  280. agency_like_tenderee.append(entity)
  281. for pattern in self.pattern_list:
  282. if 'left' in pattern:
  283. context = text[max(0, b-span):b]
  284. elif 'right' in pattern:
  285. context = text[e:e+span]
  286. elif 'center' in pattern:
  287. context = text[max(0, b-span):e+span]
  288. else:
  289. print('规则错误', pattern)
  290. ser = re.search(pattern, context)
  291. if ser:
  292. groupdict = pattern.split('>')[0].replace('(?P<', '')
  293. _role, _direct, _prob = groupdict.split('_')
  294. _label = role2id.get(_role)
  295. if _label != entity.label:
  296. continue
  297. _prob = int(_prob)*0.1
  298. # print('规则修改角色概率前:', entity.entity_text, entity.label, entity.values)
  299. if in_att:
  300. _prob = _prob - 0.1 # 0.2
  301. if pre_prob < _prob: # 如果模型预测概率小于关键词概率
  302. _prob = 0.65
  303. if len(entity.entity_text) < 6 and re.search('大学|医院', entity.entity_text)==None: # 如果实体名称小于6个字,概率再降0.05
  304. _prob -= 0.05
  305. if re.search('(地址|联系方式):$', context): # 地址结尾的概率 概率降低
  306. _prob -= 0.05
  307. if _label == 0 and is_agency(entity.entity_text): # 20250116 修复 584333688 同时有招标单位 : 安徽省招标集团股份有限公司,.采购人信息 名 称:安徽开放大学
  308. _prob -= 0.1
  309. if re.search('成交电商:$', text[:b]): # 609280615 万银政采平台 罗山县政采平台等成交电商都不是中标人
  310. _prob = 0.55
  311. entity.values[_label] = _prob + entity.values[_label] / 20
  312. not_found = 0
  313. if is_result_change and _label == 2 and (re.search('原(公告)?(中标|中选|中价|成交|竞得)(人|单位|供应商)|原?第[一1](中标|中选|中价|成交)?候选人', context)
  314. or re.search('更正前', text[max(0, b-20):b])):
  315. entity.values[_label] = 0.5
  316. log('结果变更公告去除原中标人:%s, docid:%s'%(entity.entity_text, docid))
  317. if is_result_change and _label == 3 and re.search('原(排名)?第二', text[max(0, b-20):b]):
  318. entity.label = 2
  319. entity.values[2] = 0.6
  320. log('结果变更公告第二名变中标人:%s, docid:%s'%(entity.entity_text, docid))
  321. # print('规则修改角色概率后:', entity.entity_text, entity.label, entity.values)
  322. break
  323. if original_docchannel == 117 and re.search('卖方(名称)?:$', text[max(0, b-span):b]):
  324. entity.label = 0
  325. if not_found and entity.values[entity.label]> min_prob:
  326. _prob = min_prob - 0.1 if in_att else min_prob
  327. entity.values[entity.label] = _prob + entity.values[entity.label] / 20
  328. # print('找不到规则修改角色概率:', entity.entity_text, entity.label, entity.values)
  329. if entity.label == 2 and entity.values[entity.label]> min_prob:
  330. if entity.entity_type == 'org':
  331. org_winner.append(entity)
  332. elif entity.entity_type == 'company':
  333. company_winner.append(entity) # 保存中标人实体
  334. if entity.label == 0 and entity.values[entity.label]> min_prob:
  335. org_tenderee.append(entity.entity_text) # 保存所有招标人名称
  336. elif entity.label == 1 and entity.values[entity.label]> min_prob:
  337. agency_l.append(entity.entity_text)
  338. if entity.entity_text not in role_text_type_dic:
  339. role_text_type_dic[entity.entity_text] = set()
  340. # if entity.entity_text not in role_text_ent_dic:
  341. # role_text_ent_dic[entity.entity_text] = {
  342. # "招标概率": [],
  343. # "中标概率": [],
  344. # "实体列表": []
  345. # }
  346. if entity.label in [0, 2, 3, 4]:
  347. role_text_type_dic[entity.entity_text].add(str(entity.label))
  348. # role_text_ent_dic[entity.entity_text]["实体列表"].append(entity)
  349. # if entity.label == 0:
  350. # role_text_ent_dic[entity.entity_text]["招标概率"].append(entity.values[entity.label])
  351. # else:
  352. # role_text_ent_dic[entity.entity_text]["中标概率"].append(entity.values[entity.label])
  353. # if entity.entity_type in ['org', 'company'] and entity.label == 0 and entity.entity_text in agency_set and entity.values[entity.label]<0.6: # 修改概率小于0.6的且在大数据代理集合里面的招标人为代理人
  354. # # log('修改概率小于0.6的且在大数据代理集合里面的招标人为代理人%s:'%entity.entity_text)
  355. # entity.label = 1
  356. # entity.values[entity.label] = 0.5
  357. elif entity.entity_type in ['org', 'company'] and entity.label in [1, 0] and 0.5<=entity.values[entity.label]<0.6:
  358. if entity.label == 1:
  359. low_prob_agency.append(entity)
  360. else:
  361. low_prob_tenderee.append(entity)
  362. elif entity.entity_type in ['org', 'company'] and entity.label == 2 and 0.5<=entity.values[entity.label]<0.6:
  363. low_prob_winner.append(entity)
  364. if entity.entity_type in ['org', 'company'] and entity.label in [1, 0] and 0.6<entity.values[entity.label]: # 由0.5调为0.6,避免367217504 同时为低概率招标、中标被改
  365. all_tenderee_agency.append(entity.entity_text)
  366. if bid_info and entity.entity_type in ['org', 'company']: # 中标大纲下实体
  367. for scope in bid_info:
  368. if scope[0][0] == scope[1][0]:
  369. if entity.sentence_index == scope[0][0] and scope[0][1] < entity.wordOffset_begin and entity.wordOffset_end <= scope[1][1]:
  370. bid_info_company.append(entity)
  371. else:
  372. if entity.sentence_index == scope[0][0] and entity.wordOffset_begin > scope[0][1]:
  373. bid_info_company.append(entity)
  374. elif scope[0][0] < entity.sentence_index < scope[1][0]:
  375. bid_info_company.append(entity)
  376. elif entity.sentence_index == scope[1][0] and entity.wordOffset_end <scope[1][1]:
  377. bid_info_company.append(entity)
  378. if entity.label == 2 and entity.values[entity.label] > 0.5:
  379. have_winner = True
  380. if ree_info and entity.entity_type in ['org', 'company']: # 招标大纲下实体
  381. for scope in ree_info:
  382. if scope[0][0] == scope[1][0]:
  383. if entity.sentence_index == scope[0][0] and scope[0][1] < entity.wordOffset_begin and entity.wordOffset_end <= scope[1][1]:
  384. ree_info_company.append(entity)
  385. else:
  386. if entity.sentence_index == scope[0][0] and entity.wordOffset_begin > scope[0][1]:
  387. ree_info_company.append(entity)
  388. elif scope[0][0] < entity.sentence_index < scope[1][0]:
  389. ree_info_company.append(entity)
  390. elif entity.sentence_index == scope[1][0] and entity.wordOffset_end <scope[1][1]:
  391. ree_info_company.append(entity)
  392. if entity.label == 0 and entity.values[entity.label] > 0.5:
  393. have_ree = True
  394. if is_bid_record and entity.entity_type in ['org', 'company'] and entity.label == 2 and 0.5<=entity.values[entity.label]<0.8:
  395. entity.values[entity.label] = 0.4
  396. log('开标记录去掉不确定中标人:%s,docid:%s'%(entity.entity_text, docid))
  397. # 第二步:低概率实体修正
  398. self._correct_low_prob_entities(
  399. agency_like_tenderee, org_tenderee, low_prob_agency,
  400. low_prob_tenderee, agency_l, low_prob_winner, all_tenderee_agency
  401. )
  402. # 第三步:中标人/招标人冲突修正
  403. self._correct_winner_tenderee_conflict(org_winner, org_tenderee)
  404. # 第四步:大纲范围内实体补充
  405. self._supplement_outline_entities(
  406. bid_info_company, ree_info_company, have_winner, have_ree,
  407. span, docid, sentences
  408. )
  409. # # 第五步:角色冲突最终修正
  410. # self._resolve_role_conflict(role_text_ent_dic, role_text_type_dic, docid)
  411. # for entity in agency_like_tenderee:
  412. # if not is_agency(entity.entity_text) or entity.entity_text in org_tenderee:
  413. # entity.label = 0
  414. # entity.values[entity.label] = 0.6
  415. # else:
  416. # entity.values[entity.label] = 0.5
  417. # for entity in low_prob_agency: # 如果低概率代理在招标人列表,改为招标人
  418. # if entity.entity_text in org_tenderee:
  419. # entity.label = 0
  420. # entity.values[entity.label] = 0.6
  421. # for entity in low_prob_tenderee:
  422. # if entity.entity_text in agency_l:
  423. # entity.label = 1
  424. # entity.values[entity.label] = 0.6
  425. # for entity in low_prob_winner: # 如果低概率中标人在招标或代理列表,改为非角色
  426. # if entity.entity_text in all_tenderee_agency:
  427. # entity.label = 5
  428. # # elif entity.in_attachment: # 附件低概率中标角色不要 避免:516109391 桂林银行崇左宁明支行,宁明县城中镇兴宁大道中70号,预测为中标 20241126 注释掉,558294326 附件单个候选人漏提取
  429. # # entity.label = 5
  430. # if org_winner != []:
  431. # flag = 0
  432. # if org_tenderee != []:
  433. # for ent in org_winner:
  434. # if ent.entity_text in org_tenderee:
  435. # # log('如果org中标人同时为招标人角色,降低中标概率:%s, %s' % (ent.entity_text, ent.label))
  436. # ent.values[2] = 0.6
  437. # flag = 1
  438. # # if flag == 0 and company_winner != []: # 2024/04/18 注释掉 避免提取不到 273351465 供应商(乙方:湖南省第二测绘院
  439. # # for ent in org_winner:
  440. # # if ent.label == 2 and ent.values[2] > 0.6:
  441. # # # log('如果同时包含org和company中标人,降低org中标人概率为0.6:%s, %s' % (ent.entity_text, ent.values[2]))
  442. # # ent.values[2] = 0.6
  443. # if have_winner == False and bid_info_company: # 优化 638075055 二、推荐中标候选人信息,邯郸市婷元紧固件制造有限公司第一名,
  444. # for entity in bid_info_company:
  445. # text = sentences[entity.sentence_index].sentence_text
  446. # b = entity.wordOffset_begin
  447. # e = entity.wordOffset_end
  448. # if re.search('(单位(名称)?|投标(单位|人)(名称)?|公司名称):$', text[max(0, b-span-2):b]) and entity.label == 5:
  449. # entity.label = 2
  450. # entity.values[entity.label] = 0.55
  451. # log('大纲规则补充中标人:%s, docid:%s'%(entity.entity_text, docid))
  452. # elif entity.label == 0 and entity.values[entity.label]>0.5 and re.search('(采购|招标)(单位|人)', text[max(0, b-span-2):b])==None:
  453. # entity.values[entity.label] = 0.5
  454. # log('中标大纲下招标人概率降低:%s, docid:%s'%(entity.entity_text, docid))
  455. # # elif len(bid_info_company) == 1 and entity.label != 2:
  456. # # entity.label = 2
  457. # # entity.values[entity.label] = 0.55
  458. # # log('大纲规则补充中标人:%s, docid:%s' % (entity.entity_text, docid))
  459. # if have_ree == False and ree_info_company: # 优化 638075055 二、推荐中标候选人信息,邯郸市婷元紧固件制造有限公司第一名,
  460. # for entity in ree_info_company:
  461. # text = sentences[entity.sentence_index].sentence_text
  462. # b = entity.wordOffset_begin
  463. # e = entity.wordOffset_end
  464. # if re.search('(单位名称|([^\w]|^)名称):$', text[max(0, b-span-2):b]) and entity.label == 5:
  465. # entity.label = 0
  466. # entity.values[entity.label] = 0.55
  467. # log('大纲规则补充招标人:%s, docid:%s'%(entity.entity_text, docid))
  468. # break
  469. # role_text_type_dic = {k: ' '.join(v) for k, v in role_text_type_dic.items() if len(v)>1 and '0' in v}
  470. # if role_text_type_dic:
  471. # log("同一个公司名称包含多个角色:%s, docid:%s"%(json.dumps(role_text_type_dic, ensure_ascii=False), docid))
  472. def role_special_predictor(web_source_name, content, nlp_enterprise):
  473. if web_source_name == '中国电子科技集团有限公司电子采购平台':
  474. ser = re.search(',(\w{5,30}),发布时间:\d+', content)
  475. if ser and ser.group(1) in nlp_enterprise:
  476. return ser.group(1)
  477. elif web_source_name == '高校仪器设备竞价网':
  478. ser = re.search('--(\w{5,30}),申购单主题', content)
  479. if ser and ser.group(1) in nlp_enterprise:
  480. return ser.group(1)
  481. elif web_source_name == '台泥阳光采购平台':
  482. ser = re.search(',(\w{5,30})招标公告,', content)
  483. if ser and ser.group(1) in nlp_enterprise:
  484. return ser.group(1)