template_extractor.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. @author: bidikeji
  5. @time: 2025/11/21 16:11
  6. """
  7. from BiddingKG.dl.template_extract.template_manage import TemplateManager
  8. # from BiddingKG.dl.template_extract.out_line_extractor import OutlineExtractor
  9. from BiddingKG.dl.template_extract.table_extractor import get_html_table, get_html_table_and_prev_text
  10. from BiddingKG.dl.common.Utils import find_package,uniform_package_name,money_process,log
  11. from BiddingKG.dl.ratio.re_ratio import extract_ratio
  12. from BiddingKG.dl.interface.predictor import get_td_companys
  13. from BiddingKG.dl.common.attr_utils import extract_serviceTime
  14. import pandas as pd
  15. import traceback
  16. import json
  17. import re
  18. import os
  19. log('准备注册模板提取')
  20. manager = TemplateManager()
  21. # 2. 【关键】自动扫描根目录,按“子文件夹名称”绑定站源
  22. # 目录结构示例:
  23. # ./templates_root/
  24. # ├─ nanan/ (站源1:nanan)
  25. # │ ├─ bidding_place_tpl.py (模板:tpl_bidding_place_001)
  26. # │ └─ bidding_time_tpl.py (模板:tpl_bidding_time_001)
  27. # └─ xxs/ (站源2:xxs)
  28. # └─ bidding_place_tpl.py (模板:tpl_bidding_place_002)
  29. ROOT_TEMPLATE_DIR = os.path.dirname(__file__)+"/templates" # 根目录(包含多个站源子文件夹)
  30. try:
  31. manager.auto_register_templates_from_dir(
  32. root_target_dir=ROOT_TEMPLATE_DIR,
  33. exclude_files=["test_tpl.py"], # 排除测试文件
  34. exclude_dirs=["temp_dir"] # 排除临时文件夹
  35. )
  36. log('模板注册成功')
  37. except Exception as e:
  38. traceback.print_exc()
  39. print(f"自动注册失败:{str(e)}")
  40. exit(1)
  41. def template_predict_bk(html, web_source_no, enterprise = [], page_time=''):
  42. # outline = extractor.extract(html)
  43. tables = get_html_table(html)
  44. pre_data = {
  45. "html": html,
  46. # "outline": outline,
  47. "表格列表": tables
  48. }
  49. if '-' in web_source_no:
  50. web_source_no = web_source_no.split('-')[0]
  51. result = manager.call_templates(pre_data, {}, web_source_no)
  52. if result:
  53. result = result_post_precess(result, enterprise, page_time)
  54. return result
  55. def get_phone(text):
  56. phone_pattern = '(1[3-9][0-9][-—-―]?\d{4}[-—-―]?\d{4}|' \
  57. '\+86.?1[3-9]\d{9}|' \
  58. '0[1-9]\d{1,2}[-—-―][2-9]\d{6,7}/[1-9]\d{6,10}|' \
  59. '0[1-9]\d{1,2}[-—-―][2-9]\d{6}\d?.?转\d{1,4}|' \
  60. '0[1-9]\d{1,2}[-—-―][2-9]\d{6}\d?[-—-―]\d{1,4}|' \
  61. '0[1-9]\d{1,2}[-—-―]?[2-9]\d{6}\d?(?=1[3-9]\d{9})|' \
  62. '0[1-9]\d{1,2}[-—-―]?[2-9]\d{6}\d?(?=0[1-9]\d{1,2}[-—-―]?[2-9]\d{6}\d?)|' \
  63. '0[1-9]\d{1,2}[-—-―]?[2-9]\d{6}\d?(?=[2-9]\d{6,7})|' \
  64. '0[1-9]\d{1,2}[-—-―]?[2-9]\d{6}\d?|' \
  65. '[\(|\(]0[1-9]\d{1,2}[\)|\)]-?[2-9]\d{6}\d?-?\d{,4}|' \
  66. '[2-9]\d{6,7})'
  67. phone = ''
  68. ser = re.search('^%s$'%phone_pattern, text.strip())
  69. if ser:
  70. phone = ser.group(0)
  71. return phone
  72. def is_fail(text):
  73. if re.search('(采购|招标)失败|人数不足|无效|中止|终止|废标|流标|失败|作废|异常|撤销|另行公告|另行公示', text):
  74. return True
  75. return False
  76. def data_clean(text):
  77. text = re.sub('(无|见附件|/|-)+', '', text)
  78. return text
  79. def get_pk_no(text):
  80. pk_l = []
  81. for pk in find_package(text):
  82. package = uniform_package_name(pk.group(0))
  83. pk_l.append(package)
  84. return '-'.join(pk_l)
  85. def get_rank(text):
  86. if re.match('第?[1一](名|(中标|成交|中选)?(候选人|供应商))?$|首选供应商$', text.strip()):
  87. return 'win_tenderer'
  88. elif re.match('第?[2二](名|(中标|成交|中选)?(候选人|供应商))?$', text.strip()):
  89. return 'second_tenderer'
  90. if re.match('第?[3三](名|(中标|成交|中选)?(候选人|供应商))?$', text.strip()):
  91. return 'third_tenderer'
  92. return ''
  93. def get_pk_name(d):
  94. subject = d.get('标的', '')
  95. package = d.get('标包', '')
  96. pk1 = get_pk_no(subject)
  97. pk2 = get_pk_no(package)
  98. raw_pk = ''
  99. pk = ''
  100. if pk1 != '' and pk2 != '':
  101. pk = "%s_%s" % (pk1, pk2)
  102. elif pk1 != '' and package != '':
  103. pk = "%s_%s" % (pk1, package)
  104. elif subject != '' and pk2 != '':
  105. pk = "%s_%s" % (subject, pk2)
  106. elif pk1 or pk2:
  107. pk = pk1 if pk1 else pk2
  108. elif subject != '' and package != '':
  109. raw_pk = "%s_%s" % (subject, package) if subject != package else subject
  110. else:
  111. raw_pk = subject if subject != '' else package
  112. name = subject if subject != '' else package
  113. return pk, raw_pk, name
  114. def fix_quantity(quantity_text, header_quan_unit=''):
  115. '''
  116. 产品数量标准化,统一为数值型字符串
  117. :param quantity_text: 原始数量字符串
  118. :param header_quan_unit: 表头数量单位字符串
  119. :return: 返回数量及单位
  120. '''
  121. quantity = quantity_text
  122. quantity = re.sub('[一壹]', '1', quantity)
  123. quantity = re.sub('[,,约]|(\d+)', '', quantity)
  124. ser = re.search('^(\d+\.?\d*)(?([㎡\w/]{,5})', quantity)
  125. if ser:
  126. quantity = str(ser.group(1))
  127. quantity_unit = ser.group(2)
  128. if quantity_unit == "" and header_quan_unit != "":
  129. quantity_unit = header_quan_unit
  130. else:
  131. quantity = ""
  132. quantity_unit = ""
  133. return quantity, quantity_unit
  134. def get_win_joint(role_text, enterprise):
  135. ents = []
  136. joint = ''
  137. leader = ''
  138. if re.sub('(个体工商户)', '', role_text) in enterprise or re.match('\w{6,25}(个体工商户)$', role_text):
  139. return role_text, ''
  140. if re.search('[,,/;;、]', role_text):
  141. role_text = re.sub('\w{2,8}:|((主|成|\w{,3}联合体|(牵头|成员)(方|人|单位))\w{,5})', '', role_text)
  142. for ent in re.split('[,,/;;、]+', role_text):
  143. if ent in enterprise:
  144. ents.append(ent)
  145. if len(ents) == 1:
  146. leader = ents[0]
  147. elif len(ents) > 1:
  148. leader = ents[0]
  149. joint = ','.join(ents)
  150. if leader == '' and len(enterprise)>0:
  151. match = re.search('|'.join(enterprise), role_text)
  152. if match:
  153. leader = match.group(0)
  154. return leader, joint
  155. def get_ratio(text):
  156. '''
  157. 处理费率等
  158. :param text: 必须包含下浮率,费率等中文
  159. :return:
  160. '''
  161. rs = extract_ratio(text)
  162. if len(rs)>0 and rs[0].get('value', '') != '':
  163. return str(rs[0].get('value', ''))
  164. return ''
  165. def template_predict(html, web_source_no, enterprise = [], page_time='', docid=''):
  166. # outline = extractor.extract(html)
  167. # tables = get_html_table(html)
  168. tables, tables_prev_text = get_html_table_and_prev_text(html)
  169. result_dic = {}
  170. for i in range(len(tables)):
  171. if len(tables[i]) < 2: # 少于两行的表格不要
  172. continue
  173. pre_data = {
  174. "html": html,
  175. # "outline": outline,
  176. "表格列表": [tables[i]],
  177. "表格前文": [tables_prev_text[i]]
  178. }
  179. if '-' in web_source_no:
  180. web_source_no = web_source_no.split('-')[0]
  181. result = manager.call_templates(pre_data, {}, web_source_no, docid)
  182. # print('表格提取结果:', result)
  183. if result:
  184. prev_text = tables_prev_text[i]
  185. pk_out = get_pk_no(prev_text)
  186. for k, v in result.items():
  187. if v:
  188. if k not in result_dic:
  189. result_dic[k] = []
  190. if k in ['中标信息', '招标信息', '候选人信息']:
  191. if pk_out:
  192. for d in v:
  193. d['pk_out'] = pk_out
  194. if v not in result_dic[k]: # 表格结果去重
  195. result_dic[k].append(v)
  196. for k in ['中标信息', '招标信息', '候选人信息', '产品信息']:
  197. if k in result_dic:
  198. result_dic[k] = [it for l in result_dic[k] for it in l]
  199. if result_dic:
  200. result_dic = result_post_precess(result_dic, enterprise, page_time)
  201. return result_dic
  202. def result_post_precess(dic, enterprise = [], page_time=''):
  203. enterprise = sorted(enterprise, key=lambda x: len(x), reverse=True)
  204. rs_dic = {}
  205. if '项目编号' in dic:
  206. rs_dic['code'] = dic['项目编号']
  207. if '项目名称' in dic:
  208. rs_dic['name'] = dic['项目名称'][0]
  209. if dic.get('招标信息', []) != [] or dic.get('中标信息', []) != [] or dic.get('候选人信息', []) != []:
  210. rs_dic['prem'] = {}
  211. role_dic = {}
  212. for d in dic.get('招标信息', []):
  213. pk, raw_pk, name = get_pk_name(d)
  214. pk_out = d.get('pk_out', '')
  215. if pk == "" and (raw_pk == '' or len(dic.get('招标信息', [])) == 1):
  216. pk = pk_out if pk_out else 'Project'
  217. elif raw_pk != '':
  218. pk = pk_out if pk_out else raw_pk
  219. name = d.get('标的', '') if d.get('标的', '') != '' else d.get('标包', '')
  220. tenderee = d.get('招标人', '')
  221. budget = d.get('限价', '') if d.get('限价', '') != '' else d.get('预算', '')
  222. person = d.get('联系人', '')
  223. phone = d.get('电话', '')
  224. address = d.get('地址', '')
  225. if tenderee == '' and budget == '':
  226. continue
  227. if is_fail(tenderee) or is_fail(budget) or tenderee not in enterprise:
  228. continue
  229. budget, unit = money_process(budget, '金额')
  230. phone = get_phone(phone)
  231. if pk not in role_dic:
  232. role_dic[pk] = {}
  233. if name != '':
  234. role_dic[pk]['name'] = name
  235. if 'tenderee' not in role_dic[pk]:
  236. role_dic[pk]['tenderee'] = {
  237. 'role_text': tenderee,
  238. 'price': budget,
  239. 'money_unit': unit,
  240. 'person': person,
  241. 'phone': phone,
  242. 'address': address
  243. }
  244. else:
  245. role_dic[pk]['tenderee']['price'] += budget
  246. for d in dic.get('中标信息', []):
  247. pk, raw_pk, name = get_pk_name(d)
  248. pk_out = d.get('pk_out', '')
  249. if pk == "" and (raw_pk == '' or len(dic.get('中标信息', [])) == 1):
  250. pk = pk_out if pk_out else 'Project'
  251. elif raw_pk != '':
  252. pk = pk_out if pk_out else raw_pk
  253. name = d.get('标的', '') if d.get('标的', '') != '' else d.get('标包', '')
  254. win_tenderer = d.get('中标人', '')
  255. price = d.get('中标价', '')
  256. downward_floating_ratio = d.get('下浮率', '')
  257. discount_ratio = d.get('费率', '')
  258. person = d.get('联系人', '')
  259. phone = d.get('电话', '')
  260. address = d.get('地址', '')
  261. serviceTime = d.get('服务时间', '')
  262. joint = ''
  263. if win_tenderer == '':
  264. continue
  265. if is_fail(win_tenderer) or is_fail(price):
  266. continue
  267. if win_tenderer not in enterprise:
  268. # leader, joint = get_win_joint(win_tenderer, enterprise)
  269. leader, joint = get_td_companys(win_tenderer, enterprise)
  270. if leader != '':
  271. win_tenderer = leader
  272. else:
  273. log('模板提取实体不在列表公司:%s' % win_tenderer)
  274. continue
  275. price, unit = money_process(price, '金额')
  276. phone = get_phone(phone)
  277. if downward_floating_ratio != '':
  278. downward_floating_ratio = get_ratio('下浮率:%s'%downward_floating_ratio)
  279. if discount_ratio != '':
  280. discount_ratio = get_ratio('费率:%s'%discount_ratio)
  281. if serviceTime != '':
  282. serviceTime = extract_serviceTime(serviceTime, page_time)
  283. if pk not in role_dic:
  284. role_dic[pk] = {}
  285. if name != '':
  286. role_dic[pk]['name'] = name
  287. if 'win_tenderer' not in role_dic[pk]:
  288. role_dic[pk]['win_tenderer'] = {
  289. 'role_text': win_tenderer,
  290. 'price': price,
  291. 'money_unit': unit,
  292. 'person': person,
  293. 'phone': phone,
  294. 'address': address
  295. }
  296. if joint != '':
  297. role_dic[pk]['win_tenderer']['win_tenderer_joint'] = joint
  298. if downward_floating_ratio != '':
  299. role_dic[pk]['win_tenderer']['downward_floating_ratio'] = downward_floating_ratio
  300. if discount_ratio != '':
  301. role_dic[pk]['win_tenderer']['discount_ratio'] = discount_ratio
  302. if serviceTime != '':
  303. role_dic[pk]['win_tenderer']['serviceTime'] = serviceTime
  304. elif win_tenderer != role_dic[pk]['win_tenderer']['role_text']:
  305. if 'other_winner_dic' not in role_dic[pk]['win_tenderer']:
  306. role_dic[pk]['win_tenderer']['other_winner_dic'] = {
  307. win_tenderer: price
  308. }
  309. elif win_tenderer not in role_dic[pk]['win_tenderer']['other_winner_dic']:
  310. role_dic[pk]['win_tenderer']['other_winner_dic'][win_tenderer] = price
  311. else:
  312. role_dic[pk]['win_tenderer']['other_winner_dic'][win_tenderer] += price
  313. else:
  314. role_dic[pk]['win_tenderer']['price'] += price
  315. for d in dic.get('候选人信息', []):
  316. pk, raw_pk, name = get_pk_name(d)
  317. pk_out = d.get('pk_out', '')
  318. if pk == "" and (raw_pk == '' or len(dic.get('候选人信息', [])) == 1):
  319. pk = pk_out if pk_out else 'Project'
  320. elif raw_pk != '':
  321. pk = pk_out if pk_out else raw_pk
  322. name = d.get('标的', '') if d.get('标的', '') != '' else d.get('标包', '')
  323. candidate = d.get('候选人', '')
  324. rank = d.get('排名', '')
  325. price = d.get('中标价', '')
  326. downward_floating_ratio = d.get('下浮率', '')
  327. discount_ratio = d.get('费率', '')
  328. person = d.get('联系人', '')
  329. phone = d.get('电话', '')
  330. address = d.get('地址', '')
  331. serviceTime = d.get('服务时间', '')
  332. joint = ''
  333. if rank == '' or candidate == '':
  334. continue
  335. if is_fail(candidate) or is_fail(rank):
  336. continue
  337. if candidate not in enterprise:
  338. # leader, joint = get_win_joint(candidate, enterprise)
  339. leader, joint = get_td_companys(candidate, enterprise)
  340. if leader != '':
  341. candidate = leader
  342. else:
  343. log('模板提取实体不在列表公司:%s'%candidate)
  344. continue
  345. role_name = get_rank(rank)
  346. if role_name == '':
  347. continue
  348. price, unit = money_process(price, '金额')
  349. phone = get_phone(phone)
  350. if downward_floating_ratio != '':
  351. downward_floating_ratio = get_ratio('下浮率:%s'%downward_floating_ratio)
  352. if discount_ratio != '':
  353. discount_ratio = get_ratio('费率:%s'%discount_ratio)
  354. if serviceTime != '':
  355. serviceTime = extract_serviceTime(serviceTime, page_time)
  356. if pk not in role_dic:
  357. role_dic[pk] = {}
  358. if name != '':
  359. role_dic[pk]['name'] = name
  360. if role_name not in role_dic[pk]:
  361. role_dic[pk][role_name] = {
  362. 'role_text': candidate,
  363. 'price': price,
  364. 'money_unit': unit,
  365. 'person': person,
  366. 'phone': phone,
  367. 'address': address
  368. }
  369. if joint != '':
  370. role_dic[pk][role_name]['win_tenderer_joint'] = joint
  371. if downward_floating_ratio != '':
  372. role_dic[pk][role_name]['downward_floating_ratio'] = downward_floating_ratio
  373. if discount_ratio != '':
  374. role_dic[pk][role_name]['discount_ratio'] = discount_ratio
  375. if serviceTime != '':
  376. role_dic[pk]['win_tenderer']['serviceTime'] = serviceTime
  377. elif candidate == role_dic[pk][role_name]['role_text']:
  378. role_dic[pk][role_name]['price'] += price
  379. else:
  380. print('候选人异常:同标段有其他中标人')
  381. for pk, role in role_dic.items():
  382. rs_dic['prem'][pk] = {
  383. 'roleList': [],
  384. 'tendereeMoney': 0,
  385. 'tendereeMoneyUnit': ""
  386. }
  387. if role.get('name', '') != '':
  388. rs_dic['prem'][pk]['name'] = role.get('name', '')
  389. for k, v in role.items():
  390. if k not in ['tenderee', 'win_tenderer', 'second_tenderer', 'third_tenderer']:
  391. continue
  392. if v['role_text'] != '':
  393. d = {
  394. 'role_name': k,
  395. 'role_text': v['role_text'],
  396. }
  397. if v['address'] != '':
  398. d['address'] = v['address']
  399. if v['person'] != '' or v['phone'] != '':
  400. d['linklist'] = [[v['person'], v['phone']]]
  401. if k != 'tenderee':
  402. d['role_money'] = {'money': v['price'], 'money_unit': v['money_unit']}
  403. if 'downward_floating_ratio' in v:
  404. d['role_money']['downward_floating_ratio'] = v['downward_floating_ratio']
  405. if 'discount_ratio' in v:
  406. d['role_money']['discount_ratio'] = v['discount_ratio']
  407. if k == 'win_tenderer':
  408. if 'other_winner_dic' in v:
  409. d['other_winner_dic'] = []
  410. for text, money in v['other_winner_dic'].items():
  411. d['other_winner_dic'].append(
  412. {
  413. 'role_text': text,
  414. 'money': money
  415. }
  416. )
  417. d['multi_winner'] = v['role_text'] + ',' + ','.join(v['other_winner_dic'])
  418. if 'win_tenderer_joint' in v:
  419. d['win_tenderer_joint'] = v['win_tenderer_joint']
  420. if 'serviceTime' in v:
  421. d['serviceTime'] = v['serviceTime']
  422. if k != 'tenderee': # 20260717 模板提取暂时不要招标人
  423. rs_dic['prem'][pk]['roleList'].append(d)
  424. if k == 'tenderee':
  425. rs_dic['prem'][pk]['tendereeMoney'] = v['price']
  426. rs_dic['prem'][pk]['tendereeMoneyUnit'] = v['money_unit']
  427. product_l = []
  428. key_dic = {
  429. '产品': 'product',
  430. '品目': 'pinmu_name',
  431. '品牌': 'brand',
  432. '规格': 'specs',
  433. '数量': 'quantity',
  434. '单位': 'quantity_unit',
  435. '单价': 'unitPrice',
  436. '总价': 'total_price'
  437. }
  438. for d in dic.get('产品信息', []):
  439. if '单价' in d:
  440. d['单价'], unit = money_process(d['单价'], '金额')
  441. if '总价' in d:
  442. d['总价'], unit = money_process(d['总价'], '金额')
  443. if '数量' in d:
  444. d['数量'], quantity_unit = fix_quantity(d['数量'])
  445. product_dic = {key_dic[k]: v for k, v in d.items() if v!=''}
  446. if len(product_dic) < 3: # 少于三个要素的产品不放进产品属性
  447. continue
  448. if product_dic not in product_l:
  449. product_l.append(product_dic)
  450. if len(product_l) > 0:
  451. rs_dic['product_attrs'] = {'data': product_l}
  452. return rs_dic # role_dic
  453. def get_role(prem):
  454. winners = []
  455. for k, v in prem.items():
  456. if 'roleList' in v:
  457. for d in v['roleList']:
  458. if d['role_name'] == 'win_tenderer':
  459. winners.append(d['role_text'])
  460. if 'win_tenderer_joint' in d:
  461. for winner in d['win_tenderer_joint'].split(','):
  462. winners.append(winner)
  463. if 'multi_winner' in d:
  464. for winner in d['multi_winner'].split(','):
  465. winners.append(winner)
  466. return set(winners)
  467. def update_template_result(product_attrs, codeName, prem, result_template, docid=''):
  468. if result_template.get('name', '') != '':
  469. # print('模板修改项目名称信息')
  470. codeName[0]['name'] = result_template['name']
  471. if result_template.get('code', '') != '':
  472. # print('模板修改项目编号信息')
  473. codeName[0]['code'] = result_template['code']
  474. if result_template.get('product_attrs', '') != '':
  475. # print('模板修改产品信息')
  476. product_attrs[0]['product_attrs'] = result_template['product_attrs']
  477. if result_template:
  478. log('模板提取要素:%s,docid:%s'%(json.dumps(list(result_template.keys()), ensure_ascii=False), docid))
  479. if result_template.get('prem', '') != '' and len(result_template['prem']) > 0:# and 'win_tenderer' in str(result_template['prem']):
  480. old_winners = get_role(prem[0]['prem'])
  481. new_winners = get_role(result_template['prem'])
  482. if old_winners != new_winners:
  483. log('模板提取中标人与原来提取不一致,新减旧:%s, 旧减新:%s, docid:%d'%(', '.join(new_winners-old_winners), ', '.join(old_winners-new_winners), docid))
  484. if len(old_winners) > len(new_winners):
  485. log("模板提取中标人比原来少,原来:%d, 模板:%d, docid:%d"%(len(old_winners), len(new_winners), docid))
  486. original_pk_num = len(prem[0]['prem'])
  487. # 模板提取有角色,原来的prem只保留默认包
  488. prem[0]['prem'] = {k:v for k,v in prem[0]['prem'].items() if k == 'Project'}
  489. # 去除非招标、代理角色
  490. for k, v in prem[0]['prem'].items():
  491. if 'roleList' in v:
  492. v['roleList'] = [d for d in v['roleList'] if d.get('role_name', '') in ['tenderee', 'agency']]
  493. for package, dic in result_template['prem'].items():
  494. if package == 'Project' and package in prem[0]['prem']:
  495. for k in ['code', 'name', 'tendereeMoney']:
  496. if k in dic and dic[k] not in [0, '']:
  497. prem[0]['prem']['Project'][k] = dic[k]
  498. if 'roleList' in dic:
  499. for d in dic['roleList']:
  500. if d['role_name'] not in ['tenderee', 'agency']:
  501. prem[0]['prem'][package]['roleList'].append(d)
  502. else:
  503. prem[0]['prem'][package] = dic
  504. new_pk_num = len(prem[0]['prem'])
  505. if abs(original_pk_num - new_pk_num) > 1:
  506. log('模板提取结果替换原来prem,原来包数量:%d, 模板提取数量:%d, 合并后包数量:%d,docid:%s'%(
  507. original_pk_num, len(result_template.get('prem', {})), new_pk_num,docid))
  508. return product_attrs, codeName, prem
  509. if __name__ == "__main__":
  510. with open('d:/html/要素提取输入要素.json', 'r', encoding='utf-8') as f:
  511. docid, title, page_time, web_source_no, web_source_name, original_docchannel, page_attachments = json.load(f)
  512. with open('d:/html/2.html', encoding='utf-8') as f:
  513. html = f.read()
  514. web_source_no = 'DX016887'
  515. rs = template_predict(html, web_source_no=web_source_no.split('_')[0])
  516. print(type(rs), rs)
  517. d = {'招标信息': [], '中标信息': [], '候选人信息': [{'标的': '广东电网能源投资有限公司“十五五”发展规划咨询服务项目', '标包': '广东电网能源投资有限公司“十五五”发展规划咨询服务项目', '候选人': '北京力久科技有限公司', '投标价': '70万元', '排名': '1'}, {'标的': '广东电网能源投资有限公司“十五五”发展规划咨询服务项目', '标包': '广东电网能源投资有限公司“十五五”发展规划咨询服务项目', '候选人': '广州逸仙咨询顾问有限公司', '投标价': '66.6万元', '排名': '2'}, {'标的': '广东电网能源投资有限公司人力资源规划及市场化薪酬用工机制改革咨询服务项目', '标包': '广东电网能源投资有限公司人力资源规划及市场化薪酬用工机制改革咨询服务项目', '候选人': '广东中大管理咨询集团股份有限公司', '投标价': '47.3万元', '排名': '1'}, {'标的': '广东电网能源投资有限公司人力资源规划及市场化薪酬用工机制改革咨询服务项目', '标包': '广东电网能源投资有限公司人力资源规划及市场化薪酬用工机制改革咨询服务项目', '候选人': '广州逸仙咨询顾问有限公司', '投标价': '47.5万元', '排名': '2'}]}
  518. # d = {'招标信息': [],
  519. # '中标信息': [{'标的': '标的1:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年党员干部党性教育培训体系研究与建设项目', '中标人': '广东中大管理咨询集团股份有限公司'},
  520. # {'标的': '标的1:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司国际化人才培训评价产品开发及运营机制研究', '中标人': '南方电网澜湄能源咨询(广东)有限责任公司'},
  521. # {'标的': '标的1:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年新形势下的干部管理教育体系探索与研究课题', '中标人': '深圳市仁脉教育发展有限公司'},
  522. # {'标的': '标的1:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年基于领导力培养的行动学习课题研究项目', '中标人': '广州睿思管理咨询有限公司'},
  523. # {'标的': '标的1:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年基于“工学一体化”的现代化产业工人队伍建设的培训体系研究',
  524. # '中标人': '深圳市远界管理咨询有限公司'},
  525. # {'标的': '标的1:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年基于世界一流企业定位的干部“选育管用”全链条体系优化研究',
  526. # '中标人': '广州逸仙咨询顾问有限公司'},
  527. # {'标的': '标的2:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年党建及团员青年教育教研体系建设课题研究项目', '中标人': '广东中大管理咨询集团股份有限公司'},
  528. # {'标的': '标的2:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年培训评价实施机构安全风险管理体系研究与实践', '中标人': '广东邮电职业技术学院'},
  529. # {'标的': '标的2:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年电力生产领域专业技术人员培养研究', '中标人': '广州睿思管理咨询有限公司'},
  530. # {'标的': '标的2:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年承接南方电网公司专业技术人员培训内容规范编制', '中标人': '深圳市远界管理咨询有限公司'},
  531. # {'标的': '标的2:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年基于人工智能驱动的技能类培训与评价技术研究与应用项目', '中标人': '广州逸仙咨询顾问有限公司'},
  532. # {'标的': '标的2:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司基于技能岗评大数据建立安全事故“预警机制”的课题研究', '中标人': '广东华工精卓企业管理咨询有限公司'},
  533. # {'标的': '标的2:广东电网有限责任公司2025年培评课题研究', '标包': '广东电网公司2025年培训评价计划编制项目', '中标人': '深圳市仁脉教育发展有限公司'}], '候选人信息': []}
  534. # d = {'招标信息': [{'标的': '深圳电网智慧能源技术有限公司2025年承装(修、试)三级电力设施许可资质办理咨询服务项目', '预算': '30000.00元', '限价': '30000.00元'}], '中标信息': [], '候选人信息': []}
  535. # d = {'项目编号': 'OUC-HW-ZG2506914', '项目名称': '绘图测量仪器', '招标信息': [{'招标人': '工程学院', '地址': '中国海洋大学西海岸校区', '联系人': '宁**', '电话': '156****2849'}], '中标信息': [{'中标人': '温岭市城西三忠工具店', '中标价': '260元'},{'中标人': '温岭市城西三忠工具店', '中标价': '260元'},{'中标人': '温岭市文具店', '中标价': '1260元'}], '产品信息': [{'产品': '磁性表座', '品牌': '', '规格': '', '数量': '2台'}]}
  536. d = {'候选人信息': [{'标的': '肇庆110千伏武垄输变电工程等2项工程施工', '标包': '肇庆110千伏武垄输变电工程施工', '排名': '1', '候选人': '广州电力建设有限公司e/深圳市远界管理咨询有限公司s', '投标价': '/'}, {'标的': '肇庆110千伏武垄输变电工程等2项工程施工', '标包': '肇庆110千伏长溪(贝水2)输变电工程施工', '排名': '1', '候选人': '肇庆市恒电电力工程有限公司', '投标价': '/'}, {'标的': '中山220千伏德隆站扩建第三台主变工程等3项工程施工', '标包': '中山220千伏德隆站扩建第三台主变工程施工', '排名': '1', '候选人': '东莞市输变电工程建设有限责任公司', '投标价': '535.95万元'}, {'标的': '中山220千伏德隆站扩建第三台主变工程等3项工程施工', '标包': '中山110千伏广源输变电工程施工', '排名': '1', '候选人': '中山市城区电气工程有限公司', '投标价': '/'}, {'标的': '中山220千伏德隆站扩建第三台主变工程等3项工程施工', '标包': '中山110千伏外沙输变电工程施工', '排名': '1', '候选人': '中山市农村电力工程有限公司', '投标价': '/'}, {'标的': '惠州惠阳110千伏迎宾(尧岗)输变电工程施工', '标包': '惠州惠阳110千伏迎宾(尧岗)输变电工程施工', '排名': '1', '候选人': '惠州市惠城电力实业有限公司', '投标价': '3249.44万元'}, {'标的': '珠海110千伏梅华输变电工程施工', '标包': '珠海110千伏梅华输变电工程施工', '排名': '1', '候选人': '珠海电力建设工程有限公司', '投标价': '3181.14万元'}, {'标的': '佛山110千伏顺利输变电工程等2项工程施工', '标包': '佛山110千伏顺利输变电工程施工', '排名': '1', '候选人': '联合体:广东威恒输变电工程有限公司(牵头人),中国建筑第四工程局有限公司(成员)', '投标价': '2912.27万元'}, {'标的': '佛山110千伏顺利输变电工程等2项工程施工', '标包': '佛山110千伏龙眼输变电工程施工', '排名': '1', '候选人': '广东汇盈电力工程有限公司', '投标价': '2533.33万元'}, {'标的': '梅州城区220千伏赞化站扩建第三台主变工程等6项工程施工', '标包': '梅州城区220千伏赞化站扩建第三台主变工程施工', '排名': '1', '候选人': '广东运峰电力安装有限公司', '投标价': '360.84万元'}, {'标的': '梅州城区220千伏赞化站扩建第三台主变工程等6项工程施工', '标包': '梅州城区110千伏干才输变电工程等3项工程施工', '排名': '招标失败', '候选人': '招标失败', '投标价': '招标失败'}, {'标的': '梅州城区220千伏赞化站扩建第三台主变工程等6项工程施工', '标包': '蕉岭供电局新建35kV北礤台区变工程等2项工程施工', '排名': '1', '候选人': '五华县华源电力发展有限公司', '投标价': '203万元'}, {'标的': '湛江220千伏工业园站扩建第二台主变工程等5项工程施工', '标包': '湛江220千伏工业园站扩建第二台主变工程施工', '排名': '1', '候选人': '中国能源建设集团广东火电工程有限公司', '投标价': '252.02万元'}, {'标的': '湛江220千伏工业园站扩建第二台主变工程等5项工程施工', '标包': '湛江110千伏曲界站扩建第二台主变工程等4项工程施工', '排名': '1', '候选人': '广东雷能电力集团有限公司', '投标价': '902.02万元'}, {'标的': '清远三江涡水供电所新建35kV涡水台区工程等4项工程施工', '标包': '清远三江涡水供电所新建35kV涡水台区工程等4项工程施工', '排名': '1', '候选人': '清远市方能电力工程安装有限公司', '投标价': '466.32万元'}]}
  537. enterprise = {
  538. "东莞市输变电工程建设有限责任公司": {
  539. "in_text": 1
  540. },
  541. "中国南方电网有限责任公司": {
  542. "in_text": 1
  543. },
  544. "中国建筑第四工程局有限公司": {
  545. "in_text": 1
  546. },
  547. "中国能源建设集团广东火电工程有限公司": {
  548. "in_text": 1
  549. },
  550. "中山市农村电力工程有限公司": {
  551. "in_text": 1
  552. },
  553. "中山市城区电气工程有限公司": {
  554. "in_text": 1
  555. },
  556. "五华县华源电力发展有限公司": {
  557. "in_text": 1
  558. },
  559. "南方电网供应链(广东)有限公司": {
  560. "in_text": 1
  561. },
  562. "广东威恒输变电工程有限公司": {
  563. "in_text": 1
  564. },
  565. "广东汇盈电力工程有限公司": {
  566. "in_text": 1
  567. },
  568. "深圳市远界管理咨询有限公司": {
  569. "in_text": 1
  570. },
  571. "广东电网有限责任公司": {
  572. "in_text": 1
  573. },
  574. "广东运峰电力安装有限公司": {
  575. "in_text": 1
  576. },
  577. "广东雷能电力集团有限公司": {
  578. "in_text": 1
  579. },
  580. "广州电力建设有限公司": {
  581. "in_text": 1
  582. },
  583. "惠州市惠城电力实业有限公司": {
  584. "in_text": 1
  585. },
  586. "清远三江涡水供电所": {
  587. "in_text": 1
  588. },
  589. "清远市方能电力工程安装有限公司": {
  590. "in_text": 1
  591. },
  592. "珠海电力建设工程有限公司": {
  593. "in_text": 1
  594. },
  595. "肇庆市恒电电力工程有限公司": {
  596. "in_text": 1
  597. },
  598. "蕉岭供电局": {
  599. "in_text": 1
  600. }
  601. }
  602. # rs = result_post_precess(d, enterprise=enterprise)
  603. # print(rs)
  604. from collections import Counter
  605. df = pd.read_csv('E:/模版提取/中国南方电网-站源公告.csv')
  606. df2 = pd.read_csv('E:/模版提取/中国南方电网-站源公告_html.csv')
  607. # df = pd.read_csv('E:/模版提取/医院大学站源公告.csv')
  608. # df2 = pd.read_csv('E:\模版提取/医院大学站源公告_html.csv')
  609. # df = pd.read_csv('E:/模版提取/站源数据_易派客1122.csv')
  610. # df2 = pd.read_csv('E:/模版提取/站源数据_易派客1122_html.csv')
  611. df = df.merge(df2, on='docid', how='inner')[:]
  612. print('公告数量:', len(df))
  613. df.fillna('', inplace=True)
  614. df = df[df['dochtmlcon'] != '']
  615. # df = df[df['web_source_name'] == '中山大学']
  616. # df = df[df['web_source_no'].str.contains('XX0182-')]
  617. print('公告数量2:', len(df))
  618. print(Counter(df['web_source_no']).most_common())
  619. rs_list = []
  620. n1 = n2 = 0
  621. datas = []
  622. datas2 = []
  623. keys = ['标的', '标包', '包号', '招标人', '预算', '限价', '联系人', '电话', '地址', '中标人', '中标价', '排名', '候选人', '投标价']
  624. key_ree = ['包号', '地址', '招标人', '标包', '标的', '电话', '联系人', '限价', '预算']
  625. key_win = ['中标人', '中标价', '包号', '地址', '标包', '标的', '电话', '联系人']
  626. key_can = ['候选人', '包号', '地址', '投标价', '排名', '标包', '标的', '电话', '联系人']
  627. key_pro = ['产品', '单价', '单位', '品牌', '品目', '总价', '数量', '规格']
  628. for docid, html, web_no, web_name in zip(df['docid'], df['dochtmlcon'], df['web_source_no'], df['web_source_name']):
  629. if '-' in web_no:
  630. web_no = web_no.split('-')[0]
  631. rs = template_predict(html, web_source_no=web_no)
  632. if rs == {}:
  633. continue
  634. # try:
  635. ds = result_post_precess(rs)
  636. # except Exception as e:
  637. # print('内容错误:',e , rs)
  638. # break
  639. print('docid:',docid)
  640. print('预处理前:',rs)
  641. print('预处理后:',ds)
  642. print('*'*20)
  643. # for d in rs.get('招标信息', []):
  644. # datas.append(tuple([docid, rs, ds, web_name]+[d.get(k, '') for k in keys]))
  645. # for d in rs.get('中标信息', []):
  646. # datas.append(tuple([docid, rs, ds, web_name]+[d.get(k, '') for k in keys]))
  647. # for d in rs.get('候选人信息', []):
  648. # datas.append(tuple([docid, rs, ds, web_name]+[d.get(k, '') for k in keys]))
  649. # for d in rs.get('产品信息', []):
  650. # datas2.append(tuple([docid, rs, ds, web_name] + [d.get(k, '') for k in key_pro]))
  651. #
  652. # # datas.append(tuple([docid, rs, ds, web_name, len(rs.get('招标信息', [])), len(rs.get('中标信息', [])), len(rs.get('候选人信息', [])), len(ds.get('prem', {}))]))
  653. #
  654. # df = pd.DataFrame(datas, columns=['docid', '原始结果', '解析结果', '站源']+keys)
  655. # df2 = pd.DataFrame(datas2, columns=['docid', '原始结果', '解析结果', '站源']+key_pro)
  656. #
  657. # for col in df.columns:
  658. # df[col] = df[col].apply(lambda x: json.dumps(x, ensure_ascii=False, indent=2, sort_keys=True) if not isinstance(x, str) else x)
  659. #
  660. # # df = pd.DataFrame(datas, columns=['docid', '原始结果', '解析结果', '站源', '招标数量', '中标数量', '候选数量', '包数量'])
  661. #
  662. # # df.to_excel('E:/模版提取/中国南方电网-站源公告_模板提取结果.xlsx', index=False)
  663. # # df.to_excel('E:/模版提取/医院大学站源公告_模板提取结果2.xlsx', index=False)
  664. # # df2.to_excel('E:/模版提取/医院大学站源公告_模板提取结果_产品信息.xlsx', index=False)
  665. # print('数量:%d, %.4d'%(len(df), len(datas)))
  666. #
  667. # print('************招标人************')
  668. # c = Counter(df['招标人'])
  669. # print(c.most_common())
  670. #
  671. # print('************中标人************')
  672. # c = Counter(df['中标人'])
  673. # print(c.most_common(100))
  674. #
  675. # print('************候标人************')
  676. # c = Counter(df['排名'])
  677. # print(c.most_common())