approval.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. # -*- coding: utf-8 -*-
  2. """审批信息预测器。
  3. 按 ARCHITECTURE.md Phase 5 拆分建议,从 ``interface/predictor.py`` 迁出以下类:
  4. - ``ApprovalPredictor`` — 审批公告结构化信息提取(项目名称/编号/文号/审批部门
  5. /建设单位/环评机构/建设地址/建设年限/建设规模/审批结果等)
  6. (原 predictor.py 第 9323-9833 行)
  7. 原 ``from common.Utils import *`` / ``from interface.modelFactory import *``
  8. 已替换为显式 import;``os.path.dirname(__file__)`` 路径引用替换为
  9. ``predictors._common.INTERFACE_DIR``。
  10. ``interface/predictor.py`` 仍保留原定义,老 import 不受影响。
  11. """
  12. from __future__ import absolute_import
  13. import re
  14. import pandas as pd
  15. from bs4 import BeautifulSoup
  16. from BiddingKG.dl.common.Utils import timeFormat
  17. from BiddingKG.dl.common.attr_utils import turnMoneySource
  18. from BiddingKG.dl.interface.predictor import getPredictor
  19. from BiddingKG.dl.predictors._common import get_role
  20. from BiddingKG.dl.predictors.table_prem import TableTag2List, get_header_line
  21. __all__ = ["ApprovalPredictor"]
  22. class ApprovalPredictor():
  23. def __init__(self):
  24. '''
  25. 项目(法人)单位
  26. '''
  27. self.other_part = {
  28. "project_name": "((项目|工程|采购|招标|计划|建设|规划)名称?|生产建设项目|申请项目):(?P<main>[^:。]{5,50})[,。]([\w()]{2,15}:|$)?", # 项目名称
  29. "project_code": "(立案号|项目(统一)?代码|(项目|工程|采购|招标|计划|任务|备案|索引)([编代][号码]|号)):?(?P<main>(\w{2,8})?[()〔〕【】\[\]a-zA-Z0-9-]{5,30}号?)([\w()]{2,15}:|$)?", # 项目编号
  30. "doc_num": "((环评|\w{,3})(审[批查核]|批[复准]|立项|[定知文]书|[公发批]文|用地|决定|备案|核准|许可|确认|受理|申请报告|文[件书]|意见书|办件)[文编证]?号|综合受理号|文书?号|合格书号|申报号|(办件|事项)[编代][号码]|收件号))?为?:?(?P<main>[()〔〕【】\[\]0-9]{,8}([\w()〔〕【】]{2,15})?[()〔〕【】\[\]a-zA-Z0-9-.]{3,30}号?)[,。]?([\w()]{2,15}:|$)?", # 文号
  31. "pro_type": "((申[报请]|审核备|项目|立项)(类型|种类)|项目所属行业|行业(分类|归属)|产业领域|项目行业):(?P<main>[^:。]{2,30})[,。]([\w()]{2,15}:|$)?", # 项目类型
  32. "year_limit": "((建设|工程|服务|项目)(起止|\w{,2})?(年限|期限|时长|工期)):(约|超过|大概|建设工期|共计|合计)?(?P<main>[\d一二三四五六七八九十]+个月|\d{1,3}(日?历?天|小时)|20\d{2}[年/-](\d{1,2}[月/-]?)?(\d{1,2}日?)?([至—-]+20\d{2}[年/-](\d{1,2}[月/-]?)?(\d{1,2}日?)?)?)[(,。]([\w()]{2,15}:|$)?", # 建设年限
  33. "construction_scale": "([\d一二三四五六七八九十]{1,2}、|([\d一二三四五六七八九十]{1,2}))?(工程|项目|\w{,4})?((建设内容[及和](建设)?规模|建设规模[及和](主要)?(建设)?内容|(建设|招标|采购))?内容|(建设|工程|项目)(主要)?(规模|内容|概况|面积)([及和](主要)?(规模|内容|概况|面积))?(如下|为)?)|^规模(情况)?):(?P<main>[^:。]{2,500})[,。]?([\w()]{2,30}:|$)?", # 建设规模 #56924861 主要环境影响及预防或者减轻不良环境影响的对策和措施:
  34. "approval_items": "((审[批查核]|批[复准]|申请|监管|受理)(事项|内容|名称)|事项名称|事项审批):(?P<main>[^:。]{2,150})[,。]([\w()]{2,15}:|$)?", # 审批事项
  35. "properties": "((建设|工程|项目)性质):(?P<main>[^:。]{2,50})[,。]([\w()]{2,15}:|$)?", # 建设性质
  36. "approval_result": "((审[批查核]|批[复准]|核[发准]|许可|抽查|备案)(结果|决定|结论|状态|回复|意见)|(办[理件]|,)(状态|意见|结果)|项目(当前|目前)?状态):(?P<main>[^:。]{2,20})[,。]([\w()]{2,15}:|$)?", # 审批结果
  37. "phone": "(联系)?电话:(?P<main>1[3-9][0-9][-—-―]?\d{4}[-—-―]?\d{4}|" # 联系电话
  38. '\+86.?1[3-9]\d{9}|'
  39. '0[1-9]\d{1,2}[-—-―][2-9]\d{6}\d?[-—-―]\d{1,4}|'
  40. '0[1-9]\d{1,2}[-—-―]{0,2}[2-9]\d{6}\d?(?=1[3-9]\d{9})|'
  41. '0[1-9]\d{1,2}[-—-―]{0,2}[2-9]\d{6}\d?(?=0[1-9]\d{1,2}[-—-―]?[2-9]\d{6}\d?)|'
  42. '0[1-9]\d{1,2}[-—-―]{0,2}[2-9]\d{6}\d?(?=[2-9]\d{6,7})|'
  43. '0[1-9]\d{1,2}[-—-―]{0,2}[2-9]\d{6}\d?|'
  44. '[\(|\(]0[1-9]\d{1,2}[\)|\)]-?[2-9]\d{6}\d?-?\d{,4}|'
  45. '400\d{7}转\d{1,4}|'
  46. '[2-9]\d{6,7})[,。]([\w()]{2,15}:|$)?'
  47. }
  48. self.role_type = {
  49. "declare_company": "(申[请报]|填报|呈报)(人|部门|机关|单位|企业|公司|机构|组织)", # 申报单位
  50. "construct_company": "(业主|建设|用地|委托|发包|产权|项目|法人|采购|招标|询价))?(部门|机关|单位|企业|公司|方|业主|人)|主送机关|法人单位|甲方", # 建设单位
  51. "approver": "(审[批查核议图]|许可|批[复准](用地)?|发证|管理|办理|受理|核[发准]|备案|承办))?(部门|机关|单位|企业|公司|机构)|实施主体", # 审批部门
  52. "evaluation_agency": "(环境|环保)?(影响)?(环评|评价|评估)(机构|单位|公司)" , # 环评机构
  53. "compilation_unit": "编制单位", # 编制单位 20240701加
  54. "publisher": "(发布|发文|公示|公告)(人|部门|机关|单位|企业|公司|机构|组织)" # 发布机构 20240703加
  55. }
  56. self.person_type = {
  57. "legal_person": "项目法人|法定代表人|企业法人" # 项目法人
  58. }
  59. self.date_type = {
  60. "time_declare": "(申[请报]|填报|呈报)(时间|日期)", # 申报时间
  61. "time_commencement": "(开工|动工|(项目|建设|工程|施工)开始)(时间|日期)", # 开工时间
  62. "time_completion": "(竣工|完工|验收|(项目|建设|工程|施工)(完成|结束))(备案)?(时间|日期)", # 竣工时间
  63. "time_approval": "(审[批查核查议]|许可|批[复准](用地)?|发证|管理|办理|受理|核[发准]|备案|决定)(时间|日期)", # 审批时间 20240701加
  64. "time_release": "(发布|发文|公告|生成|成文)(时间|日期)" # 发布时间
  65. }
  66. self.addr_type = {
  67. "project_addr": "((建设|工程|项目|施工|地块|用地)\w{,2}(地址|地点|位置|所在地)|[宗土]地坐落)" # 建设地址
  68. }
  69. self.money_type = {
  70. "total_tendereeMoney": "(项目|概算|投资)金额|项目投资|总投资|总预算|总概算|投资(规模|总额|估算|概算)|批复概算|投资额|项目概算", # 总投资
  71. }
  72. self.head_rule_dic = {**self.role_type, **self.person_type, **self.date_type, **self.addr_type, **self.money_type}
  73. self.head_rule_dic.update({k: v.split(':')[0] for k,v in self.other_part.items()})
  74. self.tb = TableTag2List()
  75. def recursive_text(self, tag):
  76. '''
  77. 递归获取 soup 节点文本
  78. :param tag:
  79. :return:
  80. '''
  81. texts = []
  82. for child in tag.children:
  83. if child.name:
  84. if child.name in ['p'] and len(child.find_all('br'))>2:
  85. texts.extend(self.recursive_text(child))
  86. if child.name in ["td", "th", "p", "li", "h1", "h2", "h3", "h4", "h5",
  87. "h6"] and child.get_text().strip():
  88. texts.append(re.sub('\s', '', child.get_text().strip().replace(':', ':').replace('(', '(').replace(')', ')')))
  89. else:
  90. texts.extend(self.recursive_text(child))
  91. else:
  92. if child.strip():
  93. texts.append(re.sub('\s', '', child.strip().replace(':', ':').replace('(', '(').replace(')', ')')))
  94. return texts
  95. def get_table_info(self, df, nlp_enterprise):
  96. def get_header_index(datas):
  97. '''
  98. 根据表格表头判断结果0/1 得到哪些行和列是表头
  99. :param datas: 表格内容表头判断结果数据[[1,1,1,1],[0,0,0,0]]
  100. :return: 表头所在的行和列序号
  101. '''
  102. header_row = []
  103. header_col = []
  104. df_h = pd.DataFrame(datas) # 表头判断数据 , columns=columns
  105. for i in df_h.index:
  106. line = df_h.loc[i].values
  107. if sum(line) == len(line):
  108. header_row.append((i, sum(line) / len(line)))
  109. elif sum(line) / len(line) > 0.8:
  110. header_row.append((i, sum(line) / len(line)))
  111. elif len(line) > 3 and len(re.findall('11', ''.join([str(it) for it in line]))) > len(
  112. re.findall('10', ''.join([str(it) for it in line]))):
  113. header_row.append((i, sum(line) / len(line)))
  114. for i in df_h.columns:
  115. col = df_h[i].values
  116. if sum(col) == len(col):
  117. header_col.append((i, sum(col) / len(col)))
  118. elif sum(col) / len(col) > 0.8:
  119. header_col.append((i, sum(col) / len(col)))
  120. elif len(col) > 3 and len(re.findall('11', ''.join([str(it) for it in line]))) > len(
  121. re.findall('10', ''.join([str(it) for it in line]))):
  122. header_col.append((i, sum(col) / len(col)))
  123. return header_row, header_col
  124. def get_header(l, head_rule_dic):
  125. header_dic = {}
  126. for i in range(len(l)):
  127. text = l[i].replace(' ', '') # 修复54969575 项目 名称 被空格分割
  128. num = 0
  129. tmp_dic = {}
  130. for k, v in head_rule_dic.items():
  131. if re.search(v, text):
  132. tmp_dic[k] = i
  133. num += 1
  134. for k, v in tmp_dic.items():
  135. if k not in header_dic:
  136. header_dic[k] = v
  137. return header_dic
  138. result_l = []
  139. datas = []
  140. for i in df.index:
  141. line = get_header_line(df.loc[i].values)
  142. datas.append(line)
  143. header_row, header_col = get_header_index(datas)
  144. if len(header_col) == 1 and header_col[0][0] > 1: # 列表头不可能在第1列后面开始
  145. header_col = []
  146. if len(header_row) >= 1 and len(header_col) == 0: # 有行表头无列表头
  147. i = 0
  148. while i < len(header_row):
  149. idx, ratio = header_row[i]
  150. if idx + 1 >= len(df):
  151. break
  152. header_dic = get_header(df.loc[idx].values, self.head_rule_dic)
  153. i += 1
  154. range_from = idx + 1
  155. range_to = len(df)
  156. if i < len(header_row):
  157. next_header = i
  158. for j in range(i, len(header_row)):
  159. idx2, ratio2 = header_row[j]
  160. if idx2 - idx == 1:
  161. header_dic2 = get_header(df.loc[idx2].values, self.head_rule_dic)
  162. if set(df.loc[idx].values) & set(df.loc[idx2].values) != set():
  163. header_dic.update(header_dic2)
  164. else:
  165. header_dic = header_dic2
  166. range_from = idx2 + 1
  167. range_to = len(df)
  168. next_header = j + 1
  169. idx = idx2
  170. else:
  171. range_from = idx + 1
  172. range_to = idx2
  173. next_header = j
  174. break
  175. i = next_header
  176. if len(header_dic) >= 2 and 'project_name' in header_dic:
  177. for index in range(range_from, range_to):
  178. if len(set(df.loc[index, :])) <= 2: # 修复 56873031 补全内容跟表头错误连接
  179. continue
  180. tmp_dic = {}
  181. for k, v in header_dic.items():
  182. if k.startswith('time_'):
  183. try:
  184. content = timeFormat(df.loc[index, v], default_first_day=False) if k in [
  185. 'time_completion'] else timeFormat(df.loc[index, v])
  186. except:
  187. content = ""
  188. elif k in self.role_type:
  189. content = get_role(df.loc[index, v], nlp_enterprise)
  190. elif k == 'moneysource':
  191. content = turnMoneySource(df.loc[index, v])
  192. else:
  193. content = df.loc[index, v]
  194. if content != '':
  195. tmp_dic[k] = content
  196. if len(tmp_dic) > 1 and 'project_name' in tmp_dic and tmp_dic not in result_l:
  197. result_l.append(tmp_dic)
  198. elif len(header_row) == 0 and len(header_col) >= 1:
  199. return result_l # 不提取列向表格,容易出错 例 53489774 作多标段
  200. i = 0
  201. while i < len(header_col):
  202. idx, ratio = header_col[i]
  203. if idx + 1 >= len(df.columns):
  204. break
  205. header_dic = get_header(df[idx].values, self.head_rule_dic)
  206. i += 1
  207. range_from = idx + 1
  208. range_to = len(df.columns)
  209. if i < len(header_col):
  210. next_header = i
  211. for j in range(i, len(header_col)):
  212. idx2, ratio2 = header_col[j]
  213. if idx2 - idx == 1:
  214. header_dic2 = get_header(df[idx2].values, self.head_rule_dic)
  215. if set(df[idx].values) & set(df[idx2].values) != set():
  216. header_dic.update(header_dic2)
  217. else:
  218. header_dic = header_dic2
  219. range_from = idx2 + 1
  220. range_to = len(df.columns)
  221. next_header = j + 1
  222. idx = idx2
  223. else:
  224. range_from = idx + 1
  225. range_to = idx2
  226. next_header = j
  227. break
  228. i = next_header
  229. if len(header_dic) >= 2 and 'project_name' in header_dic:
  230. for index in range(range_from, range_to):
  231. if len(set(df.loc[:, index])) <= 2:
  232. continue
  233. tmp_dic = {}
  234. for k, v in header_dic.items():
  235. if k.startswith('time_'):
  236. content = timeFormat(df.loc[v, index], default_first_day=False) if k in [
  237. 'time_completion'] else timeFormat(df.loc[v, index])
  238. elif k in self.role_type:
  239. content = get_role(df.loc[v, index], nlp_enterprise)
  240. elif k == 'moneysource':
  241. content = turnMoneySource(df.loc[v, index])
  242. else:
  243. content = df.loc[v, index]
  244. if content != '':
  245. tmp_dic[k] = content
  246. if len(tmp_dic) > 2 and 'project_name' in tmp_dic and tmp_dic not in result_l:
  247. result_l.append(tmp_dic)
  248. elif len(header_row) == 1 and len(header_col) == 1:
  249. pass
  250. return result_l
  251. def predict_table(self, html, nlp_enterprise=[]):
  252. html = re.sub("<html>|</html>|<body>|</body>", "", html)
  253. html = re.sub("##attachment##", "", html)
  254. soup = BeautifulSoup(html, 'lxml')
  255. richText = soup.find(name='div', attrs={'class': 'richTextFetch'})
  256. self.nlp_enterprise = nlp_enterprise
  257. if richText:
  258. richText = richText.extract() # 过滤掉附件
  259. tables = soup.find_all('table')
  260. if len(tables) == 0 and richText:
  261. tables = richText.find_all('table')
  262. tables.reverse()
  263. data_list = []
  264. for table in tables:
  265. trs = self.tb.table2list(table)
  266. if len(trs) > 1 and len(set(trs[0])) > 0 and len(set([len(tr) for tr in trs])) == 1: # 表格两行以上且每行列数一样才处理
  267. df = pd.DataFrame(trs)
  268. rs_l = self.get_table_info(df, nlp_enterprise)
  269. for d in rs_l: # 53338603 项目名称+建设内容才是唯一
  270. if d not in data_list:
  271. data_list.append(d)
  272. if rs_l:
  273. table.extract()
  274. return data_list
  275. def predict(self, list_sentences, list_entitys, html, nlp_enterprise=[], span=12):
  276. tabel_rs = self.predict_table(html, nlp_enterprise) # 表格多项目提取
  277. soup = BeautifulSoup(html, 'lxml')
  278. texts_list = self.recursive_text(soup)
  279. rs_dic = {k: "" for k in
  280. self.other_part.keys() | self.role_type.keys() | self.date_type.keys() | self.addr_type.keys() | self.money_type.keys() | self.person_type.keys()}
  281. rs_dic['moneysource'] = ""
  282. sentences = [it.sentence_text for it in sorted(list_sentences[0], key=lambda x: x.sentence_index)]
  283. entities = [[] for _ in range(len(sentences))]
  284. rs_l = []
  285. found_key = 0
  286. code_name_set = set() # 项目编号、名称集合
  287. org_set = set() # 保存可能为审批部门的角色
  288. not_sure_role = '' # 不确定角色, 例:单位名称:长沙驰能新能源开发有限公司眉县分公司
  289. for entity in list_entitys[0]:
  290. entities[entity.sentence_index].append(entity)
  291. for i in range(len(sentences)):
  292. multi_project = {k: "" for k in
  293. self.other_part.keys() | self.role_type.keys() | self.date_type.keys() | self.addr_type.keys() | self.money_type.keys() | self.person_type.keys()}
  294. multi_project['moneysource'] = ''
  295. text = sentences[i]
  296. for entity in entities[i]:
  297. b, e = entity.wordOffset_begin, entity.wordOffset_end
  298. if entity.entity_type in ['org', 'company']:
  299. flag = 1
  300. role_l = []
  301. for k, v in self.role_type.items():
  302. ser = re.search(v, sentences[entity.sentence_index][max(0, b - span):b])
  303. if ser:
  304. role_l.append((k, ser.end()))
  305. if role_l:
  306. role_l = sorted(role_l, key=lambda x: x[1]) # 解决 400064746000 表格某个为空导致两个表头相近提取错误 申报单位名称:备案机关:海门经济技术开发区管理委员会,备案证号:海开审备〔2024〕346号
  307. k, _ = role_l[-1]
  308. if rs_dic[k] == '':
  309. rs_dic[k] = entity.entity_text
  310. multi_project[k] = entity.entity_text
  311. found_key = 1
  312. flag = 0
  313. if not_sure_role == entity.entity_text:
  314. not_sure_role = ''
  315. elif re.search('(,|^)单位名称:', sentences[entity.sentence_index][max(0, b - span):b]):
  316. not_sure_role = entity.entity_text
  317. if flag and entity.entity_type == "org" and re.search('(局|委员会|委|厅)$', entity.entity_text):
  318. org_set.add(entity.entity_text)
  319. elif entity.entity_type in ['person']:
  320. for k, v in self.person_type.items():
  321. if re.search(v, sentences[entity.sentence_index][max(0, b - span):b]):
  322. if rs_dic[k] == '':
  323. rs_dic[k] = entity.entity_text
  324. multi_project[k] = entity.entity_text
  325. found_key = 1
  326. break
  327. elif entity.entity_type in ['time']:
  328. time_l = []
  329. for k, v in self.date_type.items():
  330. ser = re.search(v, sentences[entity.sentence_index][max(0, b - span):b])
  331. if ser:
  332. time_l.append((k, ser.end()))
  333. if time_l:
  334. time_l = sorted(time_l, key=lambda x: x[1])
  335. k, end = time_l[-1]
  336. time = timeFormat(entity.entity_text, default_first_day=False) if k in [
  337. 'time_completion'] else timeFormat(entity.entity_text)
  338. if time == "":
  339. continue
  340. if rs_dic[k] == '':
  341. rs_dic[k] = time
  342. multi_project[k] = time
  343. found_key = 1
  344. elif entity.entity_type in ['location']:
  345. for k, v in self.addr_type.items():
  346. if re.search(v, sentences[entity.sentence_index][max(0, b - span):b]):
  347. if rs_dic[k] == '':
  348. rs_dic[k] = entity.entity_text
  349. multi_project[k] = entity.entity_text
  350. found_key = 1
  351. elif entity.entity_type in ['money']:
  352. for k, v in self.money_type.items():
  353. if re.search(v, sentences[entity.sentence_index][max(0, b - span):b]):
  354. if rs_dic[k] == '':
  355. rs_dic[k] = entity.entity_text
  356. multi_project[k] = entity.entity_text
  357. found_key = 1
  358. elif entity.entity_type in ['moneysource']:
  359. rs_dic['moneysource'] = turnMoneySource(entity.entity_text)
  360. multi_project['moneysource'] = turnMoneySource(entity.entity_text)
  361. elif entity.entity_type in ['code']:
  362. k = 'project_code'
  363. v = self.other_part[k].split(':', maxsplit=1)[0]
  364. if re.search(v, sentences[entity.sentence_index][max(0, b - span):b]):
  365. if rs_dic[k] == '':
  366. rs_dic[k] = entity.entity_text
  367. multi_project[k] = entity.entity_text
  368. found_key = 1
  369. elif entity.entity_type in ['name']:
  370. k = 'project_name'
  371. v = self.other_part[k].split(':', maxsplit=1)[0]
  372. if re.search(v, sentences[entity.sentence_index][max(0, b - span):b]):
  373. if rs_dic[k] == '':
  374. rs_dic[k] = entity.entity_text
  375. multi_project[k] = entity.entity_text
  376. found_key = 1
  377. for k, v in self.other_part.items(): # 规则提取非实体类信息
  378. ser = re.search(v, text)
  379. if ser:
  380. if rs_dic[k] == '' or (k == 'project_name' and ',审批事项:' in rs_dic[k]): # 修复 54087410 项目名称包含错误
  381. rs_dic[k] = ser.group('main')
  382. multi_project[k] = ser.group('main')
  383. found_key = 1
  384. for k, v in self.date_type.items(): # 规则补充时间实体
  385. if multi_project[k] != '':
  386. continue
  387. ser = re.search(v+':?(?P<main>20\d{2}-\d{1,2}(-\d{1,2})?|20\d{2}/\d{1,2}(/\d{1,2})?|20\d{2}\.\d{1,2}(\.\d{1,2})?|20\d{2}(0[1-9]|1[0-2])(0[1-9]|[1-2][0-9]|3[0-1])?)', text)
  388. if ser:# 规则补充实体识别不到的日期时间
  389. time = timeFormat(ser.group('main'), default_first_day=False) if k in ['time_completion'] else timeFormat(ser.group('main'))
  390. if time == "":
  391. continue
  392. if rs_dic[k] == '':
  393. rs_dic[k] = time
  394. multi_project[k] = time
  395. found_key = 1
  396. for k, v in self.addr_type.items(): # 规则补充地址实体 400063690529 实体不完整 建设地点:湖北省-咸宁市-通城县 通城县大坪乡沙口村15组(通城经济开发区)
  397. ser = re.search(v + ':?(?P<main>(\w{1,13}(自治[区州县旗]|地区|[省市区县旗盟])[^\w]*)+|\w{2,15}[,。])',text)
  398. if ser:
  399. if rs_dic[k] == '' or len(rs_dic[k]) < len(ser.group('main')):
  400. rs_dic[k] = ser.group('main')
  401. if len(multi_project[k]) < len(ser.group('main')):
  402. multi_project[k] = ser.group('main')
  403. found_key = 1
  404. for k, v in self.role_type.items(): # 规则补充公司实体
  405. if multi_project[k] != '':
  406. continue
  407. ser = re.search('(%s):(?P<main>[\w()]{6,30}(局|发改|超市|棋牌室|店|(个体工商户)))[,。]'%self.role_type[k], text)
  408. if ser:
  409. if rs_dic[k] == '':
  410. rs_dic[k] = ser.group('main')
  411. multi_project[k] = ser.group('main')
  412. if (multi_project['project_code'] != "" or multi_project['project_name'] != "") and multi_project['project_code']+multi_project['project_name'] not in code_name_set:
  413. code_name_set.add(multi_project['project_code']+multi_project['project_name'])
  414. if len(set([k for k,v in multi_project.items() if v!=''])-set(['project_name', 'project_code']))<2: # 除了包其他要素少于两个的不作为多包
  415. continue
  416. district = getPredictor('district').get_area(
  417. multi_project['approver'] + multi_project['project_name'] + multi_project['project_addr'], '')
  418. if district['district']['province'] != '全国':
  419. multi_project['area'] = district['district']['area']
  420. multi_project['province'] = district['district']['province']
  421. multi_project['city'] = district['district']['city']
  422. multi_project['district'] = district['district']['district']
  423. multi_project = {k: v for k, v in multi_project.items() if v != ''}
  424. rs_l.append(multi_project)
  425. if not_sure_role != '' and rs_dic.get('construct_company', '') == '' and not_sure_role not in org_set: # 补充,单位名称:这种作为建设单位 例:400069851014
  426. rs_dic['construct_company'] = not_sure_role
  427. if len(tabel_rs) > 1:
  428. rs_dic_key = [k for k, v in rs_dic.items() if v != '']
  429. keys = set(["approver", "publisher", "time_release", "phone", "doc_num"]) & set(rs_dic_key) - set(tabel_rs[0].keys())
  430. if keys:
  431. for d in tabel_rs:
  432. for k in keys:
  433. d[k] = rs_dic[k]
  434. return tabel_rs
  435. if len(rs_l)>1 and len(set(rs_l[0].keys()))>2 and set(rs_l[0].keys())==set(rs_l[1].keys()):
  436. for k in self.role_type.keys(): # 多项目无建设单位等通过整篇提取补充
  437. if rs_dic.get(k, '') != '' and k not in rs_l[0].get(k, '') == '':
  438. for d in rs_l:
  439. if d.get(k, '') == '':
  440. d[k] = rs_dic[k]
  441. return rs_l
  442. if found_key == 1:
  443. district = getPredictor('district').get_area(
  444. rs_dic['approver'] + rs_dic['project_name'] + rs_dic['project_addr'], '')
  445. if district['district']['province'] != '全国':
  446. rs_dic['area'] = district['district']['area']
  447. rs_dic['province'] = district['district']['province']
  448. rs_dic['city'] = district['district']['city']
  449. rs_dic['district'] = district['district']['district']
  450. if len(org_set) == 1 and rs_dic['approver'] == "":
  451. rs_dic['approver'] == org_set.pop()
  452. n = 0
  453. scale_l = [] # 保存以建设规模开头的文本,如果只有一个且比原来长的替换为此文本,避免提取不完成情况
  454. for text in texts_list: # 补充纠正内容
  455. for k, v in self.other_part.items():
  456. kw = v.split(':')[0]
  457. if re.search('^(%s)$'%kw, text) and rs_dic[k]=='': # 处理非表格表头内容 排列数据 例:400064764198,web_no: XM0016-5
  458. if n >1 and n+2 < len(texts_list) and get_header_line(texts_list[n-2:n+3]) == [1,0,1,0,1]:
  459. rs_dic[k] = texts_list[n+1]
  460. elif n in [0,1] and n+2 < len(texts_list) and get_header_line(texts_list[n:n+3]) == [1,0,1]:
  461. rs_dic[k] = texts_list[n + 1]
  462. elif n >1 and n+2 == len(texts_list) and get_header_line(texts_list[n-2:n+2]) == [1,0,1,0]:
  463. rs_dic[k] = texts_list[n + 1]
  464. elif k == 'construction_scale' and re.search('^(?[一二三四五六七八九十][)、]', text) and n+1 < len(texts_list): # 大纲 例:53375037
  465. rs_dic[k] = texts_list[n + 1]
  466. if k == 'construction_scale' and len(rs_dic.get(k, '')) < len(text):
  467. ser = re.search('^(%s):(?P<main>.+)'%kw, text)
  468. if ser:
  469. rs_dic[k] = ser.group('main')
  470. n += 1
  471. if 0<len(rs_dic['construction_scale'])<len(text) and rs_dic['construction_scale'][-1] not in [',', '。'] and text.find(rs_dic['construction_scale'])==0:
  472. scale_l.append(text)
  473. if len(scale_l)==1 and len(scale_l[0])>len(rs_dic['construction_scale']): # 规则补充不完整规模信息 例:53334434
  474. rs_dic['construction_scale'] = scale_l[0]
  475. if 0<len(rs_dic['construction_scale'])<8 and re.search('([编代][号码]|名称|时间|日期|金额|单位|机构)$', rs_dic['construction_scale']):
  476. rs_dic['construction_scale'] = ''
  477. for k, v in rs_dic.items(): # 限制最大长度
  478. if len(v)>500:
  479. v = v[:500]+'...后面省略%d字'%(len(v)-500)
  480. rs_dic[k] = v
  481. if v == 'null':
  482. rs_dic[k] = ''
  483. rs_dic = {k: v for k, v in rs_dic.items() if v != ''}
  484. return [rs_dic]
  485. return []
  486. def add_ree2approval(self, approval, prem):
  487. '''
  488. 把招标人补充到审批项目建设单位
  489. :param approval:
  490. :param prem:
  491. :return:
  492. '''
  493. ree = ''
  494. if "Project" in prem:
  495. for d in prem["Project"]['roleList']:
  496. if d["role_name"] == "tenderee":
  497. ree = d["role_text"]
  498. break
  499. if ree != '':
  500. for d in approval:
  501. if d.get('construct_company', '') == '':
  502. d['construct_company'] = ree
  503. else:
  504. break
  505. return approval
  506. def add_codename2approval(self, approval, codeName):
  507. if len(approval) == 1 and codeName: # 根据整个公告项目编号及名称补充审批信息
  508. if 'project_code' not in approval[0] and codeName[0].get('code', []) != []:
  509. approval[0]['project_code'] = codeName[0].get('code', [])[0]
  510. if 'project_name' not in approval[0] and codeName[0].get('name', '') != '':
  511. approval[0]['project_name'] = codeName[0].get('name', '')
  512. return approval