10.28适配

This commit is contained in:
zy123 2024-10-28 17:40:02 +08:00
parent 8be199a9c8
commit 2aaaaa3c02
11 changed files with 867 additions and 404 deletions

2
.idea/encodings.xml generated
View File

@ -11,6 +11,8 @@
<file url="file://$PROJECT_DIR$/flask_app/static/output/d23f3af9-21f9-4d59-9a47-8da58b934a00/log.txt" charset="GBK" />
<file url="file://$PROJECT_DIR$/flask_app/static/output/da877b04-57e2-422a-a93e-ca9b1c802c95/log.txt" charset="GBK" />
<file url="file://$PROJECT_DIR$/flask_app/static/output/output1/2a86c54f-8e1d-456e-a966-845b48d80b90/log.txt" charset="GBK" />
<file url="file://$PROJECT_DIR$/flask_app/static/output/output1/2c0dfa10-b356-467a-9fde-db06d0c1e028/log.txt" charset="GBK" />
<file url="file://$PROJECT_DIR$/flask_app/static/output/output1/4cadda82-be0c-4c6e-be4a-89d42e6960b4/log.txt" charset="GBK" />
<file url="file://$PROJECT_DIR$/flask_app/static/output/output1/64f3b2a7-9a80-4311-8380-2190c9d17420/log.txt" charset="GBK" />
</component>
</project>

View File

@ -36,7 +36,7 @@ def little_parse_goods(output_folder, file_path):
dict: 包含 '基础信息' 的字典
"""
# 截取特定的货物 PDF 文件
selections = [4, 5] # 仅处理 selection 4 和 5
selections = [4, 1] # 仅处理 selection 1和4 #公告+投标人须知
files = truncate_pdf_specific_goods(file_path, output_folder,selections)
if not files:
raise ValueError("未找到截取后的文件。")

View File

@ -36,7 +36,8 @@
],
"关键字": [
"法定代表",
"法人代表"
"法人代表",
"法定代表人身份证"
]
},
"法定代表人授权人身份证": {
@ -93,9 +94,8 @@
"供应商认为有必要提供的其他资料及方案"
],
"关键字": [
"实力",
"资质证书",
"证明材料",
"企业实力",
"企业资质证书",
"安全生产许可证",
"认证证书"
]
@ -110,14 +110,14 @@
"企业经营业绩"
],
"关键字": [
"业绩",
"企业业绩",
"类似项目",
"案例",
"项目情况",
"施工项目"
]
},
"财务审计报告": {
"财务信息(财务审计报告)": {
"章节": [
"提供依法缴纳税收和社会保障资金的相关材料",
"具有依法缴纳税收和社会保障资金的良好记录",
@ -131,12 +131,10 @@
"关键字": [
"财务报告",
"财务状况",
"资格审查资料",
"资信证明",
"财务会计报表"
]
},
"缴纳税收证明": {
"财务信息(缴纳税收证明)": {
"章节": [
"提供依法缴纳税收和社会保障资金的相关材料",
"具有依法缴纳税收和社会保障资金的良好记录",
@ -150,11 +148,10 @@
],
"关键字": [
"缴纳税收",
"用户评审的证明材料",
"资格审查资料"
"税收缴纳证明"
]
},
"缴纳社保证明": {
"财务信息(公司缴纳社保证明)": {
"章节": [
"投标人资质证明文件",
"按照“供应商资格要求”规定提交的相关证明材料",
@ -165,7 +162,9 @@
],
"关键字": [
"社会保障资金",
"资格审查资料"
"社保资料",
"社会保险证明",
"社保证明"
]
}
}

View File

@ -1,76 +1,172 @@
# -*- encoding:utf-8 -*-
import ast
import json
from flask_app.general.json_utils import clean_json_string
from flask_app.general.多线程提问 import multi_threading,read_questions_from_file
from flask_app.general.通义千问long import upload_file,qianwen_long
import re
import os
import sys
from flask_app.general.多线程提问 import multi_threading
from flask_app.general.通义千问long import upload_file
# 从json文件中读取数据
with open('../testdir/test.json', 'r', encoding='utf-8') as f:
data_dict = json.load(f)
# 定义目标名称列表
target_names = [
"营业执照",
# "开户信息",
"法定代表人身份证",
# "法定代表人授权人身份证",
"人员证书",
"人员社保资料",
# "劳动合同",
"企业证书",
"企业业绩",
"财务审计报告",
"缴纳税收证明",
"公司缴纳社保证明"
]
# 定义user_query模板
def generate_user_query(target, chapters, keywords):
#章节名格式通常是如'三、联合体协议书'这样的序号+标题。现在我需要将{target}贴在该章节的最后面,但是在下一章之前,目前我需要定位到插入的位置,
template = f"""这是投标文件模板,作为投标人,我需要把不同的投标材料填充到对应位置,请你根据该文件回答:{target}应该插入在该文件哪块地方?你可能需要查找以下关键词出现的地方:{', '.join([f"'{chapter}'" for chapter in chapters])},并确认插入的小节,然后在该小节的末尾内容之后插入{target}请你返回给我插入位置的上下文内容上文为该小节的末尾内容下文为下一小节的开头内容上下文字数请限制在20字以内即你只需返回最末尾的内容以及最开头的内容不需考虑语义。你的回答以json格式返回外层键名为'{target}',嵌套键名分别是'上文','下文',上下文内容应完全与原文保持一致,不得擅自删减总结,若插入位置不明确,那么嵌套键名'上文''下文'的键值为'未知',输出格式示例如下:
{{
{target}:{{
"上文":"上文测试投标人: (盖单位章)
",
"下文":"下文测试章节名
招标人名称测试"
}}
}}
"""
template2=f"""该文件为投标文件格式要求,请你根据该招标文件回答:{target}应该附在哪个地方?你可能需要查找以下章节出现的地方:{', '.join([f"'{chapter}'" for chapter in chapters])};或者可能匹配的关键字:{', '.join([f"'{kw}'" for kw in keywords])},并确定所在章节。我需要将{target}贴在该章节的最后面目前我需要定位到插入的位置请你返回给我插入位置的上下文下文应该是下一章的章节名或开头内容字数限制在30字以内以json格式返回键名分别是'上文','下文',上下文格式内容应完全与原文保持一致,不得擅自删减总结,示例输出如下:
{{
"上文":"上文相关内容
测试",
"下文":"四、下文章节名
招标人名称测试"
}}
def load_data(json_path):
"""
return template
从指定的JSON文件中加载数据
# 生成user_query_list
user_query_list = []
Args:
json_path (str): JSON文件的路径
for target in target_names:
if target in data_dict:
chapters = data_dict[target]["章节"]
keywords = data_dict[target]["关键字"]
user_query = generate_user_query(target, chapters, keywords)
user_query_list.append({
"target": target,
"query": user_query
})
else:
print(f"警告:'{target}'未在数据字典中找到相关信息。")
Returns:
dict: 加载的JSON数据字典
"""
try:
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
return data
except FileNotFoundError:
print(f"错误:文件未找到 - {json_path}")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"错误解析JSON文件时出错 - {e}")
sys.exit(1)
#TODO:先对文件打标记,
# 将生成的查询添加到queries列表
queries = [item['query'] for item in user_query_list]
truncate_file="C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest19\\zbtest19_214-320.pdf"
# 上传文件并获取file_id
file_id = upload_file(truncate_file)
# 使用multi_threading并行处理查询
results = multi_threading(queries, "", file_id, 2)
# 清理返回结果并输出
baseinfo_list = [clean_json_string(res) for _, res in results] if results else []
for i in baseinfo_list:
print(json.dumps(i, ensure_ascii=False, indent=4))
def define_target_names():
"""
定义目标名称列表
Returns:
list: 目标名称列表
"""
return [
"营业执照",
# "开户信息",
"法定代表人身份证",
# "法定代表人授权人身份证",
"人员证书",
"人员社保资料",
# "劳动合同",
"企业证书",
"企业业绩",
"财务信息(财务审计报告)",
"财务信息(缴纳税收证明)",
"财务信息(公司缴纳社保证明)"
]
def generate_user_query(target, chapters, keywords):
"""
根据目标章节和关键词生成用户查询模板
Args:
target (str): 目标名称
chapters (list): 相关章节列表
keywords (list): 相关关键词列表
Returns:
str: 生成的用户查询字符串
"""
template3 = f"""这是投标文件模板,作为投标人,我需要把不同的投标材料填充到对应位置,请你根据该文件回答:{target}应该插入在该文件哪个地方?你可能需要查找以下关键词出现的地方:{', '.join([f"'{kw}'" for kw in keywords])},并确认插入的位置。我已在原文中打上若干待插入位置的标记,形如'[$$第17个可插入位置$$]',它的标记与它上面的小节内容关联。你需要返回给我{target}应该插入位置的标记序号,即'[$$第17个可插入位置$$]'中的'17',而不是页码,若有多个位置需要插入,可以返回多个序号,你的回答以数组返回,如[17, 19],若插入位置不明确,那么返回[-1]。
"""
return template3
def generate_user_queries(target_names, data_dict):
"""
为每个目标生成对应的用户查询
Args:
target_names (list): 目标名称列表
data_dict (dict): 数据字典
Returns:
list: 包含目标和查询的字典列表
"""
user_queries = []
for target in target_names:
if target in data_dict:
chapters = data_dict[target].get("章节", [])
keywords = data_dict[target].get("关键字", [])
query = generate_user_query(target, chapters, keywords)
user_queries.append({
"target": target,
"query": query
})
else:
print(f"警告:'{target}'未在数据字典中找到相关信息。")
return user_queries
def process_string_list(string_list):
"""
处理字符串列表提取方括号内的内容并转换为实际列表
Args:
string_list (str): 包含方括号的字符串
Returns:
list: 解析后的列表内容
"""
match = re.search(r'\[(.*?)\]', string_list)
if match:
content_inside = match.group(1).strip()
if content_inside:
items = [item.strip() for item in content_inside.split(',')]
if all(item.isdigit() for item in items):
formatted_list = [int(item) for item in items]
else:
formatted_list = items
return formatted_list
return []
def main():
# 定义JSON文件路径
# json_path = "flask_app/general/static/插入位置.json"
json_path = "D:\\flask_project\\flask_app\\general\\static\\插入位置.json"
# 加载数据
data_dict = load_data(json_path)
# 定义目标名称
target_names = define_target_names()
# 生成用户查询列表
user_query_list = generate_user_queries(target_names, data_dict)
if not user_query_list:
print("没有生成任何用户查询。")
sys.exit(0)
# 提取查询
queries = [item['query'] for item in user_query_list]
# 定义文件路径
format_part = "C:\\Users\\Administrator\\Desktop\\outzb2 (2).pdf"
# 检查文件是否存在
if not os.path.isfile(format_part):
print(f"错误:文件未找到 - {format_part}")
sys.exit(1)
# 上传文件并获取file_id
file_id = upload_file(format_part)
if not file_id:
print("错误:文件上传失败。")
sys.exit(1)
# 使用多线程并行处理查询
results = multi_threading(queries, "", file_id,2)
if not results:
print("错误:未收到任何处理结果。")
sys.exit(1)
# 清理返回结果
baseinfo_list = [process_string_list(res) for _, res in results]
# 输出结果
for info in baseinfo_list:
print(f'{target_names}:{info}')
if __name__ == "__main__":
main()

View File

@ -153,7 +153,7 @@ if __name__ == '__main__':
# file_path = 'C:\\Users\\Administrator\\Desktop\\货物标\\output4\\2-招标文件2020年广水市中小学教师办公电脑系统及多媒体“班班通”设备采购安装项目_tobidders_notice_part2.pdf'
# file_path = 'C:\\Users\\Administrator\\Desktop\\货物标\\output4\\磋商文件_tobidders_notice_part2.pdf'
# file_path = 'C:\\Users\\Administrator\\Desktop\\货物标\\截取test\\交警支队机动车查验监管系统项目采购_tobidders_notice_part1.pdf'
file_path="C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\\zbtest12.pdf"
file_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\68549b0b-e892-41a9-897c-c3694535ee61\\ztbfile.pdf"
# ress = extract_common_header(file_path)
# print(ress)
res=extract_text_by_page(file_path)

View File

@ -387,6 +387,7 @@ def process_and_stream(file_url, zb_type):
yield f"data: {json.dumps(response, ensure_ascii=False)}\n\n"
logger.info("开始后处理:保存文件+发送提取之后的数据")
# **保存 combined_data 到 output_folder 下的 'final_result.json'**
output_json_path = os.path.join(output_folder, 'final_result.json')
extracted_info_path=os.path.join(output_folder, 'extracted_result.json')

View File

@ -26,7 +26,7 @@ logger=None
# 创建全局线程池
executor = ThreadPoolExecutor()
def preprocess_files(output_folder, downloaded_file_path, file_type):
def preprocess_files(output_folder, downloaded_file_path, file_type,unique_id):
logger.info("starting 文件预处理...")
logger.info("output_folder..." + output_folder)
@ -45,7 +45,7 @@ def preprocess_files(output_folder, downloaded_file_path, file_type):
return None
# 调用截取PDF多次
truncate_files = truncate_pdf_multiple(pdf_path, output_folder)
truncate_files = truncate_pdf_multiple(pdf_path, output_folder,unique_id)
# 处理各个部分
truncate0_docpath = pdf2docx(truncate_files[0]) # 投标人须知前附表转docx

View File

@ -3,6 +3,14 @@ import re # 导入正则表达式库
import os # 用于文件和文件夹操作
from flask_app.general.merge_pdfs import merge_pdfs
import concurrent.futures
import logging
def get_global_logger(unique_id):
if unique_id is None:
return logging.getLogger() # 获取默认的日志器
logger = logging.getLogger(unique_id)
return logger
logger=None
def clean_page_content(text, common_header):
# 首先删除抬头公共部分
if common_header: # 确保有公共抬头才进行替换
@ -392,7 +400,7 @@ def merge_selected_pdfs(output_folder, truncate_files, output_path, base_file_na
return ""
def truncate_pdf_multiple(input_path, output_folder):
def truncate_pdf_multiple(input_path, output_folder, unique_id="123"):
"""
处理 PDF 文件选择 selection 1-6 的部分并合并结果
@ -403,6 +411,8 @@ def truncate_pdf_multiple(input_path, output_folder):
Returns:
list: 截取的文件路径列表包括合并后的文件路径如果有
"""
global logger
logger = get_global_logger(unique_id)
base_file_name = os.path.splitext(os.path.basename(input_path))[0] # 纯文件名
truncate_files = []
selections = range(1, 7) # 选择 1 到 6
@ -422,9 +432,10 @@ def truncate_pdf_multiple(input_path, output_folder):
valid_files = [f for f in files if f]
truncate_files.extend(valid_files)
else:
logger.error(f"Selection {selection}: 截取失败,已添加空字符串。")
truncate_files.append("") # 截取失败时添加空字符串
except Exception as e:
print(f"Selection {selection} 生成了一个异常: {e}")
logger.error(f"Selection {selection} 生成了一个异常: {e}")
truncate_files.append("") # 发生异常时添加空字符串
if any(f for f in truncate_files if f): # 检查是否有有效的文件路径
@ -432,18 +443,18 @@ def truncate_pdf_multiple(input_path, output_folder):
merged_result = merge_selected_pdfs(output_folder, truncate_files, merged_output_path, base_file_name)
if merged_result:
truncate_files.append(merged_result)
print(f"已生成合并文件: {merged_output_path}")
logger.info(f"merged_baseinfo: 已生成合并文件: {merged_output_path}")
else:
truncate_files.append("") # 如果merged_result未生成添加空字符串
print("未生成合并文件,因为没有找到需要合并的 PDF 文件。")
logger.warning("merged_baseinfo: 未生成合并文件,因为没有找到需要合并的 PDF 文件。")
else:
truncate_files.append("") # 如果没有文件需要合并,也添加空字符串
print(f"没有文件需要合并 for {input_path}")
logger.warning(f"merged_baseinfo: 没有文件需要合并 for {input_path}")
return truncate_files
def truncate_pdf_specific_engineering(pdf_path, output_folder, selections):
def truncate_pdf_specific_engineering(pdf_path, output_folder, selections, unique_id="123"):
"""
处理 PDF 文件选择指定的 selections并合并结果
@ -451,11 +462,14 @@ def truncate_pdf_specific_engineering(pdf_path, output_folder, selections):
pdf_path (str): 要处理的 PDF 文件路径
output_folder (str): 截取后的文件保存文件夹路径
selections (list): 需要截取的部分例如 [4, 5]
unique_id (str): 用于日志记录的唯一标识符
Returns:
list: 截取的文件路径列表包括合并后的文件路径如果有
"""
try:
global logger
logger = get_global_logger(unique_id)
base_file_name = os.path.splitext(os.path.basename(pdf_path))[0]
truncate_files = []
@ -474,10 +488,10 @@ def truncate_pdf_specific_engineering(pdf_path, output_folder, selections):
valid_files = [f for f in files if f]
truncate_files.extend(valid_files)
else:
logger.error(f"Selection {selection}: 截取失败,已添加空字符串。")
truncate_files.append("") # 截取失败时添加空字符串
print(f"截取 selection {selection} 失败,已添加空字符串。")
except Exception as e:
print(f"Selection {selection} 生成了一个异常: {e}")
logger.error(f"Selection {selection} 生成了一个异常: {e}")
truncate_files.append("") # 发生异常时添加空字符串
if any(f for f in truncate_files if f): # 检查是否有有效的文件路径
@ -485,26 +499,27 @@ def truncate_pdf_specific_engineering(pdf_path, output_folder, selections):
merged_result = merge_selected_pdfs(output_folder, truncate_files, merged_output_path, base_file_name)
if merged_result:
truncate_files.append(merged_result)
print(f"已生成合并文件: {merged_output_path}")
logger.info(f"merged_specific: 已生成合并文件: {merged_output_path}")
else:
truncate_files.append("") # 如果 merged_result 未生成,添加空字符串
print("未生成合并文件,因为没有找到需要合并的 PDF 文件。")
logger.warning("merged_specific: 未生成合并文件,因为没有找到需要合并的 PDF 文件。")
else:
truncate_files.append("") # 如果没有文件需要合并,也添加空字符串
print(f"没有文件需要合并 for {pdf_path}")
logger.warning(f"merged_specific: 没有文件需要合并 for {pdf_path}")
return truncate_files
except Exception as e:
print(f"Error in truncate_pdf_specific_engineering: {e}")
return [] # 返回空列表表示失败
# 假设 selections 的长度是已知的,可以通过传递参数或其他方式获取
logger.error(f"Error in truncate_pdf_specific_engineering: {e}")
return [""] * len(selections) # 返回与 selections 数量相同的空字符串列表
# TODO:需要完善二次请求。目前invalid一定能返回 前附表 须知正文如果为空的话要额外处理一下比如说就不进行跳转见xx表 开评定标这里也要考虑 如果评分表为空,也要处理。
#TODO:zbtest8 zbtest18有问题 后期需要完善,截取需要截两次,第一次严格第二次宽松
if __name__ == "__main__":
input_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest1.pdf" # 可以是单个PDF文件路径或文件夹路径
# input_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\e378e002-45ff-440d-a65a-9974b5015472\\ztbfile.pdf"
input_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest4_evaluation_method.pdf" # 可以是单个PDF文件路径或文件夹路径
# input_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\68549b0b-e892-41a9-897c-c3694535ee61\\ztbfile.pdf"
# input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\2-招标文件.pdf"
output_folder="C:\\Users\\Administrator\\Desktop\\招标文件\\output6"
files=truncate_pdf_multiple(input_path,output_folder)

View File

@ -1,228 +1,480 @@
import json
import re
from collections import defaultdict
def process_data_based_on_key(data):
exclude_word = ["", "未知", "评分因素"]
# 获取字典的键列表
keys = list(data.keys())
# 检查键的数量是否为1并且 exclude_word 中的任何词包含在 keys[0] 中
if len(keys) == 1 and any(word in keys[0] for word in exclude_word):
# 返回内层的字典
return data[keys[0]]
# 如果条件不满足,则返回原始字典
return data
def parse_json_with_duplicates(raw_string):
def post_process_baseinfo(base_info):
"""
解析具有重复键的 JSON 字符串将所有重复的键值对存储为列表
'base_info' 任务完成后执行的函数
确保在缺少某些键时返回 good_list=[]
Args:
json_string (str): 需要解析的 JSON 字符串
参数
- base_info (dict): 原始的 base_info 数据
Returns:
dict: 解析后的字典重复的键对应的值为列表
返回
- tuple: (处理后的 base_info, good_list)
"""
try:
pure_base_info = base_info.get("基础信息", {})
# 尝试提取 '货物列表',若中间某个键不存在,返回 good_list=[]
procurement_reqs = pure_base_info.get('采购要求', {})
technical_requirements = procurement_reqs.get('技术要求', {})
good_list = technical_requirements.pop('货物列表', []) # 如果 '货物列表' 不存在,返回 []
eg:输入"综合实力": {
"评分": "2分",
"要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分不能够提供不得分开标时需提供原件"
# 删除 '货物列表' 后更新原始的 base_info
if '技术要求' in procurement_reqs and not technical_requirements:
# 若技术要求为空,删除该键
procurement_reqs.pop('技术要求')
if '采购要求' in pure_base_info and not procurement_reqs:
# 若采购要求为空,删除该键
pure_base_info.pop('采购要求')
# 更新基础信息
base_info['基础信息'] = pure_base_info
return base_info, good_list
except Exception as e:
return base_info, [] # 返回空列表
data={
"基础信息": {
"招标人/代理信息": {
"招标人": "广水市公路管理局",
"招标人联系方式": {
"名称": "广水市公路管理局",
"联系电话": "17362698785",
"地址": "随州市广水市四贤路 7号应山办事处南门"
},
"综合实力": {
"评分": "2分",
"要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分否则不得分。证书开标原件备查"
"招标代理机构": "湖北众恒永业工程项目管理有限公司广水分公司",
"招标代理机构联系方式": {
"名称": "湖北众恒永业工程项目管理有限公司广水分公司",
"联系电话": "13997896775",
"地址": "广水市名都花园 22栋"
},
"项目联系方式": {
"名称": "闵杨、喻亚",
"联系电话": "13997896775、15337331616"
}
输出"综合实力": [
{
"评分": "2分",
"要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分不能够提供不得分开标时需提供原件"
},
{
"评分": "2分",
"要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分否则不得分。证书开标原件备查"
}]
"""
def custom_object_pairs_hook(pairs):
d = defaultdict(list)
for key, value in pairs:
# 如果值是字典或列表,递归处理
if isinstance(value, dict):
value = process_dict(value)
elif isinstance(value, list):
value = process_list(value)
d[key].append(value)
# 将有多个值的键转换为列表,单个值的键保持原样
return {key: (values if len(values) > 1 else values[0]) for key, values in d.items()}
def process_dict(d):
"""
递归处理字典确保所有重复键的值为列表
Args:
d (dict): 需要处理的字典
Returns:
dict: 处理后的字典
"""
return custom_object_pairs_hook(d.items())
def process_list(l):
"""
递归处理列表确保列表中的所有字典也被处理
Args:
l (list): 需要处理的列表
Returns:
list: 处理后的列表
"""
return [process_dict(item) if isinstance(item, dict) else item for item in l]
"""输入字符串,提取 { 和 } 之间的内容,并将其解析为字典"""
if not raw_string.strip():
return {}
match = re.search(r'\{[\s\S]*\}', raw_string)
if match:
try:
json_string = match.group(0)
return json.loads(json_string, object_pairs_hook=custom_object_pairs_hook)
except json.JSONDecodeError as e:
print(f"json_utils: extract_content_from_json: JSON decode error: {e}")
return {}
else:
print("json_utils: extract_content_from_json: No valid JSON content found.")
return {}
def combine_technical_and_business(data, target_values):
extracted_data = {} # 根级别存储所有数据
technical_found = False
business_found = False
def extract_nested(data, parent_key='', is_technical=False, is_business=False):
nonlocal technical_found, business_found
if isinstance(data, dict):
for key, value in data.items():
current_key = f"{parent_key}.{key}" if parent_key else key
# 检查是否为技术标的内容
if any(target in key for target in target_values):
if not is_technical:
extracted_data[key] = value
technical_found = True
continue
# 默认其他所有内容都归为商务标
else:
if not is_business:
if '商务评分' not in extracted_data:
extracted_data['商务评分'] = {}
extracted_data['商务评分'][key] = value
business_found = True
continue
if isinstance(value, dict) or isinstance(value, list):
extract_nested(value, current_key, is_technical, is_business)
elif isinstance(data, list):
for index, item in enumerate(data):
extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business)
extract_nested(data)
if not technical_found:
extracted_data['技术评分'] = ''
if not business_found:
extracted_data['商务评分'] = ''
return extracted_data
raw_data="""
{
"一包": {
"技术评分": {
"主要监理岗位的职责": {
"评分": "4分",
"要求": "1、总监理工程师的职责全面、清晰、合理得 1.2-2分一般的1.2分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 1.2-2分一般的 1.2分。"
},
"项目信息": {
"项目名称": "107国道交通信号灯及安全防护设施采购项目",
"项目编号": "HBZHYY-2021-001",
"项目概况": "107国道交通信号灯及安全防护设施采购项目",
"项目基本情况": {
"1": "政府采购计划编号2020-09-000560",
"2": "项目编号HBZHYY-2021-001",
"3": "项目名称107国道交通信号灯及安全防护设施采购项目",
"4": "采购方式:公开招标",
"5": "预算金额RMB10071358.07元",
"6": "最高限价RMB10070194.68元(一包:RMB6230708.59元、二包:RMB2175838.09元、三包:RMB1663648元",
"7": {
"7.1": "项目概况及内容一包107国道交通信号灯及安全防护设施的监控抓拍系统、电子警察、信号灯、专用配套设备等。二包107国道广水段标示标牌及标线等。三包107国道霞家河超限检测站抓拍、超重系统等。以上具体要求见招标文件第三章项目技术、服务及商务要求。",
"7.2": "项目地点107国道路段。",
"7.3": "投标人只能对此项目三个包中的一个包进行报名,若同时对两个包或两个包以上进行报名,则所有报名无效。"
},
"备注": "若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求则本项技术部分50分不得分。"
"8": "合同履行期限:合同签订后 6个月内。",
"9": "本项目不接受联合体投标。",
"10": "是否可采购进口产品:否。"
},
"商务评分": {
"控制系统内主板": {
"评分": "10分",
"要求": "所投电梯控制系统内主板为制造商原厂原品牌制造生产且为进口部件得 10分。提供进口部件报关单及原产地证明扫描件加盖公章否则不得分"
"招标控制价": "RMB10070194.68元",
"投标竞争下浮率": "未知",
"分包": "不允许",
"是否接受联合体投标": ""
},
"采购要求": {
"技术要求": {
"交通信号灯": {
"单灯技术要求": "1、道路交通信号灯单灯必须符合国家标准GB14887-2011《道路交通信号灯》全部技术规定并通过公安部交通安全产品质量监督检测中心的检测。",
"光源要求": "2、道路交通信号灯光源必须采用户外型超亮度发光二极管LED使用寿命不少于50000小时。",
"材质要求": "3、信号灯灯具材质为铝质金属材料。机动车信号灯灯芯透镜尺寸采用¢400mm规格人行信号灯灯芯透镜尺寸采用¢300mm相同规格的灯芯可以互换。信号灯外观应与目前广水市使用的信号灯外观相一致。",
"供电电路要求": "4、信号灯需采用恒流供电电路单体信号灯功率¢400mm规格不超过20VA¢300mm规格不超过 15VA。",
"倒计时器要求": "1、道路交通信号倒计时器安装在信号灯的上方或右方2位数码显示倒九秒提示。当机动车信号灯采用悬臂式杆件时机动车信号灯倒计时器规格为 800×600当采用立柱式杆件时倒计时器规格为 400×400。人行信号灯倒计时显示器透光面为Φ300mm或 300mm ×300mm。倒计时显示器的光学性能、工作条件、机械强度、电气性能均符合国家安全行业标准GA/T508-2004《道路交通信号倒计时显示器》的全部技术规定。",
"倒计时器运行要求": "2、倒计时器运行中遇到电磁、静电、电网等干扰时不能有死机现象。",
"倒计时器测试要求": "3、倒计时器必须经过过电压、过电流测试具有防雷击功能。",
"倒计时器触发方式": "4、倒计时器应为学习型并支持脉冲触发、黄灯触发及通讯式触发。",
"信号灯杆技术要求": "1、信号灯杆所属的立柱、法兰盘、地脚螺栓、螺母、垫片、加强筋等金属构件及悬臂、支撑臂、拉杆、抱箍座、夹板等附件的防腐性能应符合GB/T18226《公路交通工程钢构件防腐技术条件》的规定。",
"信号灯杆材质要求": "2、信号灯杆应采用圆形或多棱形经热镀锌处理的钢管制造悬臂式灯杆悬臂杆与支撑杆使用圆形或多棱形的变截面型材制作悬臂与灯杆连接端宜焊接固定法兰盘悬臂下应留有进出线孔。",
"信号灯杆表面处理": "3、信号灯杆制作后须经过防锈处理底层喷涂富锌防锈底漆外层喷涂银灰色瓷漆。",
"信号灯杆底部要求": "4、信号灯灯杆距路面约 300-350mm处留有拉线孔和拉线孔门。孔门盖应设有防盗措施孔内设置接地端子座并与接地线可靠接驳。",
"信号灯杆顶部要求": "5、立柱式灯杆顶部安装灯具处应留有出线孔并配备橡胶护套、电缆线回水弯挂钩灯杆顶部应安装塑料或经防腐处理的内套式金属防水管帽。",
"悬臂式灯杆拉杆要求": "6、悬臂式灯杆拉杆宜使用圆钢制作一端配有可调距离的螺旋扣直径和长度根据悬臂长度确定。",
"信号灯杆法兰盘要求": "7、信号灯杆杆体底部应焊接固定法兰盘法兰盘与杆体之间应均匀焊接加强筋。",
"人行信号灯设计要求": "8、一体式人行信号灯采用整灯嵌入式设计、结构紧凑、方便拆装、便于维护。杆体前后金属面板喷塑处理两侧铝型材包边。"
},
"制造商技术实力": [
{
"评分": "3分",
"要求": "一级证书得3分二级证书得1分其他不得分"
"交通监控视频子系统": {
"高清视频抓拍像机": {
"有效像素": "≥900W像素",
"最低照度": "彩色≤0.001lx",
"传感器类型": "≥1英寸全局曝光COMS/GMOS/GS COMS",
"电子快门": "至少满足 1/25s至 1/100,000s可调",
"视频压缩标准": "至少支持H264、H265等",
"视频分辨率": "≥4096×2160向下可设置",
"视频流帧率": "≥25fps,至少双视频流",
"图片压缩方式": "JPEG",
"图像分辨率": "≥4096(H)×2160(V)",
"强光抑制": "具备",
"API接口开放": "具备支持标准ONVIF协议与第三方厂家设备进行互联支持GB/T28181应提供 SDK",
"通讯接口": "≥1个 RJ4510M/100M/1000M自适应以太网电口≥1个 RS-485接口",
"前端存储卡": "嵌入式支持断网时本地存储裸容量≥128GB",
"具备其他功能": "应具备I/O触发、RS-485触发、视频触发支持电源同步支持频闪式卜光装置和脉冲式补光装置同步补光",
"具备其他功能2": "具备AI深度学习算法和GPU芯片",
"光圈、聚焦": "手动光圈;焦距应可根据车道宽度及抓拍距离进行调整设置,要求中心成像圆内解像力不小于 900万像素边缘解像力不低于中心 60%,镜头的成像尺寸应与摄像机成像靶面尺寸相等或略大。镜头应配置电动偏振镜,具备根据环境自动切换功能",
"防护罩类型": "室外型防护罩,含底座;具备隔热防潮、防水、防尘、防腐、防震等功能;具有加热器、支架、遮阳罩等,具有良好的密封性和恒温等功能。",
"防护等级": "≥IP66"
},
{
"评分": "2分",
"要求": "行业销量排名连续前 2 名,得 2 分,第 4-6 名得 0.5 分,其他不得分"
"补光灯": {
"配置方式": "每个车道配置≥1台补光装置必须与高清摄像设备高度匹配",
"LED管芯": "采用超高亮大功率白光LED管芯灯珠数量不少于 20颗",
"功耗": "30W/车道≤单台平均功耗≤50W/车道,可调",
"峰值光照度(基准轴)": "≤300lx",
"峰值光照度(补光区)": "应大于等于基准轴上有效光照度的 50%",
"平均光照度": "≤50lx",
"闪光频率": "≥50Hz补光装置应能与集成式高清摄像设备同步",
"有效补光距离": "≥25m",
"最大点亮时间": "≤4ms(可调节)",
"控制方式": "电平,同步触发方式",
"安全性": "在保证瞬时亮度的前提下,必须进行防炫目处理,不会造成光污染",
"使用寿命": "≥50000小时",
"防护等级": "不低于IP65",
"其它": "应配置光栅装置或遮光阻断装置"
},
"光纤收发器": {
"1路千兆以太网": "1路千兆以太网。",
"单模单纤": "单模单纤光接口为FC接口满足实际传输距离要求。",
"10/100/1000M自适应以太网接口": "10/100/1000M自适应以太网接口。",
"工作环境适应性强": "工作环境适应性强,满足全天候使用的要求。",
"耐压": "耐压≥300V。"
},
"交换机": {
"交换容量": "≥6Gbps包转发率≥7MppsMAC地址列表≥8K应具备线性转发能力",
"以太网接口": "应至少具有 8个 10/100/1000M自适应以太网接口和至少 2个 1000M光口",
"以太网光端口传输距离": "应不小于 40km以太网光模块为单芯双向光模块光口接口类型为LC型接口光模块连接单模光纤",
"支持协议": "至少支持 IEEE802.1p、IEEE802.1q、IEEE802.3、IEEE802.3u、IEEE802.3z、IEEE802.3x等协议;",
"VLAN功能": "应具有 IEEE802.1Q VLAN应实现信号控制独立传输至少支持 4个 VLAN划分",
"路由协议": "应至少支持三层动态路由协议;",
"QOS功能": "应具有 IEEE802.1p_QOS功能;",
"组播功能": "应具有 IGMP静态组播、端口聚合、端口镜像等功能",
"防护等级": "IP40以上等级防护",
"设计要求": "应采用无风扇设计;",
"网管功能": "应具有网管功能。"
},
"终端服务器": {
"结构形式": "采用嵌入式架构;",
"操作系统": "嵌入式操作系统;",
"接入路数": "提供≥12路高清摄像机视频存储、过车记录存储、图片存储、数据上传、视频流转发等",
"主机存储": "内置 SATA接口配置存储容量≥16T硬盘 ",
"网络接口": "提供≥8个 RJ45 1000M网络接口",
"其他接口": "提供 RS232、RS485、外置 USB接口、VGA接口等",
"访问操作": "支持 Web操作访问",
"接口协议": "至少支持 ONVIFGB/T28181等",
"API接口开放支持": "支持标准ONVIF协议与第三方厂家设备进行互通支持 GB/T 28181。",
"支持套牌车检测": "可将抓拍图片与本地历史数据进行车辆特征比对分析,检测出套牌车辆,同时给出告警提示"
}
]
},
"投标报价评审": {
"投标报价是否出现违反计价规范": {
"评分": "合格/不合格",
"要求": "A:投标报价未违反计价规范的评审意见为“合格”B投标报价违反计价规范的评审意见为“不合格”"
},
"交通诱导子系统": {
"交通诱导屏": {
"主要特性": "1)温度范围-40~70℃ 2)光带宽度>130mm 3)通迅接口RS485、网络通讯、3G网络 4)视角水平110°120°垂直55° 5)发光亮度6500-7000 cd/m2 6)平整度≤0.5mm 7)使用寿命≥10万小时 8)平均故障时间≥1万小时 9)电源采用 n+1高可靠容错的开关电源系统 10)显示尺寸:长 3.84米×高 2.56米=9.83平方米 11)机箱要求:冷轧钢板,机箱为内外两层内箱体为全封闭、全天候、防风雨型符合IP65防护等级 12)抗风等级40m/s",
"技术参数": "1物理点间距10mm2物理密度10000点/m23发光点颜色1R1G 4基色纯红+纯绿 5模组尺寸:320mm*160mm 6刷新频率≥800HZ 7工作电压AC220V±1050Hz三相五线制8平均功耗350W/m2 9最大功耗≤700W/ m2"
}
},
"电子警察子系统": {
"高清视频抓拍像机": {
"摄像机.有效像素": "≥900W像素",
"摄像机.最低照度": "彩色≤0.001lx",
"摄像机.传感器类型": "≥1英寸全局曝光COMS/GMOS/GS COMS",
"摄像机.电子快门": "至少满足 1/25s至 1/100,000s可调",
"视频图像.视频压缩标准": "至少支持H264、H265等",
"视频图像.视频分辨率": "≥4096×2160向下可设置",
"视频图像.视频流帧率": "≥25fps,至少双视频流",
"视频图像.图片压缩方式": "JPEG",
"视频图像.图像分辨率": "≥4096(H)×2160(V)",
"视频图像.强光抑制": "具备",
"协议.API接口开放": "具备支持标准ONVIF协议与第三方厂家设备进行互联支持GB/T28181应提供 SDK",
"接口.通讯接口": "≥1个 RJ4510M/100M/1000M自适应以太网电口≥1 个 RS-485接口",
"接口.前端存储卡": "嵌入式支持断网时本地存储裸容量≥128GB",
"功能.具备其他功能": "应具备I/O触发、RS-485触发、视频触发支持电源同步支持频闪式卜光装置和脉冲式补光装置同步补光",
"功能.具备其他功能.具备AI深度学习算法和GPU芯片": "具备AI深度学习算法和GPU芯片",
"高清工业级镜头.光圈、聚焦": "手动光圈;焦距应可根据车道宽度及抓拍距离进行调整设置,要求中心成像圆内解像力不小于 900万像素边缘解像力不低于中心 60%,镜头的成像尺寸应与摄像机成像靶面尺寸相等或略大。镜头应配置电动偏振镜,具备根据环境自动切换功能",
"防护罩.防护罩类型": "室外型防护罩,含底座;具备隔热防潮、防水、防尘、防腐、防震等功能;具有加热器、支架、遮阳罩等,具有良好的密封性和恒温等功能。",
"防护罩.防护等级": "≥IP66"
},
"补光灯": {
"配置方式": "每个车道配置≥1台补光装置必须与高清摄像设备高度匹配",
"LED管芯": "采用超高亮大功率白光LED管芯灯珠数量不少于 20颗",
"功耗": "30W/车道≤单台平均功耗≤50W/车道,可调",
"峰值光照度(基准轴)": "≤300lx",
"峰值光照度(补光区)": "应大于等于基准轴上有效光照度的 50%",
"平均光照度": "≤50lx",
"闪光频率": "≥50Hz补光装置应能与集成式高清摄像设备同步",
"有效补光距离": "≥25m",
"最大点亮时间": "≤4ms(可调节)",
"控制方式": "电平,同步触发方式",
"安全性": "在保证瞬时亮度的前提下,必须进行防炫目处理,不会造成光污染",
"使用寿命": "≥50000小时",
"防护等级": "不低于IP65",
"其它": "应配置光栅装置或遮光阻断装置"
},
"光纤收发器": {
"1路千兆以太网": "1路千兆以太网。",
"单模单纤": "单模单纤光接口为FC接口满足实际传输距离要求。",
"10/100/1000M自适应以太网接口": "10/100/1000M自适应以太网接口。",
"工作环境适应性强": "工作环境适应性强,满足全天候使用的要求。",
"耐压": "耐压≥300V。"
},
"交换机": {
"交换容量": "≥6Gbps包转发率≥7MppsMAC地址列表≥8K应具备线性转发能力",
"以太网接口": "应至少具有 8个 10/100/1000M自适应以太网接口和至少 2个 1000M光口",
"以太网光端口传输距离": "应不小于 40km以太网光模块为单芯双向光模块光口接口类型为LC型接口光模块连接单模光纤",
"支持协议": "至少支持 IEEE802.1p、IEEE802.1q、IEEE802.3、IEEE802.3u、IEEE802.3z、IEEE802.3x等协议;",
"VLAN功能": "应具有 IEEE802.1Q VLAN应实现信号控制独立传输至少支持 4个 VLAN划分",
"动态路由协议": "应至少支持三层动态路由协议;",
"QOS功能": "应具有 IEEE802.1p_QOS功能;",
"组播功能": "应具有 IGMP静态组播、端口聚合、端口镜像等功能",
"防护等级": "IP40以上等级防护",
"设计要求": "应采用无风扇设计;",
"网管功能": "应具有网管功能。"
},
"终端服务器": {
"结构形式": "采用嵌入式架构;",
"操作系统": "嵌入式操作系统;",
"接入路数": "提供≥12路高清摄像机视频存储、过车记录存储、图片存储、数据上传、视频流转发等",
"主机存储": "内置 SATA接口配置存储容量≥16T硬盘 ",
"网络接口": "提供≥8个 RJ45 1000M网络接口",
"其他接口": "提供 RS232、RS485、外置 USB接口、VGA接口等",
"访问操作": "支持 Web操作访问",
"接口协议": "至少支持 ONVIFGB/T28181等",
"API接口开放支持": "支持标准ONVIF协议与第三方厂家设备进行互通支持 GB/T 28181。",
"支持套牌车检测": "可将抓拍图片与本地历史数据进行车辆特征比对分析,检测出套牌车辆,同时给出告警提示"
}
},
"交通卡口子系统": {
"高清视频抓拍像机": {
"摄像机.有效像素": "≥900W像素",
"摄像机.最低照度": "彩色≤0.001lx",
"摄像机.传感器类型": "≥1英寸全局曝光COMS/GMOS/GS COMS",
"摄像机.电子快门": "至少满足 1/25s至 1/100,000s可调",
"视频图像.视频压缩标准": "至少支持H264、H265等",
"视频图像.视频分辨率": "≥4096×2160向下可设置",
"视频图像.视频流帧率": "≥25fps,至少双视频流",
"视频图像.图片压缩方式": "JPEG",
"视频图像.图像分辨率": "≥4096(H)×2160(V)",
"视频图像.强光抑制": "具备",
"协议.API接口开放": "具备支持标准ONVIF协议与第三方厂家设备进行互联支持GB/T28181应提供 SDK",
"接口.通讯接口": "≥1个 RJ4510M/100M/1000M自适应以太网电口≥1 个 RS-485接口",
"接口.前端存储卡": "嵌入式支持断网时本地存储裸容量≥128GB",
"功能.具备其他功能": "应具备I/O触发、RS-485触发、视频触发支持电源同步支持频闪式卜光装置和脉冲式补光装置同步补光",
"功能.具备其他功能.1": "具备AI深度学习算法和GPU芯片",
"高清工业级镜头.光圈、聚焦": "手动光圈;焦距应可根据车道宽度及抓拍距离进行调整设置,要求中心成像圆内解像力不小于 900万像素边缘解像力不低于中心 60%,镜头的成像尺寸应与摄像机成像靶面尺寸相等或略大。镜头应配置电动偏振镜,具备根据环境自动切换功能",
"防护罩.防护罩类型": "室外型防护罩,含底座;具备隔热防潮、防水、防尘、防腐、防震等功能;具有加热器、支架、遮阳罩等,具有良好的密封性和恒温等功能。",
"防护罩.防护等级": "≥IP66"
},
"补光灯": {
"配置方式": "每个车道配置≥1台补光装置必须与高清摄像设备高度匹配",
"LED管芯": "采用超高亮大功率白光LED管芯灯珠数量不少于 20颗",
"功耗": "30W/车道≤单台平均功耗≤50W/车道,可调",
"峰值光照度(基准轴)": "≤300lx",
"峰值光照度(补光区)": "应大于等于基准轴上有效光照度的 50%",
"平均光照度": "≤50lx",
"闪光频率": "≥50Hz补光装置应能与集成式高清摄像设备同步",
"有效补光距离": "≥25m",
"最大点亮时间": "≤4ms(可调节)",
"控制方式": "电平,同步触发方式",
"安全性": "在保证瞬时亮度的前提下,必须进行防炫目处理,不会造成光污染",
"使用寿命": "≥50000小时",
"防护等级": "不低于IP65",
"其它": "应配置光栅装置或遮光阻断装置"
},
"光纤收发器": {
"1路千兆以太网": "1路千兆以太网。",
"单模单纤": "单模单纤光接口为FC接口满足实际传输距离要求。",
"10/100/1000M自适应以太网接口": "10/100/1000M自适应以太网接口。",
"工作环境适应性强": "工作环境适应性强,满足全天候使用的要求。",
"耐压": "耐压≥300V。"
},
"交换机": {
"交换容量": "≥6Gbps包转发率≥7MppsMAC地址列表≥8K应具备线性转发能力",
"以太网接口": "应至少具有 8个 10/100/1000M自适应以太网接口和至少 2个 1000M光口",
"以太网光端口传输距离": "应不小于 40km以太网光模块为单芯双向光模块光口接口类型为LC型接口光模块连接单模光纤",
"支持协议": "至少支持 IEEE802.1p、IEEE802.1q、IEEE802.3、IEEE802.3u、IEEE802.3z、IEEE802.3x等协议;",
"VLAN功能": "应具有 IEEE802.1Q VLAN应实现信号控制独立传输至少支持 4个 VLAN划分",
"路由协议": "应至少支持三层动态路由协议;",
"QOS功能": "应具有 IEEE802.1p_QOS功能;",
"组播功能": "应具有 IGMP静态组播、端口聚合、端口镜像等功能",
"防护等级": "IP40以上等级防护",
"设计要求": "应采用无风扇设计;",
"网管功能": "应具有网管功能。"
},
"终端服务器": {
"结构形式": "采用嵌入式架构;",
"操作系统": "嵌入式操作系统;",
"接入路数": "提供≥12路高清摄像机视频存储、过车记录存储、图片存储、数据上传、视频流转发等",
"主机存储": "内置 SATA接口配置存储容量≥16T硬盘 ",
"网络接口": "提供≥8个 RJ45 1000M网络接口",
"其他接口": "提供 RS232、RS485、外置 USB接口、VGA接口等",
"访问操作": "支持 Web操作访问",
"接口协议": "至少支持 ONVIFGB/T28181等",
"API接口开放支持": "支持标准ONVIF协议与第三方厂家设备进行互通支持 GB/T 28181。",
"支持套牌车检测": "可将抓拍图片与本地历史数据进行车辆特征比对分析,检测出套牌车辆,同时给出告警提示"
}
},
"区间测速子系统": {
"高清视频抓拍像机": {
"1.摄像机.有效像素": "≥900W像素",
"1.摄像机.最低照度": "彩色≤0.001lx",
"1.摄像机.传感器类型": "≥1英寸全局曝光COMS/GMOS/GS COMS",
"1.摄像机.电子快门": "至少满足 1/25s至 1/100,000s可调",
"2.视频图像.视频压缩标准": "至少支持H264、H265等",
"2.视频图像.视频分辨率": "≥4096×2160向下可设置",
"2.视频图像.视频流帧率": "≥25fps,至少双视频流",
"3.视频图像.图片压缩方式": "JPEG",
"3.视频图像.图像分辨率": "≥4096(H)×2160(V)",
"3.视频图像.强光抑制": "具备",
"4.协议.API接口开放": "具备支持标准ONVIF协议与第三方厂家设备进行互联支持GB/T28181应提供 SDK",
"5.接口.通讯接口": "≥1个 RJ4510M/100M/1000M自适应以太网电口≥1 个 RS-485接口",
"5.接口.前端存储卡": "嵌入式支持断网时本地存储裸容量≥128GB",
"5.功能.具备其他功能": "具备AI深度学习算法和GPU芯片",
"6.高清工业级镜头.光圈、聚焦": "手动光圈;焦距应可根据车道宽度及抓拍距离进行调整设置,要求中心成像圆内解像力不小于 900万像素边缘解像力不低于中心 60%,镜头的成像尺寸应与摄像机成像靶面尺寸相等或略大。镜头应配置电动偏振镜,具备根据环境自动切换功能",
"7.防护罩.防护罩类型": "室外型防护罩,含底座;具备隔热防潮、防水、防尘、防腐、防震等功能;具有加热器、支架、遮阳罩等,具有良好的密封性和恒温等功能。",
"7.防护罩.防护等级": "≥IP66"
},
"补光灯": {
"配置方式": "每个车道配置≥1台补光装置必须与高清摄像设备高度匹配",
"LED管芯": "采用超高亮大功率白光LED管芯灯珠数量不少于 20颗",
"功耗": "30W/车道≤单台平均功耗≤50W/车道,可调",
"峰值光照度(基准轴)": "≤300lx",
"峰值光照度(补光区)": "应大于等于基准轴上有效光照度的 50%",
"平均光照度": "≤50lx",
"闪光频率": "≥50Hz补光装置应能与集成式高清摄像设备同步",
"有效补光距离": "≥25m",
"最大点亮时间": "≤4ms(可调节)",
"控制方式": "电平,同步触发方式",
"安全性": "在保证瞬时亮度的前提下,必须进行防炫目处理,不会造成光污染",
"使用寿命": "≥50000小时",
"防护等级": "不低于IP65",
"其它": "应配置光栅装置或遮光阻断装置"
},
"光纤收发器": {
"1路千兆以太网": "1路千兆以太网。",
"单模单纤": "单模单纤光接口为FC接口满足实际传输距离要求。",
"10/100/1000M自适应以太网接口": "10/100/1000M自适应以太网接口。",
"工作环境适应性强": "工作环境适应性强,满足全天候使用的要求。",
"耐压": "耐压≥300V。"
},
"交换机": {
"交换容量": "≥6Gbps",
"包转发率": "≥7Mpps",
"MAC地址列表": "≥8K",
"线性转发能力": "应具备线性转发能力",
"以太网接口": "应至少具有 8个 10/100/1000M自适应以太网接口和至少 2个 1000M光口",
"以太网光端口传输距离": "应不小于 40km以太网光模块为单芯双向光模块光口接口类型为LC型接口光模块连接单模光纤",
"支持协议": "至少支持 IEEE802.1p、IEEE802.1q、IEEE802.3、IEEE802.3u、IEEE802.3z、IEEE802.3x等协议",
"VLAN": "应具有 IEEE802.1Q VLAN应实现信号控制独立传输至少支持 4个 VLAN划分",
"三层动态路由协议": "应至少支持三层动态路由协议",
"QOS功能": "应具有 IEEE802.1p_QOS功能",
"IGMP静态组播": "应具有 IGMP静态组播、端口聚合、端口镜像等功能",
"防护等级": "IP40以上等级防护",
"无风扇设计": "应采用无风扇设计",
"网管功能": "应具有网管功能"
},
"终端服务器": {
"结构形式": "采用嵌入式架构;",
"操作系统": "嵌入式操作系统;",
"接入路数": "提供≥8路高清摄像机视频存储、过车记录存储、图片存储、数据上传、视频流转发等",
"主机存储": "内置 SATA接口配置存储容量≥16T硬盘 ",
"网络接口": "提供≥8个 RJ45 1000M网络接口",
"其他接口": "提供 RS232、RS485、外置 USB接口、VGA接口等",
"访问操作": "支持 Web操作访问",
"接口协议": "至少支持 ONVIFGB/T28181等",
"API接口开放支持": "支持标准ONVIF协议与第三方厂家设备进行互通支持 GB/T 28181。"
}
},
"后台接入服务器": {
"1、机架式服务器": "1、机架式服务器",
"2、处理器": "2、处理器至少配置 2颗单颗 10核 CPU,单核 CPU主频≥2.0GHz",
"3、内存": "3、内存配置 128GB DDR4内存",
"4、硬盘": "4、硬盘配置 6块 1.2T SAS硬盘,转速≥10Krpm",
"5、RAID": "5、RAID配置 RAID控制器支持 RAID0、1、10、1E、5、50、60等",
"6、网络": "6、网络配置双千兆网卡",
"7、电源": "7、电源双电源"
},
"存储服务器": {
"1": "4U 48盘位磁盘阵列",
"2": "单设备配置 64位多核处理器",
"3": "16GB缓存",
"4": "冗余电源;",
"5": "支持 SATA硬盘",
"6": "2个千兆网口",
"7": "1个系统 SSD盘",
"8": "支持视音频、图片、直接写入支持视频高速预览、回放、下载支持云内容灾备份支持一体化运维支持GB/T28181-2011、Onvif、RTSP、H265、SVAC等标准视频协议。"
},
"硬盘": {
"容量": "4T",
"尺寸": "3.5寸",
"接口": "SATA"
},
"室外挂箱": {
"安装方式": "采用悬挂式安装方式,悬挂于杆件立柱,高度应确保机箱下边缘距离地面净高 2.5 米以上。设备机箱安装后不得侵入机动车道建筑界限以内,不得影响车辆正常通行。设备机箱应安装牢固;",
"表面处理": "设备机箱表面应经过考漆处理,应具备防锈蚀、防盐雾、防霉菌能力;",
"内部空间": "机箱内部空间应足够大,能确保设备、装置的合理摆放,设有存放用户手册、说明书、接线图、维修记录等资料的存储盒,并有适当空间预留。机箱空间应有利于机箱内各设备单元的散热、安装、使用和维修,同时应提供设备辅助散热措施,提高系统环境适应能力;",
"防护等级": "设备机箱的结构应能防雨并能降低灰尘及有害物质的侵入机箱门盖应有溢水槽机箱门内侧应配备密封条机箱顶部应具有防积水措施。机箱防护等级应达到IP55以上",
"机械强度": "机箱结构应具有足够的机械强度,应能承受正常条件下可预料到的运输、安装、搬运、维护等过程中的操作;",
"门的设计": "机箱门的最大开启角度应大于 120°。机箱门锁应采用保险柜天地锁式的结构设计防止被非法打开门锁至少可对上、下及左右侧中的一侧进行缩栓式保护应具备较强的设备防砸、防盗能力。机箱应具有防盗报警功能机箱在非正常状态下开启时能够报警提示。机箱门接缝处有耐久且有弹性的密封垫密封垫连续设置无间断接口。机柜门锁上后无松动、变形现象",
"电源保护": "机箱内设应置有具备稳压、过载、漏电、短路保护功能的电源开关和防雷保护功能的电源浪涌保护器。在熔断器和电源开关等处应有警告标志。机箱内合适位置配备接地铜排,接地铜排的截面应不小于 100 mm2接地端子应进行防腐处理。并应设置接地标志接地铜排应保证良好接地接地线截面积应不小于 16mm2",
"电源插座": "机箱内应配备不少于 2路单相 2孔扁圆电源插座、2路单相 3孔扁圆电源插座",
"电源开关": "应具有稳压、短路、过载、漏电保护;电源保护响应时间应为纳秒级;开关的额定电压、额定电流值应满足设备正常运行的要求;机械寿命应不少于 20000次具有良好的散热性能。",
"额外要求": "室外大机箱需满足上述使用外,还应可安装前端管理主机。"
},
"交通管道": {
"过街管道": "主要垂直于道路中线埋设,可开挖路段采用 DN90镀锌钢管不宜开挖路段根据现场情况采用拖拉管DN90 PE管。路口必须在各方向埋设双管。",
"非过街管道": "采用 PE双壁波纹管。",
"干线(纵向)路段": "两接线井之间最大距 50m。",
"管道埋设深度": "机动车道和非机动车道下管道埋设深度管顶至路面应≥0.7m人行道及绿化带下管道埋设深度管顶至路面应≥0.5m。如不能达到上述埋设深度要求应采取混凝土包封或采用钢管等保护措施。",
"管道敷设的坡度": "应为 0.3%-0.4%,不得小于 0.25%",
"其他敷设要求": "应参照《市政公用工程细部构造做法(湖北省)》中《交通管道预埋断面图》做法。",
"交通管道开挖后": "应按原有道路或绿化进行恢复,管线尽量敷设于人行道上,不影响后续的绿化园林施工。"
}
}
},
"商务要求": "",
"服务要求": "",
"其他要求": ""
},
"关键时间/内容": {
"投标文件递交截止日期": "2021年月日点分北京时间",
"投标文件递交地点": "广水市公共资源交易中心五楼号开标室",
"开标时间": "未知",
"开标地点": "广水市公共资源交易中心五楼号开标室",
"澄清招标文件的截止时间": "未知",
"投标有效期": "提交投标文件截止之日起 60日历日",
"信息公示媒介": [
{
"名称": "中国湖北政府采购网",
"网址": "http://www.ccgp-hubei.gov.cn/"
},
{
"名称": "中国广水网",
"网址": "http://www.zggsw.gov.cn/"
}
]
},
"保证金相关": {
"是否提交履约保证金": "未知",
"质量保证金": "未知"
},
"其他信息": {
"投标费用承担": "投标人应承担所有与准备和参加投标有关的费用。不论投标的结果如何,分散采购机构和采购人均无义务和责任承担这些费用。",
"招标代理服务费": {
"收费标准": "服务费按国家规定收费标准按国家发展改革委关于印发《招标代理服务收费管理暂行办法》的通知(计价格[2002]1980 号)执行。",
"支付方": "中标方",
"支付时间": "政府采购代理缴纳中标服务费"
},
"是否退还投标文件": "",
"投标预备会": "不召开",
"偏离": {
"商务要求响应、偏离说明表": "第七章投标文件格式(参考)附件十二",
"商务要求“★”号条款响应、偏离说明表": "第七章投标文件格式(参考)附件十三",
"技术、服务要求响应、偏离说明表": "第七章投标文件格式(参考)附件十五",
"技术、服务要求“★”号条款响应、偏离说明表": "第七章投标文件格式(参考)附件十六",
"偏离项的具体要求": "未知"
},
"踏勘现场": "不组织",
"货物列表": {}
}
}
"""
def reorganize_data(input_dict, include=None):
"""
重组输入字典技术评分商务评分提升为最外层键
并将包含在 include 列表中的包名的数据嵌套在相应的评分类别下
如果 input_dict 的顶层键不包含任何 include 列表中的项则返回原始字典
:param input_dict: 原始输入字典
:param include: 包名列表例如 ['一包', '二包', '三包']
:return: 重组后的字典
"""
if include is None:
include = []
# 检查是否有任何顶层键包含在 include 列表中
has_include = any(key in include for key in input_dict.keys())
if not has_include:
# 没有包含任何指定的包名,直接返回原始字典
return input_dict
# 初始化新的字典结构
reorganized = {
"技术评分": {},
"商务评分": {}
}
# 遍历每一个包(如 "一包", "二包"
for package, categories in input_dict.items():
# 处理技术评分
if "技术评分" in categories:
reorganized["技术评分"][package] = categories["技术评分"]
# 处理商务评分
if "商务评分" in categories:
reorganized["商务评分"][package] = categories["商务评分"]
return reorganized
include = ['一包', '二包', '三包', '四包', '五包']
target_values = ['技术', '设计', '实施']
cleaned_evaluation_res = parse_json_with_duplicates(raw_data) #处理重复键名的情况
result_data = process_data_based_on_key(cleaned_evaluation_res)
updated_jsons = {}
if any(key for key in result_data if any(included in key for included in include)):
# 有匹配的项,处理这些项
for key in result_data:
if any(item in key for item in include):
inner_dict = result_data[key]
updated_jsons[key] = combine_technical_and_business(inner_dict, target_values)
else:
# 没有匹配的项,对整个字典运行
updated_jsons = combine_technical_and_business(result_data, target_values)
res=reorganize_data(updated_jsons,include)
print(json.dumps(res, ensure_ascii=False, indent=4))
}
res1,res2=post_process_baseinfo(data)
print(res1)

View File

@ -1,9 +1,18 @@
import logging
from PyPDF2 import PdfReader, PdfWriter
import re # 导入正则表达式库
import os # 用于文件和文件夹操作
from flask_app.general.format_change import docx2pdf
from flask_app.general.merge_pdfs import merge_and_cleanup,merge_pdfs
import concurrent.futures
def get_global_logger(unique_id):
if unique_id is None:
return logging.getLogger() # 获取默认的日志器
logger = logging.getLogger(unique_id)
return logger
logger = None
def clean_page_content(text, common_header):
# 首先删除抬头公共部分
if common_header: # 确保有公共抬头才进行替换
@ -179,26 +188,27 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter
common_header = extract_common_header(pdf_path)
pdf_document = PdfReader(pdf_path)
exclusion_pattern = None
total_pages = len(pdf_document.pages)-1 # 获取总页数
total_pages = len(pdf_document.pages) - 1 # 获取总页数
if output_suffix == "tobidders_notice":
exclusion_pattern = re.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据')
start_page, mid_page, end_page = extract_pages_tobidders_notice(pdf_document, begin_pattern, end_pattern,
begin_page, common_header,
exclusion_pattern)
if start_page is None or mid_page is None or end_page is None:
start_page, mid_page, end_page = extract_pages_tobidders_notice(
pdf_document, begin_pattern, begin_page, common_header, exclusion_pattern
)
if start_page is None or end_page is None or mid_page is None:
print(f"first: {output_suffix} 未找到起始或结束页在文件 {pdf_path} 中!尝试备用提取策略。")
return extract_pages_twice_tobidders_notice(pdf_path, output_folder, output_suffix, common_header)
path1 = save_extracted_pages(pdf_document, start_page, mid_page, pdf_path, output_folder,
"tobidders_notice_part1")
path2 = save_extracted_pages(pdf_document, mid_page, end_page, pdf_path, output_folder,
"tobidders_notice_part2")
path1 = save_extracted_pages(pdf_document, start_page, mid_page, pdf_path, output_folder, "tobidders_notice_part1")
path2 = save_extracted_pages(pdf_document, mid_page, end_page, pdf_path, output_folder, "tobidders_notice_part2")
return path1, path2
else:
# 原有的处理逻辑保持不变
if output_suffix == "qualification1":
exclusion_pattern = re.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据')
start_page, end_page = extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page,
common_header, exclusion_pattern, output_suffix)
start_page, end_page = extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, common_header, exclusion_pattern, output_suffix)
# 针对 selection = 6 的特殊处理
if output_suffix == "format":
if start_page is None:
@ -213,12 +223,13 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter
print(f"first: {output_suffix} 未找到起始或结束页在文件 {pdf_path} 中!尝试备用提取策略。")
return extract_pages_twice(pdf_path, output_folder, output_suffix, common_header)
elif output_suffix == "qualification1":
truncate_pdf_main(pdf_path, output_folder, 2, "qualification3") #合并'资格审查'章节和'评标办法'章节
truncate_pdf_main(pdf_path, output_folder, 2, "qualification3") # 合并'资格审查'章节和'评标办法'章节
return save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix)
except Exception as e:
print(f"Error processing {pdf_path}: {e}")
return ""
def get_patterns_for_procurement():
begin_pattern = re.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:服务|项目|商务).*?要求|'
@ -294,46 +305,105 @@ def get_patterns_for_notice():
# break
# return start_page, mid_page, end_page
def extract_pages_tobidders_notice(pdf_document, begin_pattern, end_pattern, begin_page, common_header,
exclusion_pattern):
def run_extraction(use_multiline=False):
def extract_pages_tobidders_notice(pdf_document, begin_pattern, begin_page, common_header, exclusion_pattern):
def run_extraction():
start_page = None
mid_page = None
end_page = None
chapter_type = None # 用于存储“章”或“部分”
for i, page in enumerate(pdf_document.pages):
text = page.extract_text() or ""
cleaned_text = clean_page_content(text, common_header)
# print(cleaned_text)
if exclusion_pattern and re.search(exclusion_pattern, cleaned_text) and mid_page is not None:
continue
if start_page is None and re.search(begin_pattern, cleaned_text) and i > begin_page:
start_page = i
if start_page is not None and mid_page is None:
mid_pattern = r'^\s*[(]?\s*[一1]\s*[)]?\s*[、..]*\s*(说\s*明|总\s*则)'
flags = re.MULTILINE if use_multiline else 0
if re.search(mid_pattern, cleaned_text, flags):
if start_page is None:
match = re.search(begin_pattern, cleaned_text)
if match and i > begin_page:
start_page = i
matched_text = match.group(0) # 获取整个匹配的文本
if '' in matched_text:
chapter_type = ''
elif '部分' in matched_text:
chapter_type = '部分'
else:
chapter_type = None # 未匹配到“章”或“部分”
if chapter_type:
# 根据 chapter_type 动态生成 end_pattern
end_pattern = re.compile(
rf'^第[一二三四五六七八九十百千]+?(?:{chapter_type})\s*[\u4e00-\u9fff]+',
re.MULTILINE
)
# print(f"动态生成的 end_pattern: {end_pattern.pattern}") # 打印生成的 end_pattern
# 根据 chapter_type 动态生成 additional_mid_pattern
if chapter_type == '':
additional_mid_pattern = r'^第[一二三四五六七八九十百千]+?(?:部分)'
elif chapter_type == '部分':
additional_mid_pattern = r'^第[一二三四五六七八九十百千]+?(?:章)'
else:
additional_mid_pattern = ''
# 定义基础的 mid_pattern
base_mid_pattern = r'^\s*(?:[(]\s*[一1]?\s*[)]\s*[、..]*|[一1][、..]+|[、..]+)\s*(说\s*明|总\s*则)'
# 合并基础模式和额外模式
if additional_mid_pattern:
combined_mid_pattern = re.compile(
rf'(?:{base_mid_pattern})|(?:{additional_mid_pattern})',
re.MULTILINE
)
else:
combined_mid_pattern = re.compile(
rf'{base_mid_pattern}',
re.MULTILINE
)
# print(f"生成的 combined_mid_pattern: {combined_mid_pattern.pattern}") # 打印 combined_mid_pattern
else:
# 如果未匹配到“章”或“部分”,使用默认的 end_pattern 和 mid_pattern
end_pattern = re.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+',
re.MULTILINE
)
print(f"使用默认的 end_pattern: {end_pattern.pattern}") # 打印默认的 end_pattern
# 定义基础的 mid_pattern
base_mid_pattern = r'^\s*(?:[(]\s*[一1]?\s*[)]\s*[、..]*|[一1][、..]+|[、..]+)\s*(说\s*明|总\s*则)'
combined_mid_pattern = re.compile(
rf'{base_mid_pattern}',
re.MULTILINE
)
print(
f"使用默认的 combined_mid_pattern: {combined_mid_pattern.pattern}") # 打印默认的 combined_mid_pattern
continue
if start_page is not None and mid_page is None and combined_mid_pattern:
if re.search(combined_mid_pattern, cleaned_text):
mid_page = i
if start_page is not None and re.search(end_pattern, cleaned_text):
if mid_page is None:
if i > start_page:
end_page = i
break
else:
if i > mid_page:
end_page = i
break
if start_page is not None and mid_page is not None and chapter_type:
if re.search(end_pattern, cleaned_text):
if mid_page is None:
if i > start_page:
end_page = i
break
else:
if i > mid_page:
end_page = i
break
return start_page, mid_page, end_page
# 第一次运行
# 运行提取
start_page, mid_page, end_page = run_extraction()
# 如果有任何一个值为 None使用 re.MULTILINE 重新运行
if start_page is None or mid_page is None or end_page is None:
start_page, mid_page, end_page = run_extraction(use_multiline=True)
return start_page, mid_page, end_page
def extract_pages_twice_tobidders_notice(pdf_path, output_folder, output_suffix, common_header):
begin_pattern = re.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:(?:投标人?|磋商|供应商|谈判供应商|磋商供应商)须知)+'
@ -341,7 +411,7 @@ def extract_pages_twice_tobidders_notice(pdf_path, output_folder, output_suffix,
end_pattern = re.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*([\u4e00-\u9fff]+)' # 捕获中文部分
)
exclusion_words = ["合同", "评标", "开标"] # 在这里添加需要排除的关键词
exclusion_words = ["合同", "评标", "开标","评审","采购","资格"] # 在这里添加需要排除的关键词
pdf_document = PdfReader(pdf_path)
exclusion_pattern = re.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据')
@ -453,6 +523,11 @@ def extract_pages_twice(pdf_path, output_folder, output_suffix, common_header):
def save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix):
try:
# 检查 start_page 和 end_page 是否为 None
if start_page is None or end_page is None:
print("Error: start_page 或 end_page 为 None")
return ""
base_file_name = os.path.splitext(os.path.basename(pdf_path))[0]
output_pdf_path = os.path.join(output_folder, f"{base_file_name}_{output_suffix}.pdf")
@ -492,16 +567,20 @@ def merge_selected_pdfs(output_folder, truncate_files, output_path, base_file_na
- truncate_files (list): 包含 PDF 文件路径的列表
- output_path (str): 合并后的 PDF 文件保存路径
- base_file_name (str): 用于匹配文件名的基础名称
- logger (logging.Logger): 日志记录器对象
返回:
- str: 如果合并成功返回 output_path否则返回空字符串 ""
"""
# 1. 获取 output_folder 中所有文件
try:
all_output_files = os.listdir(output_folder)
except FileNotFoundError:
print(f"输出文件夹 '{output_folder}' 未找到。")
return
return ""
except PermissionError:
print(f"没有权限访问输出文件夹 '{output_folder}'")
return
return ""
# 2. 定义要选择的文件后缀及合并顺序,包括 before 文件
desired_suffixes = [
@ -538,26 +617,31 @@ def merge_selected_pdfs(output_folder, truncate_files, output_path, base_file_na
if not all_pdfs_to_merge:
print("没有找到要合并的 PDF 文件。")
return
return ""
# 调用 merge_pdfs 函数进行合并
merge_pdfs(all_pdfs_to_merge, output_path)
print(f"已成功合并 PDF 文件到 '{output_path}'")
# 检查合并后的文件是否存在且不为空
if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
return output_path
else:
print(f"合并失败,没有生成 '{output_path}'")
return ""
def truncate_pdf_main(input_path, output_folder, selection, output_suffix="default"):
try:
if selection == 1:
# 更新的正则表达式以匹配"第x章"和"第x部分",考虑到可能的空格和其他文字
if selection == 1: #招标公告
begin_page = 0
begin_pattern = re.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:服务|项目|商务).*?要求|'
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?采购.*|'
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?需求.*'
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:公告|邀请书|邀请函).*'
)
begin_page = 3
end_pattern = re.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+'
# r'^(?:第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:投标人|磋商|供应商|谈判供应商|磋商供应商)须知+|(?:一\s*、\s*)?(?:投标人|磋商|供应商)须知前附表)'
r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE
)
local_output_suffix = "procurement"
local_output_suffix = "notice"
elif selection == 2:
begin_pattern = re.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(磋商|谈判|评标|评定|评审)(方法|办法).*'
@ -582,20 +666,23 @@ def truncate_pdf_main(input_path, output_folder, selection, output_suffix="defau
r'^(?:第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:投标人?|磋商|供应商|谈判供应商|磋商供应商)须知+|(?:一\s*、\s*)?(?:投标人?|磋商|供应商)须知前附表)',
re.MULTILINE
)
end_pattern = re.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE
)
end_pattern=None
# end_pattern = re.compile(
# r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE
# )
local_output_suffix = "tobidders_notice"
elif selection == 5: # 招标公告
begin_page = 0
elif selection == 5: #采购需求
# 更新的正则表达式以匹配"第x章"和"第x部分",考虑到可能的空格和其他文字
begin_pattern = re.compile(
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:公告|邀请书|邀请函).*'
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:服务|项目|商务).*?要求|'
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?采购.*|'
r'^第[一二三四五六七八九十百千]+(?:章|部分).*?需求.*'
)
begin_page = 3
end_pattern = re.compile(
# r'^(?:第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:投标人|磋商|供应商|谈判供应商|磋商供应商)须知+|(?:一\s*、\s*)?(?:投标人|磋商|供应商)须知前附表)'
r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+',re.MULTILINE
r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+'
)
local_output_suffix = "notice"
local_output_suffix = "procurement"
elif selection==6: #投标文件格式
begin_page=5
begin_pattern=re.compile(
@ -607,7 +694,7 @@ def truncate_pdf_main(input_path, output_folder, selection, output_suffix="defau
local_output_suffix = "format"
else:
print("无效的选择:请选择1-5")
return None
return ['']
# 如果传入的 output_suffix 是 'default',则使用本地生成的 output_suffix
if output_suffix == "default":
@ -617,10 +704,12 @@ def truncate_pdf_main(input_path, output_folder, selection, output_suffix="defau
return process_input(input_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix) or ""
except Exception as e:
print(f"Error in truncate_pdf_main: {e}")
return "" # 返回空字符串
return [''] # 返回空字符串
def truncate_pdf_multiple(pdf_path, output_folder):
def truncate_pdf_multiple(pdf_path, output_folder,unique_id="123"):
global logger
logger = get_global_logger(unique_id)
base_file_name = os.path.splitext(os.path.basename(pdf_path))[0]
truncate_files = []
@ -642,20 +731,24 @@ def truncate_pdf_multiple(pdf_path, output_folder):
if files:
truncate_files.extend(files)
except Exception as e:
print(f"Selection {selection} 生成了一个异常: {e}")
logger.error(f"Selection {selection} 生成了一个异常: {e}")
if truncate_files:
merged_output_path = os.path.join(output_folder, f"{base_file_name}_merged_baseinfo.pdf")
merge_selected_pdfs(output_folder, truncate_files, merged_output_path, base_file_name)
truncate_files.append(merged_output_path)
print(f"已生成合并文件: {merged_output_path}")
# 定义合并后的输出路径
merged_output_path = os.path.join(output_folder, f"{base_file_name}_merged_baseinfo.pdf")
# 调用 merge_selected_pdfs 并获取返回值
merged_path = merge_selected_pdfs(output_folder, truncate_files, merged_output_path, base_file_name)
if merged_path:
# 合并成功,添加合并后的文件路径
truncate_files.append(merged_path)
logger.info(f"已生成合并文件: {merged_output_path}")
else:
print(f"没有文件需要合并 for {pdf_path}")
# 合并失败,添加空字符串
truncate_files.append("")
logger.warning(f"合并失败,没有生成合并文件 for {pdf_path}")
return truncate_files
#小解析,只需要前三章内容
def truncate_pdf_specific_goods(pdf_path, output_folder, selections):
def truncate_pdf_specific_goods(pdf_path, output_folder, selections,unique_id="123"):
"""
处理 PDF 文件选择指定的 selections并合并结果
@ -667,6 +760,8 @@ def truncate_pdf_specific_goods(pdf_path, output_folder, selections):
Returns:
list: 截取的文件路径列表包括合并后的文件路径如果有
"""
global logger
logger = get_global_logger(unique_id)
base_file_name = os.path.splitext(os.path.basename(pdf_path))[0]
truncate_files = []
@ -686,32 +781,37 @@ def truncate_pdf_specific_goods(pdf_path, output_folder, selections):
elif isinstance(files, str):
truncate_files.append(files)
except Exception as e:
print(f"Selection {selection} 生成了一个异常: {e}")
logger.error(f"Selection {selection} 生成了一个异常: {e}")
if truncate_files:
merged_output_path = os.path.join(output_folder, f"{base_file_name}_merged_specific.pdf")
merge_selected_pdfs(output_folder, truncate_files, merged_output_path, base_file_name)
truncate_files.append(merged_output_path)
print(f"已生成合并文件: {merged_output_path}")
# 定义合并后的输出路径
merged_output_path = os.path.join(output_folder, f"{base_file_name}_merged_baseinfo.pdf")
# 调用 merge_selected_pdfs 并获取返回值
merged_path = merge_selected_pdfs(output_folder, truncate_files, merged_output_path, base_file_name)
if merged_path:
# 合并成功,添加合并后的文件路径
truncate_files.append(merged_path)
logger.info(f"已生成合并文件: {merged_output_path}")
else:
print(f"没有文件需要合并 for {pdf_path}")
# 合并失败,添加空字符串
truncate_files.append("")
logger.warning(f"合并失败,没有生成合并文件 for {pdf_path}")
return truncate_files
# TODO:交通智能系统和招标(1)(1)文件有问题 包头 绍兴 资格审查文件可能不需要默认与"evaluation"同一章 无效投标可能也要考虑 “more”的情况类似工程标
if __name__ == "__main__":
input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\招标文件(广水市教育局封闭管理项目二次).pdf"
# input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\zbtest4_evaluation_method.pdf"
# input_path = "C:\\Users\\Administrator\\Desktop\\fsdownload\\b151fcd0-4cd8-49b4-8de3-964057a9e653\\ztbfile.pdf"
# input_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles"
input_path="C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles"
# input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\output1\\2-招标文件_procurement.pdf"
# input_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\a091d107-805d-4e28-b8b2-0c7327737238\\ztbfile.pdf"
# output_folder = "C:\\Users\\Administrator\\Desktop\\fsdownload\\a091d107-805d-4e28-b8b2-0c7327737238\\tmp"
output_folder="C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\新建文件夹"
# files = truncate_pdf_multiple(input_path, output_folder)
selections = [4, 5]
files=truncate_pdf_specific_goods(input_path,output_folder,selections)
print(files)
# selection = 6# 例如1 - 商务技术服务要求, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2与评标办法一致 4.投标人须知前附表part1 投标人须知正文part2 5-公告
# generated_files = truncate_pdf_main(input_path, output_folder, selection)
# selections = [1,4]
# files=truncate_pdf_specific_goods(input_path,output_folder,selections)
# print(files)
selection = 4# 例如1 - 商务技术服务要求, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2与评标办法一致 4.投标人须知前附表part1 投标人须知正文part2 5-公告
generated_files = truncate_pdf_main(input_path, output_folder, selection)
# print(generated_files)

View File

@ -54,11 +54,11 @@ def preprocess_files(output_folder, file_path, file_type):
# 处理各个部分
invalid_docpath = docx_path # docx截取无效标部分
procurement_path = truncate_files[0] # 商务技术服务要求
procurement_path = truncate_files[5] # 商务技术服务要求
evaluation_method_path = truncate_files[1] # 评标办法
qualification_path = truncate_files[2] # 资格审查
tobidders_notice_path = truncate_files[4] # 投标人须知正文
notice_path = truncate_files[5]
notice_path = truncate_files[0] #招标公告
merged_baseinfo_path = truncate_files[6] # 合并封面+招标公告+投标人须知前附表+须知正文
clause_path = convert_clause_to_json(tobidders_notice_path, output_folder) # 投标人须知正文条款pdf->json
@ -204,14 +204,12 @@ def goods_bid_main(output_folder, file_path, file_type, unique_id):
collected_good_list = good_list # Store good_list for later use
yield json.dumps({'base_info': transform_json_values(base_info)}, ensure_ascii=False)
# 如果是 evaluation_standards拆分技术标和商务标
if key == 'evaluation_standards':
elif key == 'evaluation_standards':
technical_standards = result["technical_standards"]
commercial_standards = result["commercial_standards"]
# 分别返回技术标和商务标
yield json.dumps({'technical_standards': transform_json_values(technical_standards)},
ensure_ascii=False)
yield json.dumps({'commercial_standards': transform_json_values(commercial_standards)},
ensure_ascii=False)
yield json.dumps({'technical_standards': transform_json_values(technical_standards)},ensure_ascii=False)
yield json.dumps({'commercial_standards': transform_json_values(commercial_standards)},ensure_ascii=False)
else:
# 处理其他任务的结果
yield json.dumps({key: transform_json_values(result)}, ensure_ascii=False)