template4.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. # coding:utf8
  2. import re
  3. from typing import Dict, Any
  4. from BiddingKG.dl.template_extract.abstract_template import AbstractTemplate, table2list
  5. import os
  6. import json
  7. class BidInfoExtractTemplate(AbstractTemplate):
  8. """
  9. 招投标信息提取模板
  10. 适配包含分包、供应商、报价、成交等信息的表格提取
  11. """
  12. def __init__(self):
  13. super().__init__(
  14. template_id=os.path.abspath(__file__),
  15. priority=3
  16. )
  17. def check_call_timing(self, preprocessed_data: Dict[str, Any]) -> bool:
  18. """检查是否符合模板调用条件"""
  19. if "表格列表" not in preprocessed_data:
  20. return False
  21. tables = preprocessed_data["表格列表"]
  22. if len(tables) < 1:
  23. return False
  24. try:
  25. datas = tables[0]
  26. # 至少需要表头行和一行数据
  27. if len(datas) < 2:
  28. return False
  29. header = datas[0]
  30. # 表头列数需匹配目标表格(9列)
  31. if len(header) != 9:
  32. # print(f"表头列数异常:{len(header)}列,预期9列")
  33. return False
  34. # 表头内容正则匹配(泛化类似表达)
  35. header_patterns = [
  36. r'分包名称|标包名称|标段名称|包号|标段号', # 第1列:分包名称
  37. r'供应商名称|投标人名称|中标人名称|候选人名称', # 第2列:供应商名称
  38. r'报价金额|投标报价|参选价|投标价((万?元))?', # 第3列:报价金额
  39. r'成交金额|中标金额|成交价((万?元))?', # 第4列:成交金额
  40. r'实际成交金额|最终成交金额|实际中标金额((万?元))?', # 第5列:实际成交金额
  41. r'评审方式|评审类型|评标方式', # 第6列:评审方式
  42. r'评审结果|中标结果|成交结果', # 第7列:评审结果
  43. r'需求日期|采购日期|招标日期|服务日期', # 第8列:需求日期
  44. r'成交/未成交原因|中标/未中标原因|成交原因|未成交原因' # 第9列:成交/未成交原因
  45. ]
  46. # 逐列验证表头
  47. for i, pattern in enumerate(header_patterns):
  48. if not re.fullmatch(pattern, header[i]):
  49. # print(f'表头第{i+1}列未匹配:{pattern} vs {header[i]}')
  50. return False
  51. self.header = header
  52. self.data = datas[1:]
  53. return True
  54. except Exception as e:
  55. print(f"表格验证错误: {e}")
  56. return False
  57. def extract(self, preprocessed_data: Dict[str, Any]) -> Dict[str, Any]:
  58. """提取表格中的招投标信息"""
  59. # 初始化返回结果结构
  60. extract_result = {
  61. "项目编号": "",
  62. "项目名称": "",
  63. "招标信息": [],
  64. "中标信息": [],
  65. "候选人信息": [],
  66. "产品信息": []
  67. }
  68. for row in self.data:
  69. # 跳过列数异常的行
  70. if len(row) != 9:
  71. continue
  72. # 解析每行数据(按data.json表头顺序)
  73. package_name, supplier_name, quote_amount, deal_amount, actual_deal_amount, review_method, review_result, demand_date, deal_reason = row
  74. # 处理金额单位补充
  75. # 报价金额单位补充
  76. if re.search(r'[万亿美欧日]?元', self.header[2]) and not re.search(r'[万亿美欧日]?元', quote_amount):
  77. quote_amount += re.search(r'[万亿美欧日]?元', self.header[2]).group(0)
  78. # 成交金额单位补充
  79. if re.search(r'[万亿美欧日]?元', self.header[3]) and not re.search(r'[万亿美欧日]?元', deal_amount):
  80. deal_amount += re.search(r'[万亿美欧日]?元', self.header[3]).group(0)
  81. # 实际成交金额单位补充
  82. if re.search(r'[万亿美欧日]?元', self.header[4]) and not re.search(r'[万亿美欧日]?元', actual_deal_amount):
  83. actual_deal_amount += re.search(r'[万亿美欧日]?元', self.header[4]).group(0)
  84. # 判断是否为成交/中标结果,优先提取候选人信息(有排名逻辑,此处根据成交原因提取排名)
  85. rank = ""
  86. # 从成交原因中提取排名(如"第1中选人")
  87. rank_match = re.search(r'第(\d+)中选人', deal_reason)
  88. if rank_match:
  89. rank = rank_match.group(1)
  90. if rank and supplier_name:
  91. # 提取候选人信息(满足候选人+排名条件)
  92. candidate_info = {
  93. "标的": "", # 表格无标的信息,置空
  94. "标包": package_name,
  95. "包号": "", # 表格无包号信息,置空
  96. "候选人": supplier_name,
  97. "投标价": quote_amount,
  98. "排名": rank,
  99. "联系人": "", # 表格无候选人联系人信息,置空
  100. "电话": "", # 表格无候选人电话信息,置空
  101. "服务时间": demand_date, # 用需求日期替代服务时间
  102. "地址": "" # 表格无候选人地址信息,置空
  103. }
  104. # 过滤空值字段
  105. candidate_info = {k: v for k, v in candidate_info.items() if v}
  106. extract_result["候选人信息"].append(candidate_info)
  107. elif review_result == "成交" and supplier_name:
  108. # 提取中标信息(满足中标人条件)
  109. bid_win_info = {
  110. "标的": "", # 表格无标的信息,置空
  111. "标包": package_name,
  112. "包号": "", # 表格无包号信息,置空
  113. "中标人": supplier_name,
  114. "中标价": actual_deal_amount,
  115. "联系人": "", # 表格无中标人联系人信息,置空
  116. "电话": "", # 表格无中标人电话信息,置空
  117. "服务时间": demand_date, # 用需求日期替代服务时间
  118. "地址": "" # 表格无中标人地址信息,置空
  119. }
  120. # 过滤空值字段
  121. bid_win_info = {k: v for k, v in bid_win_info.items() if v}
  122. extract_result["中标信息"].append(bid_win_info)
  123. # 招标信息:表格无招标人/预算信息,故置空
  124. extract_result["招标信息"] = []
  125. # 过滤空列表和空值字段
  126. # 过滤项目编号/名称(无则删除)
  127. if not extract_result["项目编号"]:
  128. del extract_result["项目编号"]
  129. if not extract_result["项目名称"]:
  130. del extract_result["项目名称"]
  131. # 过滤空列表
  132. if not extract_result["招标信息"]:
  133. del extract_result["招标信息"]
  134. if not extract_result["中标信息"]:
  135. del extract_result["中标信息"]
  136. if not extract_result["候选人信息"]:
  137. del extract_result["候选人信息"]
  138. if not extract_result["产品信息"]:
  139. del extract_result["产品信息"]
  140. return extract_result
  141. # 使用示例
  142. if __name__ == "__main__":
  143. # 读取数据文件
  144. with open("data.json", "r", encoding="utf8") as f:
  145. preprocessed_data = json.load(f)
  146. # 初始化模板并提取信息
  147. template = BidInfoExtractTemplate()
  148. if template.check_call_timing(preprocessed_data):
  149. result = template.extract(preprocessed_data)
  150. print("提取结果:", json.dumps(result, ensure_ascii=False, indent=2))
  151. else:
  152. print("当前数据不满足模板调用条件")