From 3f79900ed4a4f45bb69d84869b325169eea8ef1b Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Fri, 6 Sep 2024 15:50:52 +0800 Subject: [PATCH] =?UTF-8?q?9.6=E9=83=A8=E5=88=86=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E5=BC=95=E7=94=A8=E4=BA=86=E6=8B=9B=E6=A0=87=E5=85=AC=E5=91=8A?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=86=85=E5=AE=B9=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=86=E5=AF=B9=E4=BA=8C=E6=AC=A1=E8=B7=B3=E8=BD=AC=E7=9A=84?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flask_app/main/ttt.py | 35 ++++-- flask_app/main/基础信息整合.py | 8 +- flask_app/main/形式响应评审.py | 112 ++++++++++++------ flask_app/main/截取pdf.py | 89 ++++++++++---- .../main/投标人须知正文条款提取成json文件.py | 33 +++--- flask_app/main/招标文件解析.py | 110 ++++++++--------- flask_app/main/根据条款号整合json.py | 22 ++-- flask_app/main/禁止投标情形.py | 4 +- flask_app/main/资格审查模块.py | 7 +- 9 files changed, 258 insertions(+), 162 deletions(-) diff --git a/flask_app/main/ttt.py b/flask_app/main/ttt.py index da6d65a..d75a150 100644 --- a/flask_app/main/ttt.py +++ b/flask_app/main/ttt.py @@ -1,15 +1,28 @@ import re -content="递交投标文件截止之日后120日内" -def extract_content_after_special_chars(content): - """ - 提取特定符号后的内容,直到遇到结束符号。 - """ - pattern = r'[\x01\x02☑√团]([^□]+)' - match = re.search(pattern, content) - if match: - return match.group(1).strip() # 提取匹配的内容,并去除多余空格 - return content # 如果没有找到匹配,返回原内容 +from PyPDF2 import PdfReader -res=extract_content_after_special_chars(content) + +def extract_common_header(pdf_path): + pdf_document = PdfReader(pdf_path) + headers = [] + num_pages_to_read = 3 # 预读页数 + + for i in range(min(num_pages_to_read, len(pdf_document.pages))): + page = pdf_document.pages[i] + text = page.extract_text() + if text: # 确保页面有文本内容 + first_line = text.strip().split('\n')[0] + headers.append(first_line) + + if len(headers) < 2: + return "" # 如果没有足够的页来比较,返回空字符串 + + # 使用set交集来找出公共部分 + common_header = set(headers[0].split()).intersection(*[set(header.split()) for header in headers[1:]]) + common_header = ' '.join(common_header) + return common_header + +input_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\test\\zbtest19.pdf" +res=extract_common_header(input_path) print(res) \ No newline at end of file diff --git a/flask_app/main/基础信息整合.py b/flask_app/main/基础信息整合.py index 5f720d7..d15e48e 100644 --- a/flask_app/main/基础信息整合.py +++ b/flask_app/main/基础信息整合.py @@ -81,8 +81,8 @@ def project_basic_info(knowledge_name,truncate0,output_folder,clause_path): # # 调用大模型回答项目基础信息 print("starting基础信息...") baseinfo_list = [] - # baseinfo_file_path='../static/提示词/前两章提问总结.txt' - baseinfo_file_path = 'flask_app/static/提示词/前两章提问总结.txt' # 替换为你的txt文件路径 + baseinfo_file_path='../static/提示词/前两章提问总结.txt' + # baseinfo_file_path = 'flask_app/static/提示词/前两章提问总结.txt' # 替换为你的txt文件路径 questions = read_questions_from_file(baseinfo_file_path) res1 = multi_threading(questions, knowledge_name) for _, response in res1: # _占位,代表ques;response[0]也是ques;response[1]是ans @@ -97,8 +97,8 @@ def project_basic_info(knowledge_name,truncate0,output_folder,clause_path): # # 判断是否分包、是否需要递交投标保证金等 chosen_numbers, merged = judge_whether_main(truncate0,output_folder) baseinfo_list.append(merged) - # judge_file_path = '../static/提示词/是否相关问题.txt' - judge_file_path ='flask_app/static/提示词/是否相关问题.txt' + judge_file_path = '../static/提示词/是否相关问题.txt' + # judge_file_path ='flask_app/static/提示词/是否相关问题.txt' judge_questions = read_questions_from_judge(judge_file_path, chosen_numbers) diff --git a/flask_app/main/形式响应评审.py b/flask_app/main/形式响应评审.py index 6bf5fb7..bcf87ba 100644 --- a/flask_app/main/形式响应评审.py +++ b/flask_app/main/形式响应评审.py @@ -4,8 +4,10 @@ import json import time from flask_app.main.多线程提问 import multi_threading -from flask_app.main.根据条款号整合json import process_and_merge_entries +from flask_app.main.根据条款号整合json import process_and_merge_entries,process_and_merge2 from flask_app.main.json_utils import extract_content_from_json +from flask_app.main.截取pdf import truncate_pdf_main +from flask_app.main.投标人须知正文条款提取成json文件 import convert_clause_to_json prompt = """ # 角色 你是一个文档处理专家,专门负责理解和操作基于特定内容的文档任务,这包括解析、总结、搜索或生成与给定文档相关的各类信息。 @@ -28,7 +30,7 @@ prompt = """ """ -def update_json_data(original_data, updates, second_response_list): +def update_json_data(original_data, updates1, updates2,second_response_list): """ 根据提供的更新字典覆盖原始JSON数据中对应的键值,支持点分隔的键来表示嵌套结构。 参数: @@ -49,7 +51,9 @@ def update_json_data(original_data, updates, second_response_list): data[keys[-1]] = value # 合并 updates 到 original_data 中 - for key, value in updates.items(): + for key, value in updates1.items(): + recursive_update(original_data, key, value) + for key,value in updates2.items(): recursive_update(original_data, key, value) # 遍历 second_response_list 中的每个字典,并合并到 original_data 中 @@ -59,8 +63,22 @@ def update_json_data(original_data, updates, second_response_list): return original_data +def judge_second_jump(input_dict): + formatted_questions = [] + keys_to_remove = [] + for key, value in input_dict.items(): + if '招标公告' in value or '第一章' in value: + formatted_entry = f"关于'{key}',第一章招标公告中的内容是怎样的?请按json格式给我提供信息,键名为'{key}',而键值需要完全与原文保持一致,不要擅自总结、删减,如果存在未知信息,请在对应键值处填'未知'。" + formatted_questions.append(formatted_entry) + keys_to_remove.append(key) # 标记需要删除的键 + # 从原始字典中删除处理过的键值对 + for key in keys_to_remove: + del input_dict[key] + + return input_dict, formatted_questions def extract_matching_keys(json_data): + #'投标人名称': '符合招标公告第3.2条规定' # 函数首先检查输入 json_data 是否为字符串类型。如果是,它会使用 json.loads() 将字符串解析为字典。 if isinstance(json_data, str): data = json.loads(json_data) @@ -68,7 +86,7 @@ def extract_matching_keys(json_data): data = json_data # 正则表达式匹配 - include_patterns = [re.compile(r"符合第"), re.compile(r"第.*?章"), re.compile(r"第.*?款"), re.compile(r"第.*?项"), re.compile(r"第.*?目")] + include_patterns = [re.compile(r"符合第"), re.compile(r"第.*?章"), re.compile(r"第.*?款"), re.compile(r"第.*?项"), re.compile(r"第.*?目"),re.compile(r"第.*?条")] additional_include_patterns = [re.compile(r"规定"), re.compile(r"条目")] exclude_patterns = ["投标文件格式", "权利义务", "技术标准","工程量清单"] additional_exclude_patterns = ["按要求", "按规定"] @@ -108,51 +126,67 @@ def extract_matching_keys(json_data): return final_matching #TODO:如果要引用到招标公告中的内容,考虑提取 或者qianwen-long -def reformat_questions(match_keys): - """ - [{'形式评审标准.多标段投标': '符合第一章“招标公告”第 3.3款规定'}, {'形式评审标准.投标文件的签署': '符合第二章“投标人须知”第 3.6.3(5)目规定'}, {'形式评审标准.投标保证金': '符合第二章“投标人须知”第 3.4.1项规定'}, {'形式评审标准.工程分包(如有)': '符合第二章“投标人须知”第 1.11款规定'}, {'响应性评审标准.投标内容': '符合第二章“投标人须知”第 1.3款的规定'}, {'响应性评审标准.监理服务阶段': '符合第二章“投标人须知”第 1.3款的规定'}, {'响应性评审标准.监理工作范围': '符合第二章“投标人须知”第 1.3款的规定'}, {'响应性评审标准.监理服务期': '符合第二章“投标人须知”第 1.3款的规定'}, {'响应性评审标准.投标有效期': '符合第二章“投标人须知”第 3.3.1项的规定'}, {'响应性评审标准.投标保证金': '符合第二章“投标人须知”第 3.4.1项的规定'}, {'响应性评审标准.重大偏差': '见第二章“投标人须知”第 1.12款规定'}] - """ - """ - 根据是否包含特定序号格式(如3.7.4或3.7.4(5)或3.7.4(5)),重新格式化匹配到的评审条目。 - 若包含序号,则提取出来;若不包含,则生成格式化的问题字符串。 - """ +def reformat_questions(match_keys, input_path, output_folder): entries_with_numbers = [] + entries_with_numbers2 = [] # 用于保存特别处理的条目 formatted_questions = [] + clause2_path = "" # 正则表达式,同时匹配全角和半角括号 - pattern = re.compile(r'(\d+(?:\.\d+)+)(?:[\(\(](\d+)[\)\)])?') #识别包含数字序列的特定格式 eg:3.7.4(5) 3.4.1 + pattern = re.compile(r'(\d+(?:\.\d+)+)(?:[\(\(](\d+)[\)\)])?') # 识别包含数字序列的特定格式 + + # 初始化文件处理 + generated_files = None + file_processed = False for entry in match_keys: key, value = next(iter(entry.items())) - if '招标公告' in value or '第一章' in value: - formatted_entry = f"关于‘{key}’,{value.replace('符合', '')}的内容是怎样的?请按json格式给我提供信息,键名为'{key}',如果存在未知信息,请在对应键值处填'未知'。" - formatted_questions.append(formatted_entry) - continue # 继续处理下一个条目 - match = pattern.search(value) - if match: - # 如果存在序号,保存序号与对应的键值对,包括括号内的数字(如果存在) + + if '招标公告' in value or '第一章' in value: + if not file_processed: + # 只处理一次文件操作 + generated_files = truncate_pdf_main(input_path, output_folder, 5) + file_processed = True # 设置标志位,避免重复处理文件 + if generated_files: + clause2_path = convert_clause_to_json(generated_files[0], output_folder, 2) + + if match and generated_files: + # 提取序号并添加到entries_with_numbers2 + num = match.group(1) + (f"({match.group(2)})" if match.group(2) else "") + entries_with_numbers2.append({key: num}) + else: + revised_value = value.replace('符合', '') + formatted_entry = f"关于'{key}',{revised_value}的内容是怎样的?请按json格式给我提供信息,键名为'{key}',而键值需要完全与原文保持一致,不要擅自总结、删减,如果存在未知信息,请在对应键值处填'未知'。" + formatted_questions.append(formatted_entry) + elif match: num = match.group(1) + (f"({match.group(2)})" if match.group(2) else "") entries_with_numbers.append({key: num}) else: - # 如果不存在序号,删除“符合”并格式化文本 - revised_standard = re.sub(r'符合', '', value) - formatted_entry = f"关于‘{key}’,{revised_standard}的内容是怎样的?请按json格式给我提供信息,键名为'{key}',如果存在未知信息,请在对应键值处填'未知'。" + revised_value = value.replace('符合', '') + formatted_entry = f"关于'{key}',{revised_value}的内容是怎样的?请按json格式给我提供信息,键名为'{key}',而键值需要完全与原文保持一致,不要擅自总结、删减,如果存在未知信息,请在对应键值处填'未知'。" formatted_questions.append(formatted_entry) - print(formatted_questions) - return entries_with_numbers, formatted_questions + + return entries_with_numbers, formatted_questions, entries_with_numbers2, clause2_path -def process_reviews(original_dict_data,knowledge_name, truncate0_jsonpath,clause_json_path): - matched_keys = extract_matching_keys(original_dict_data) #[{'形式评审标准.投标文件签字盖章': '符合第二章“投标人须知”第 3.7.3(4)目 规定'}, {'形式评审标准.多标段投标': '符合第二章“投标人须知”第 10.1款规定'}] 提取值中包含"符合"的字典 - entries_with_numbers, formatted_questions = reformat_questions(matched_keys) - results_2 = multi_threading(formatted_questions, knowledge_name, True) #无序号的直接问大模型 - second_response_list = [] +def process_reviews(original_dict_data,knowledge_name, truncate0_jsonpath,clause_json_path,input_file,output_folder): + combined_results2={} + matched_keys = extract_matching_keys(original_dict_data) #[{'形式评审标准.投标文件签字盖章': '符合第一章“投标人须知”第 3.7.3(4)目 规定'}, {'形式评审标准.多标段投标': '符合第二章“投标人须知”第 10.1款规定'}] 提取值中包含"符合"的字典 + entries_with_numbers, formatted_questions1,entries_with_numbers2, clause2_path = reformat_questions(matched_keys,input_file,output_folder) + combined_results = process_and_merge_entries(entries_with_numbers, truncate0_jsonpath, + clause_json_path) # 调用根据条款号整合json.py + combined_results1, formatted_questions2 = judge_second_jump(combined_results) + if entries_with_numbers2: #跳转第一章招标公告 + combined_results2=process_and_merge2(entries_with_numbers2,clause2_path) + + results_2 = multi_threading(formatted_questions1+formatted_questions2, knowledge_name, True) #无序号的直接问大模型 + first_response_list = [] for _, response in results_2: try: if response and len(response) > 1: # 检查response存在且有至少两个元素 temp = extract_content_from_json(response[1]) - second_response_list.append(temp) + first_response_list.append(temp) else: print(f"Warning: Missing or incomplete response data for query index {_}.") except Exception as e: @@ -160,18 +194,20 @@ def process_reviews(original_dict_data,knowledge_name, truncate0_jsonpath,clause # Assume JSON file paths are defined or configured correctly # print(entries_with_numbers) #[{'形式评审标准.多标段投标': '3.7.4(5)'}] - combined_results = process_and_merge_entries(entries_with_numbers, truncate0_jsonpath, clause_json_path) #调用根据条款号整合json.py - updated_json = update_json_data(original_dict_data, combined_results, second_response_list) + updated_json = update_json_data(original_dict_data, combined_results1, combined_results2,first_response_list) return updated_json if __name__ == "__main__": start_time=time.time() - knowledge_name="zbtest20" - truncate_tobidders_table_json_path="C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\truncate_output.json" - clause_path="C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\clause.json" - original_dict_data={'形式评审标准': {'多标段投标': '符合第一章“招标公告”第 3.3款规定', '投标文件': '投标文件能正常打开', '投标人名称': '与营业执照、资质证书一致', '投标文件的签署': '符合第二章“投标人须知”第 3.6.3(5)目规定', '投标保证金': '符合第二章“投标人须知”规定', '投标文件的格式、内容': '符合第八章“投标文件格式”的格式规定、实质性内容齐全,关键字迹清晰可辨', '投标报价': '①一份投标文件应只有一个投标报价,未提交选择性报价; ②投标函中报价与监理服务费报价汇总表中的报价保持一致; ③投标人未提交调价函。', '联合体投标人(如有)': '提交联合体协议书,并明确联合体牵头人', '工程分包(如有)': '符合第二章“投标人须知”第 1.11款规定'}, '响应性评审标准': {'投标内容': '符合第二章“投标人须知”第 1.3款的规定', '监理服务阶段': '符合第二章“投标人须知”第 1.3款的规定', '监理工作范围': '符合第二章“投标人须知”第 1.3款的规定', '投标报价': '①投标报价不高于最高投标限价(如果有); ②投标报价不低于成本价。', '监理服务期': '符合第二章“投标人须知”第 1.3款的规定', '投标有效期': '符合第二章“投标人须知”第 3.3.1项的规定', '投标保证金': '符合第二章“投标人须知”第 3.4.1项的规定', '权利义务': '符合或优于第四章“合同条款及格式”规定的权利义务', '技术标准': '符合第七章“技术标准”规定', '招标人不能接受的条件': '未附有招标人不能接受的条件', '重大偏差': '见第二章“投标人须知”第 1.12款规定'}} - formal_json = process_reviews(original_dict_data,knowledge_name, truncate_tobidders_table_json_path, clause_path) + knowledge_name="zbfile" + truncate_tobidders_table_json_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\temp8\\3abb6e16-19db-42ad-9504-53bf1072dfe7\\truncate_output.json" + clause_path="C:\\Users\\Administrator\\Desktop\\fsdownload\\temp8\\3abb6e16-19db-42ad-9504-53bf1072dfe7\\clause.json" + original_dict_data={'形式评审标准': {'投标文件': '符合第一章规定', '投标人名称': '符合招标公告第3.2条规定', '投标文件签字': '符合第二章“投标人须知”第3.7.3(4)目规定', '投标文件格式、内容': '符合第七章“投标文件格式”的要求,实质性内容齐全、关键字迹清晰可辨', '联合体投标人': '提交联合体协议书,并明确联合体牵头人', '报价唯一': '只能有一个有效报价', '多标段投标': '符合第二章“投标人须知”第 10.1款规定'}, '响应性评审标准': {'投标内容': '符合第二章“投标人须知”第1.3.1项规定', '工期': '符合第二章“投标人须知”第1.3.2项规定', '工程质量': '符合第二章“投标人须知”第1.3.3项规定'}} + + input_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\temp8\\3abb6e16-19db-42ad-9504-53bf1072dfe7\\ztbfile.pdf" + output_folder="C:\\Users\\Administrator\\Desktop\\fsdownload\\temp8\\3abb6e16-19db-42ad-9504-53bf1072dfe7" + formal_json = process_reviews(original_dict_data,knowledge_name, truncate_tobidders_table_json_path, clause_path,input_file,output_folder) data = json.dumps(formal_json, ensure_ascii=False, indent=4) end_time=time.time() elapsed_time = end_time - start_time diff --git a/flask_app/main/截取pdf.py b/flask_app/main/截取pdf.py index 6973c05..d8868a0 100644 --- a/flask_app/main/截取pdf.py +++ b/flask_app/main/截取pdf.py @@ -2,14 +2,39 @@ from PyPDF2 import PdfReader, PdfWriter import re # 导入正则表达式库 import os # 用于文件和文件夹操作 -def clean_page_numbers(text): - # 使用正则表达式删除页码 - # 假设页码在文本的最开始,紧跟着文字且无空格分隔 - cleaned_text = re.sub(r'^\s*\d+\s*(?=\D)', '', text) # 删除开头的页码,仅当紧跟非数字字符时 - # 删除结尾的页码 - cleaned_text = re.sub(r'\s+\d+\s*$', '', cleaned_text) - # 删除形如 /129 的页码 - cleaned_text = re.sub(r'\s*\/\s*\d+\\s*', '', cleaned_text) +def extract_common_header(pdf_path): + pdf_document = PdfReader(pdf_path) + headers = [] + num_pages_to_read = 3 # 预读页数 + + for i in range(min(num_pages_to_read, len(pdf_document.pages))): + page = pdf_document.pages[i] + text = page.extract_text() + if text: # 确保页面有文本内容 + first_line = text.strip().split('\n')[0] + headers.append(first_line) + + if len(headers) < 2: + return "" # 如果没有足够的页来比较,返回空字符串 + + # 使用set交集来找出公共部分 + common_header = set(headers[0].split()).intersection(*[set(header.split()) for header in headers[1:]]) + common_header = ' '.join(common_header) + return common_header + + +def clean_page_content(text, common_header): + # 首先删除抬头公共部分 + if common_header: # 确保有公共抬头才进行替换 + cleaned_text = text.replace(common_header, '', 1) # 假设抬头出现在文本开头,只替换一次 + else: + cleaned_text = text + + # 删除页码 + cleaned_text = re.sub(r'^\s*\d+\s*(?=\D)', '', cleaned_text) # 删除开头的页码,仅当紧跟非数字字符时 + cleaned_text = re.sub(r'\s+\d+\s*$', '', cleaned_text) # 删除结尾的页码 + cleaned_text = re.sub(r'\s*\/\s*\d+\s*', '', cleaned_text) # 删除形如 /129 的页码 + return cleaned_text @@ -39,18 +64,19 @@ def save_pages_to_new_pdf(pdf_path, output_folder, output_suffix, start_page, en print("提供的页码范围无效。") return output_pdf_path + def extract_pages_twice(pdf_path, output_folder, output_suffix): + common_header = extract_common_header(pdf_path) last_begin_index = 0 begin_pattern = re.compile(r'第[一二三四五六七八九十]+章\s*招标公告|第一卷') pdf_document = PdfReader(pdf_path) for i, page in enumerate(pdf_document.pages): text = page.extract_text() if text: - cleaned_text = clean_page_numbers(text) + cleaned_text = clean_page_content(text,common_header) # 检查“第一章”开始的位置 if begin_pattern.search(cleaned_text): last_begin_index = i # 更新最后匹配的索引,页码从0开始 - print(last_begin_index) if output_suffix == "qualification": common_pattern = r'^(?:附录(?:一)?[::]|附件(?:一)?[::]|附表(?:一)?[::])' # end_pattern = r'^(第[一二三四五六七八九十]+章\s*投标人须知|评标办法|评标办法前附表)' @@ -66,7 +92,7 @@ def extract_pages_twice(pdf_path, output_folder, output_suffix): for i, page in enumerate(pdf_document.pages[last_begin_index:], start=last_begin_index): text = page.extract_text() if text: - cleaned_text = clean_page_numbers(text) + cleaned_text = clean_page_content(text,common_header) # 确定起始页,需在last_begin_index之后 if ("资格审查" in cleaned_text or "资质条件" in cleaned_text): if re.search(common_pattern, cleaned_text, re.MULTILINE): @@ -94,6 +120,7 @@ def extract_pages_twice(pdf_path, output_folder, output_suffix): def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix): + common_header=extract_common_header(pdf_path) # 打开PDF文件 pdf_document = PdfReader(pdf_path) start_page = None @@ -103,10 +130,10 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter page = pdf_document.pages[i] text = page.extract_text() if text: - cleaned_text = clean_page_numbers(text) + cleaned_text = clean_page_content(text,common_header) # print(cleaned_text) if re.search(begin_pattern, cleaned_text) and i > begin_page: - if output_suffix == "invalid" and start_page: #仅当提取Invalid的时候,判断初始页码是第一个匹配到的页码,因为招标编号可能存在多个,后面的覆盖前面 + if output_suffix == "invalid" and start_page: # 仅当提取Invalid的时候,判断初始页码是第一个匹配到的页码,因为招标编号可能存在多个,后面的覆盖前面 continue else: start_page = i @@ -117,14 +144,14 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter else: condition = i > (start_page + 1) if condition: - is_invalid_condition = output_suffix == "invalid" and i > 30 #这边默认无效投标至少有30页 + is_invalid_condition = output_suffix == "invalid" and i > 30 # 这边默认无效投标至少有30页 if is_invalid_condition or output_suffix != "invalid": end_page = i break # 确保找到了起始和结束页面 if start_page is None or end_page is None: - if output_suffix == "qualification" or "invalid": + if output_suffix == "qualification" or output_suffix =="invalid": extract_pages_twice(pdf_path, output_folder, output_suffix) else: print(f"未找到起始或结束页在文件 {pdf_path} 中!") @@ -132,6 +159,7 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter else: return save_pages_to_new_pdf(pdf_path, output_folder, output_suffix, start_page, end_page) + def process_input(input_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix): # 确保输出文件夹存在 if not os.path.exists(output_folder): @@ -142,13 +170,15 @@ def process_input(input_path, output_folder, begin_pattern, begin_page, end_patt for file in os.listdir(input_path): if file.endswith(".pdf"): pdf_path = os.path.join(input_path, file) - output_pdf_path = extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix) + output_pdf_path = extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_pattern, + output_suffix) if output_pdf_path and os.path.isfile(output_pdf_path): generated_files.append(output_pdf_path) return generated_files elif os.path.isfile(input_path) and input_path.endswith(".pdf"): # 处理单个PDF文件 - output_pdf_path = extract_pages(input_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix) + output_pdf_path = extract_pages(input_path, output_folder, begin_pattern, begin_page, end_pattern, + output_suffix) if output_pdf_path and os.path.isfile(output_pdf_path): return [output_pdf_path] # 以列表形式返回,以保持一致性 else: @@ -165,7 +195,8 @@ def truncate_pdf_main(input_path, output_folder, selection): output_suffix = "tobidders_notice_table" elif selection == 2: # Configure patterns and phrases for "评标办法" - begin_pattern = re.compile(r'第[一二三四五六七八九十]+章\s*评标办法') #考虑到这种情况 '第三章 第三章 第三章 第三章 评标办法 评标办法 评标办法 评标办法' + begin_pattern = re.compile( + r'第[一二三四五六七八九十]+章\s*评标办法') # 考虑到这种情况 '第三章 第三章 第三章 第三章 评标办法 评标办法 评标办法 评标办法' begin_page = 10 end_pattern = re.compile(r'评标办法正文|评标办法') output_suffix = "evaluation_method" @@ -173,9 +204,11 @@ def truncate_pdf_main(input_path, output_folder, selection): # Configure patterns and phrases for "投标人须知正文" begin_pattern = re.compile(r'投标人须知正文') begin_page = 5 - end_pattern = re.compile(r'^第[一二三四五六七八九十]+章\s*评标办法|^评标办法前附表|^附录(?:一)?[::]|^附件(?:一)?[::]|^附表(?:一)?[::]', re.MULTILINE) + end_pattern = re.compile( + r'^第[一二三四五六七八九十]+章\s*评标办法|^评标办法前附表|^附录(?:一)?[::]|^附件(?:一)?[::]|^附表(?:一)?[::]', + re.MULTILINE) output_suffix = "tobidders_notice" - elif selection==4: + elif selection == 4: # 配置用于 "资格审查条件" 的正则表达式模式和短语 common_pattern = r'^(?:附录(?:一)?[::]|附件(?:一)?[::]|附表(?:一)?[::])' begin_pattern = re.compile(common_pattern + r'.*(?:资质|能力|信誉).*$', re.MULTILINE) @@ -187,6 +220,12 @@ def truncate_pdf_main(input_path, output_folder, selection): ) output_suffix = "qualification" elif selection == 5: + # 配置用于 "招标公告" 的正则表达式模式和短语 + begin_pattern = re.compile(r'第[一二三四五六七八九十]+章\s*招标公告|第一卷') + begin_page = 0 + end_pattern = re.compile(r'第[一二三四五六七八九十]+章\s*投标人须知', re.MULTILINE) + output_suffix = "notice" + elif selection == 6: # 配置用于 "无效标" 的正则表达式模式和短语 begin_pattern = re.compile(r'第[一二三四五六七八九十]+章\s*招标公告|第一卷|招标编号:|招标编号:') begin_page = 0 @@ -199,6 +238,7 @@ def truncate_pdf_main(input_path, output_folder, selection): # Process the selected input return process_input(input_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix) + def truncate_pdf_multiple(input_path, output_folder): truncate_files = [] for selection in range(1, 5): @@ -206,11 +246,12 @@ def truncate_pdf_multiple(input_path, output_folder): truncate_files.extend(files) return truncate_files -#TODO:需要完善二次请求。目前invalid一定能返回 前附表 须知正文如果为空的话要额外处理一下,比如说就不进行跳转(见xx表) 开评定标这里也要考虑 如果评分表为空,也要处理。 + +# TODO:需要完善二次请求。目前invalid一定能返回 前附表 须知正文如果为空的话要额外处理一下,比如说就不进行跳转(见xx表) 开评定标这里也要考虑 如果评分表为空,也要处理。 if __name__ == "__main__": - input_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标02.pdf" - output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件" + input_path = "C:\\Users\\Administrator\\Desktop\\招标文件\\test\\zbtest18.pdf" + output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\test" # truncate_pdf_multiple(input_path,output_folder) - selection = 4 # 例如:1 - 投标人须知前附表, 2 - 评标办法, 3 - 投标人须知正文 4-资格审查条件 5-无效标 + selection = 5 # 例如:1 - 投标人须知前附表, 2 - 评标办法, 3 - 投标人须知正文 4-资格审查条件 5-招标公告 6-无效标 generated_files = truncate_pdf_main(input_path, output_folder, selection) # # print("生成的文件:", generated_files) diff --git a/flask_app/main/投标人须知正文条款提取成json文件.py b/flask_app/main/投标人须知正文条款提取成json文件.py index 4199efa..70e141a 100644 --- a/flask_app/main/投标人须知正文条款提取成json文件.py +++ b/flask_app/main/投标人须知正文条款提取成json文件.py @@ -26,11 +26,11 @@ def extract_text_from_pdf(file_path): text += page_text return text -def extract_section(text, start_keyword, end_phrases): - start_index = text.find(start_keyword) - if start_index == -1: - return "" - +def extract_section(text, start_pattern, end_phrases): + start_match = re.search(start_pattern, text) + if not start_match: + return "" # 如果没有找到匹配的开始模式,返回空字符串 + start_index = start_match.start() end_index = len(text) for phrase in end_phrases: # Use multiline mode with `re.MULTILINE` @@ -124,26 +124,31 @@ def convert_to_json(file_path, start_word, end_phrases): parsed_data = parse_text_by_heading(text) return parsed_data -def convert_clause_to_json(input_path,output_folder): - start_word = "投标人须知正文" - end_phrases = [ - r'^第[一二三四五六七八九十]+章\s+评标办法', r'^评标办法前附表', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', - r'^附表:', r'^附表一:', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', r'^附表:', r'^附表一:', - ] +def convert_clause_to_json(input_path,output_folder,type=1): + if type==1: + start_word = "投标人须知正文" + end_phrases = [ + r'^第[一二三四五六七八九十]+章\s+评标办法', r'^评标办法前附表', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', + r'^附表:', r'^附表一:', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', r'^附表:', r'^附表一:', + ] + else: + start_word = r'第[一二三四五六七八九十]+章\s*招标公告|第一卷|招标编号:|招标编号:' + end_phrases=[r'第[一二三四五六七八九十]+章\s*投标人须知',r'投标人须知前附表'] result = convert_to_json(input_path, start_word, end_phrases) - output_path = os.path.join(output_folder, "clause.json") + file_name = "clause1.json" if type == 1 else "clause2.json" + output_path = os.path.join(output_folder, file_name) with open(output_path, 'w', encoding='utf-8') as f: json.dump(result, f, indent=4, ensure_ascii=False) return output_path if __name__ == "__main__": - file_path = 'C:\\Users\\Administrator\\Desktop\\fsdownload\\temp6\\713022ff-27d9-43e3-9cc9-2752effbfd66\\ztbfile_invalid.pdf' + file_path = 'C:\\Users\\Administrator\\Desktop\\招标文件\\zbtest16_tobidders_notice.pdf' start_word = "投标人须知正文" end_phrases = [ r'^第[一二三四五六七八九十]+章\s+评标办法', r'^评标办法前附表', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', r'^附表:', r'^附表一:', r'^附录:', r'^附录一:', r'^附件:', r'^附件一:', r'^附表:', r'^附表一:', ] - output_folder = 'C:\\Users\\Administrator\\Desktop\\fsdownload\\temp6\\713022ff-27d9-43e3-9cc9-2752effbfd66' + output_folder = 'C:\\Users\\Administrator\\Desktop\\招标文件' try: output_path = convert_clause_to_json(file_path,output_folder) print(f"Final JSON result saved to: {output_path}") diff --git a/flask_app/main/招标文件解析.py b/flask_app/main/招标文件解析.py index de7b71f..a101b5e 100644 --- a/flask_app/main/招标文件解析.py +++ b/flask_app/main/招标文件解析.py @@ -5,7 +5,7 @@ import os import time from flask_app.main.截取pdf import truncate_pdf_multiple from flask_app.main.table_content_extraction import extract_tables_main -from flask_app.main.知识库操作 import addfileToKnowledge, deleteKnowledge +from flask_app.main.知识库操作 import addfileToKnowledge, deleteKnowledge from flask_app.main.投标人须知正文条款提取成json文件 import convert_clause_to_json from flask_app.main.json_utils import nest_json_under_key, transform_json_values, combine_json_results from flask_app.main.无效标和废标和禁止投标整合 import combine_find_invalid @@ -14,19 +14,21 @@ import concurrent.futures from flask_app.main.基础信息整合 import project_basic_info from flask_app.main.资格审查模块 import combine_review_standards from flask_app.main.商务标技术标整合 import combine_evaluation_standards -from flask_app.main.format_change import pdf2docx,docx2pdf +from flask_app.main.format_change import pdf2docx, docx2pdf from flask_app.main.docx截取docx import copy_docx -global_logger=None +global_logger = None + + def get_global_logger(unique_id): if unique_id is None: return logging.getLogger() # 获取默认的日志器 logger = logging.getLogger(unique_id) return logger -#可能有问题:pdf转docx导致打勾符号消失 -def preprocess_files(output_folder, downloaded_file_path, file_type, unique_id): +# 可能有问题:pdf转docx导致打勾符号消失 +def preprocess_files(output_folder, downloaded_file_path, file_type, unique_id): # 根据文件类型处理文件路径 global docx_path, pdf_path if file_type == 1: # docx @@ -43,63 +45,67 @@ def preprocess_files(output_folder, downloaded_file_path, file_type, unique_id): # 调用截取PDF多次 truncate_files = truncate_pdf_multiple(pdf_path, output_folder) # [前附表, 评标办法, 须知正文, 资格审查条件,无效标] - # 处理各个部分 truncate0_docpath = pdf2docx(truncate_files[0]) # 投标人须知前附表转docx - if(file_type==1): - invalid_docpath=copy_docx(downloaded_file_path) - else: - invalid_docpath = pdf2docx(truncate_files[4]) # 无效标 + invalid_docpath = copy_docx(docx_path) #docx截取无效标部分 truncate0_jsonpath = os.path.join(output_folder, "truncate_output.json") extract_tables_main(truncate0_docpath, truncate0_jsonpath) # 投标人须知前附表docx->json,从表格提取数据 - truncate0=truncate_files[0] - truncate1=truncate_files[1] - truncate3=truncate_files[3] + truncate0 = truncate_files[0] #投标人须知前附表 + truncate1 = truncate_files[1] #评标办法前附表 + truncate3 = truncate_files[3] #资格审查表 clause_path = convert_clause_to_json(truncate_files[2], output_folder) # 投标人须知正文条款pdf->json return { - 'truncate0':truncate0, - 'truncate1':truncate1, - 'truncate3':truncate3, + 'input_file_path':downloaded_file_path, + 'output_folder':output_folder, + 'truncate0': truncate0, + 'truncate1': truncate1, + 'truncate3': truncate3, 'knowledge_index': index, - 'knowledge_name':knowledge_name, + 'knowledge_name': knowledge_name, 'truncate0_jsonpath': truncate0_jsonpath, 'clause_path': clause_path, 'invalid_docpath': invalid_docpath } -#基本信息 -def fetch_project_basic_info(knowledge_name,truncate0,output_folder,clause_path): #投标人须知前附表 + + +# 基本信息 +def fetch_project_basic_info(knowledge_name, truncate0, output_folder, clause_path): # 投标人须知前附表 global_logger.info("starting基础信息...") - basic_res=project_basic_info(knowledge_name,truncate0,output_folder,clause_path) + basic_res = project_basic_info(knowledge_name, truncate0, output_folder, clause_path) global_logger.info("基础信息done") return basic_res -#形式、响应、资格评审 -def fetch_review_standards(truncate1,truncate3,knowledge_name,truncate0_jsonpath,clause_path): + +# 形式、响应、资格评审 +def fetch_review_standards(truncate1, truncate3, knowledge_name, truncate0_jsonpath, clause_path,input_file,output_folder): global_logger.info("starting资格审查...") - review_standards_res=combine_review_standards(truncate1,truncate3,knowledge_name,truncate0_jsonpath,clause_path) + review_standards_res = combine_review_standards(truncate1, truncate3, knowledge_name, truncate0_jsonpath, + clause_path,input_file,output_folder) global_logger.info("资格审查done") return review_standards_res -#评分细则 -def fetch_evaluation_standards(truncate1): #评标办法前附表 + +# 评分细则 +def fetch_evaluation_standards(truncate1): # 评标办法前附表 global_logger.info("starting商务标技术标...") - evaluation_standards_res= combine_evaluation_standards(truncate1) + evaluation_standards_res = combine_evaluation_standards(truncate1) global_logger.info("商务标技术标done") return evaluation_standards_res -#无效、废标项解析 -def fetch_invalid_requirements(invalid_docpath,output_folder,truncate0_jsonpath,clause_path,truncate3): - #废标项要求:千问 + +# 无效、废标项解析 +def fetch_invalid_requirements(invalid_docpath, output_folder, truncate0_jsonpath, clause_path, truncate3): + # 废标项要求:千问 global_logger.info("starting无效标与废标...") - find_invalid_res=combine_find_invalid(invalid_docpath, output_folder, truncate0_jsonpath,clause_path,truncate3) + find_invalid_res = combine_find_invalid(invalid_docpath, output_folder, truncate0_jsonpath, clause_path, truncate3) global_logger.info("无效标与废标done...") return find_invalid_res -#投标文件要求 +# 投标文件要求 def fetch_bidding_documents_requirements(clause_path): global_logger.info("starting投标文件要求...") fetch_bidding_documents_requirements_json = extract_from_notice(clause_path, 1) @@ -107,35 +113,36 @@ def fetch_bidding_documents_requirements(clause_path): global_logger.info("投标文件要求done...") return qualify_nested_res -#开评定标流程 + +# 开评定标流程 def fetch_bid_opening(clause_path): global_logger.info("starting开评定标流程...") - fetch_bid_opening_json=extract_from_notice(clause_path,2) + fetch_bid_opening_json = extract_from_notice(clause_path, 2) qualify_nested_res = nest_json_under_key(fetch_bid_opening_json, "开评定标流程") global_logger.info("开评定标流程done...") return qualify_nested_res - -def main_processing(output_folder,downloaded_file_path,file_type,unique_id): #file_type=1->docx file_type=2->pdf +def main_processing(output_folder, downloaded_file_path, file_type, unique_id): # file_type=1->docx file_type=2->pdf global global_logger - global_logger= get_global_logger(unique_id) + global_logger = get_global_logger(unique_id) # Preprocess files and get necessary data paths and knowledge index processed_data = preprocess_files(output_folder, downloaded_file_path, file_type, unique_id) - with concurrent.futures.ThreadPoolExecutor() as executor: # Submit all tasks to the executor futures = { 'base_info': executor.submit(fetch_project_basic_info, processed_data['knowledge_name'], processed_data['truncate0'], output_folder, processed_data['clause_path']), - 'review_standards': executor.submit(fetch_review_standards, processed_data['truncate1'],processed_data['truncate3'], + 'review_standards': executor.submit(fetch_review_standards, processed_data['truncate1'], + processed_data['truncate3'], processed_data['knowledge_name'], processed_data['truncate0_jsonpath'], - processed_data['clause_path']), + processed_data['clause_path'],processed_data['input_file_path'],processed_data['output_folder']), 'evaluation_standards': executor.submit(fetch_evaluation_standards, processed_data['truncate1']), 'invalid_requirements': executor.submit(fetch_invalid_requirements, processed_data['invalid_docpath'], - output_folder, processed_data['truncate0_jsonpath'],processed_data['clause_path'],processed_data['truncate3']), + output_folder, processed_data['truncate0_jsonpath'], + processed_data['clause_path'], processed_data['truncate3']), 'bidding_documents_requirements': executor.submit(fetch_bidding_documents_requirements, processed_data['clause_path']), 'opening_bid': executor.submit(fetch_bid_opening, processed_data['clause_path']) @@ -143,7 +150,8 @@ def main_processing(output_folder,downloaded_file_path,file_type,unique_id): # comprehensive_responses = [] # Collect results in the defined order - for key in ['base_info', 'review_standards', 'evaluation_standards', 'invalid_requirements', 'bidding_documents_requirements','opening_bid']: + for key in ['base_info', 'review_standards', 'evaluation_standards', 'invalid_requirements', + 'bidding_documents_requirements', 'opening_bid']: try: # Wait for the future to complete and get the result result = futures[key].result() @@ -162,9 +170,9 @@ def main_processing(output_folder,downloaded_file_path,file_type,unique_id): # return final_result_path -#目前返回结果: - # "opening_bid": "{
\"开评定标流程\": {
\"开标\": {
\"开标时间和地点\": [
\"招标人在本章第4.2.1项规定的投标截止时间(开标时间)在“电子交易平台”上公开进行开标,所有投标人均应当准时在线参加开标。\",
\"招标人通过互联网在投标人须知前附表规定的地点组织开标,并在投标截止时间30分钟前,使用CA数字证书登录“电子交易平台”,进入“开标室”选择相应标段作在线开标的准备工作。\",
\"投标人应当在能够保证设施设备可靠、互联网畅通的任意地点,通过互联网在线参加开标。在投标截止时间前,使用加密其投标文件的CA数字证书登录“电子交易平台”,进入“开标室”选择所投标段进行签到,并实时在线关注招标人的操作情况。5.2开标程序\",
\"主持人按下列程序在“电子交易平台”的“开标室”进行在线开标:(1)宣布开标纪律;(2)公布主持人、招标人代表、监标人等有关人员姓名;(3)公布在投标截止时间前投标文件的递交情况;(4)公布投标保证金递交情况;(5)按照投标人须知前附表规定抽取评标基准价下浮值(如有);规定最高投标限价计算方法的,计算并公布最高投标限价(如适用),当众公布后记录在案;(6)读取已解密的投标文件的内容;(7)公布投标人名称、标段名称、投标保证金的递交情况、投标报价、项目经理姓名及其他内容,并生成开标记录;(8)开标结束。\",
\"在本章第5.2.1(6)目规定的时间内,非因“电子交易平台”原因造成投标文件未解密的,视为投标人撤回投标文件。已解密的投标文件少于三个的,招标失败;已解密的投标文件不少于三个,开标继续进行。\"
],
\"开标异议\": [
\"投标人对开标有异议的,应当在开标过程中提出;招标人当场对异议作出答复,并记入开标记录。异议与答复应通过“开标室”在“异议与答复”菜单以书面形式进行。本处所称异议是指投标人在开标过程中对投标文件提交、投标截止时间、开标程序、开标记录以及投标人和招标人或者投标人相互之间存在利益冲突的情形等提出的质疑。\",
\"投标人异议成立的,招标人将及时采取纠正措施,或者提交评标委员会评审确认;投标人异议不成立的,招标人将当场给予解释说明。\"
],
\"特殊情况的处置\": [
\"因“电子交易平台”系统故障导致无法投标的,交易中心及时通知招标人,招标人视情况决定是否顺延投标截止时间。因投标人自身原因导致无法完成投标的,由投标人自行承担后果。\",
\"因“电子交易平台”系统故障导致无法正常开标的,招标人将暂停开标,待系统恢复正常后继续开标。\",
\"“电子交易平台”系统故障是指下列情形:(1)系统服务器发生故障,无法访问或无法使用系统;(2)系统的软件或数据库出现错误,不能进行正常操作;(3)系统发现有安全漏洞,有潜在的泄密危险;(4)出现断电、断网事故;(5)其他无法保证招投标过程正常进行的情形。\"
]
},
\"评标\": {
\"评标委员会\": [
\"评标由招标人依法组建的评标委员会负责。评标委员会由招标人代表以及有关技术、经济等方面的专家组成。评标委员会成员人数以及技术、经济等方面专家的确定方式见投标人须知前附表。\",
\"评标委员会成员有下列情形之一的,应当回避:(1)投标人或投标人主要负责人的近亲属;(2)项目主管部门或者行政监督部门的人员;(3)与投标人有经济利益关系,可能影响对投标公正评审的;(4)曾因在招标、评标以及其他与招标投标有关活动中从事违法行为而受过行政处罚或刑事处罚的。\",
\"定标会招标人原则上应当在定标候选人公示结束后5个工作日内召开定标会,如有特殊情况,最迟应当在定标候选人公示结束后10个工作日内召开定标会,定标会进入公共资源交易中心进行。\",
\"定标流程(1)签到,宣读定标委员会成员名单;(2)监督小组监督员宣读定标纪律;(3)招标人代表或招标代理机构人员向定标委员会介绍定标项目相关情况;(4)定标委员成员会有疑问的,可以向招标人代表进行提问;(5)阅相关资料;(6)投票;(7)招标人代表或招标代理机构人员进行统计;(8)定标委员会组长宣读得分结果和定标结果;定标委员会成员签署定标报告,会议结束。\",
\"定标原则(1)组建定标委员会:由招标人组建定标委员会负责定标工作,按照定标委员会定标法进行定标。定标委员会成员数量为5人,招标人的法定代表人或其授权代表(为领导班子成员之一)应当参加定标会,并推荐担任定标会组长主持定标会,定标委员会其他成员从招标人组建的定标成员库中随机抽取确定。定标委员会成员与定标候选人有利害关系的,应当回避。所有参加定标会的定标委员会成员的意见应有书面记录,并由所有定标委员会成员签字确认。(2)组建定标监督小组:由招标人组建定标监督小组,对定标委员会的定标活动全过程进行监督,定标监督小组由2人组成,一般为招标人本单位或上级单位纪检监察人员,也可由招标人的法定代表人或主要负责人指定骨干成员参加。定标监督小组有权就定标委员会违反定标规则的行为进行质询。评估是否符合内控机制及价值取向,确保定标过程公正、公平。定标前,招标人或者招标代理机构在定标前可以介绍项目情况、招标情况、对投标人或者项目负责人的考察、质询情况;招标人可以邀请评标专家代表介绍评标情况、专家评审意见及评标结论、提醒注意事项。定标委员会成员有疑问的,可以向招标人或者招标代理机构、评标专家提问。\",
\"定标办法(1)定标会成员根据评标委员会提出书面评标报告,结合定标候选人的投标报价、商务标、技术标、市场信誉等,招标人应当按照充分竞争、合理低价的原则,集体讨论后,采用简单多数原则进行票决,在进入投票范围的定标候选人中,以每人投票支持一个定标候选人的方式,得票最多且过半数的定标候选人为中标人。当没有定标候选人得票超过半数,但有2个定标候选人得票较多时,选择得票较多的2个定标候选人(按上一轮得票多少的顺序选择,在选择第2个定标候选人时出现同票的投标人时,所有同票定标候选人一并纳入下一轮的投票范围)作为二次投票的范围,直至出现得票过半数的定标候选人为止。如果没有2个定标候选人得票较多时,重新投票。(2)定标会由招标人或代理机构的工作人员发放选票、定标会成员填写选票(须说明推荐理由并署名),定标过程公开、公平、公正。定标会成员按有关规定及招标文件约定的定标方法确定一名中标人。投票定标选票招标项目名称:支持的投标人支持理由定标委员签名:时间:本项目采用“评定分离”方法实施招投标活动。本项目定标办法详见第三章附件定标办法。\",
\"定标标准(1)择优要素。招标人在定标前应对评标委员会评审结果与实际情况进行实质性审查核实,重点对投标人的企业实力、企业信誉、履约能力的真实性、准确性、一致性进行核实,招标人应如实记录审查核实情况并作为定标参考。在考虑价格因素时,招标人应坚持投标人投标报价和其履约能力、服务质量等与招标项目相匹配的原则。企业实力包括资质等级、近几年营业额、过往业绩(含业绩影响力、难易程度)等方面。企业信誉包括获得各种荣誉、过往业绩履约情况,同时应重点关注近几年的不良信息,包括建设行政主管部门作出的各种不良处罚以及其他失信记录。对拟派团队履约能力与履约水平考核方式,可以考察团队主要负责人类似业绩情况,也可以对拟派项目负责人进行答辩。为确保可追溯性,答辩工作在有录音、录像场所进行。各项考核动作要针对所有投标人统一进行,不宜针对部分投标人进行考核,以体现公平原则。在同等条件下,择优的相对标准有以下几个方面:1)投标报价:各定标候选人的报价结合其履约能力,服务质量等与招标项目相匹配,经综合比较,价格最合理得优;2)工程业绩:综合比较投标人投标人近五年,完成的单项合同额在2000万元以上的装饰装修项目,主要比较项目难易程度和项目造价,工程业绩总造价高且项目难度大的优于工程业绩总造价低且设计难度小的;若总体难度差异不大且造价类似的情况下,业绩数量多的优于业绩数量少的;3)技术方案:对项目理解程度高、与本项目针对性强、技术方案完善且合理性相应程度高的企业优于项目理解程度一般、技术方案基本完善且进度控制一般的企业;4)企业实力:企业财务指标良好(整体营业收入、资产负债率等)的企业优于财务指标一般得企业;以水平相同的情况下,营业收入的优劣为准;5)企业获奖:近五年(指从投标截止日往前推算五年)类似项目获得国家级奖项优于获得省级奖项;6)企业信誉:无不良行为记录企业优于有不良行为记录企业,不良行为记录较轻企业优于不良行为记录较重企业。定标会在评议时优先进行“比优”,无法比优情况下可进行“比劣”,“比劣”可参考以下等要素进行:1)有无串通投标,围标,以行贿等不正当手段谋取中标行为;2)有无挂靠,以他人名义投标,出让或者出租资格、资质证书供他人投标行为;投标人在招标人的项目中有无严重违约或重大工程质量安全问题;投标人在近一年内经查实有以上行为的不确定为中标人。\"
],
\"评标原则\": \"评标活动遵循公平、公正、科学和择优的原则。\",
\"评标\": \"评标委员会按照第三章“评标办法”规定的方法、评审因素、标准和程序对投标文件进行评审。第三章“评标办法”没有规定的方法、评审因素和标准,不作为评标依据。\",
\"评标结果公示\": \"招标人将自收到评标报告之日起3日内,在投标人须知前附表规定的媒介公示中标候选人。公示期不少于3日。投标人或者其他利害关系人对评标结果有异议的,应当在评标结果公示期间提出。招标人自收到异议之日起3日内作出答复;作出答复前,暂停招标投标活动。异议与答复应当通过“电子交易平台”在“异议与答复”菜单以书面形式进行。\",
\"履约能力的审查(如有)\": \"如果中标候选人的经营、财务状况发生较大变化或者存在违法行为,招标人认为可能影响其履约能力的,将在发出中标通知书前报行政监督部门后,召集原评标委员会按照招标文件规定的标准和方法审查确认。\"
},
\"定标\": {
\"评标结果\": \"(1)评标委员会完成评标后,应当向招标人提出书面评标报告,阐明评标委员会对各投标文件的评审和比较意见,并按照招标文件中规定的评标方法,在投标报价合格的基础上,按照最终得分(保留2位小数)由高到低推荐定标候选人。定标候选人不少于3家,不超过5家。投标人的数量少于或者等于10家时,评标委员会推荐的定标候选人数量不超过3家,经评标委员会评审,符合招标文件要求的定标候选人不足3家时,由评标委员会作出是否具备竞争性,如具备竞争性,可继续推荐定标候选人,招标人可继续定标,否则,招标人应重新招标。定标侯选人进入定标程序。(2)经评标委员会评审,符合招标文件要求的定标候选人不足3家时,由评标委员会作出是否具备竞争性,如具备竞争性,可继续推荐定标候选人,招标人可继续定标,否则,招标人应重新招标。(3)招标人应当自收到评标报告之日起3日内公示定标候选人,公示期不少于3日。对评标结果的异议的提出和处理,适用《招标投标法实施条例》第五十四条的规定。评标结果(定标候选人)公示期间,因异议或投诉导致定标候选人少于招标文件规定的数量时,招标人继续定标还是在原评标委员会评审的基础上递补定标候选人由招标人在招标文件中明确。评标结果(定标候选人)公示期间,有定标候选人因异议或投诉并查实被取消中标资格时,若有效定标候选人不少于3家的,不再递补,招标人继续定标;除评标委员会作出具备竞争性情形外,若有效定标候选人少于3家的,按投标人得分高低补足至3家。对于递补的定标候选人需在黄石市公共资源交易信息网公示不少于3日。\"
}
}
}" -#} +# 目前返回结果: +# "opening_bid": "{
\"开评定标流程\": {
\"开标\": {
\"开标时间和地点\": [
\"招标人在本章第4.2.1项规定的投标截止时间(开标时间)在“电子交易平台”上公开进行开标,所有投标人均应当准时在线参加开标。\",
\"招标人通过互联网在投标人须知前附表规定的地点组织开标,并在投标截止时间30分钟前,使用CA数字证书登录“电子交易平台”,进入“开标室”选择相应标段作在线开标的准备工作。\",
\"投标人应当在能够保证设施设备可靠、互联网畅通的任意地点,通过互联网在线参加开标。在投标截止时间前,使用加密其投标文件的CA数字证书登录“电子交易平台”,进入“开标室”选择所投标段进行签到,并实时在线关注招标人的操作情况。5.2开标程序\",
\"主持人按下列程序在“电子交易平台”的“开标室”进行在线开标:(1)宣布开标纪律;(2)公布主持人、招标人代表、监标人等有关人员姓名;(3)公布在投标截止时间前投标文件的递交情况;(4)公布投标保证金递交情况;(5)按照投标人须知前附表规定抽取评标基准价下浮值(如有);规定最高投标限价计算方法的,计算并公布最高投标限价(如适用),当众公布后记录在案;(6)读取已解密的投标文件的内容;(7)公布投标人名称、标段名称、投标保证金的递交情况、投标报价、项目经理姓名及其他内容,并生成开标记录;(8)开标结束。\",
\"在本章第5.2.1(6)目规定的时间内,非因“电子交易平台”原因造成投标文件未解密的,视为投标人撤回投标文件。已解密的投标文件少于三个的,招标失败;已解密的投标文件不少于三个,开标继续进行。\"
],
\"开标异议\": [
\"投标人对开标有异议的,应当在开标过程中提出;招标人当场对异议作出答复,并记入开标记录。异议与答复应通过“开标室”在“异议与答复”菜单以书面形式进行。本处所称异议是指投标人在开标过程中对投标文件提交、投标截止时间、开标程序、开标记录以及投标人和招标人或者投标人相互之间存在利益冲突的情形等提出的质疑。\",
\"投标人异议成立的,招标人将及时采取纠正措施,或者提交评标委员会评审确认;投标人异议不成立的,招标人将当场给予解释说明。\"
],
\"特殊情况的处置\": [
\"因“电子交易平台”系统故障导致无法投标的,交易中心及时通知招标人,招标人视情况决定是否顺延投标截止时间。因投标人自身原因导致无法完成投标的,由投标人自行承担后果。\",
\"因“电子交易平台”系统故障导致无法正常开标的,招标人将暂停开标,待系统恢复正常后继续开标。\",
\"“电子交易平台”系统故障是指下列情形:(1)系统服务器发生故障,无法访问或无法使用系统;(2)系统的软件或数据库出现错误,不能进行正常操作;(3)系统发现有安全漏洞,有潜在的泄密危险;(4)出现断电、断网事故;(5)其他无法保证招投标过程正常进行的情形。\"
]
},
\"评标\": {
\"评标委员会\": [
\"评标由招标人依法组建的评标委员会负责。评标委员会由招标人代表以及有关技术、经济等方面的专家组成。评标委员会成员人数以及技术、经济等方面专家的确定方式见投标人须知前附表。\",
\"评标委员会成员有下列情形之一的,应当回避:(1)投标人或投标人主要负责人的近亲属;(2)项目主管部门或者行政监督部门的人员;(3)与投标人有经济利益关系,可能影响对投标公正评审的;(4)曾因在招标、评标以及其他与招标投标有关活动中从事违法行为而受过行政处罚或刑事处罚的。\",
\"定标会招标人原则上应当在定标候选人公示结束后5个工作日内召开定标会,如有特殊情况,最迟应当在定标候选人公示结束后10个工作日内召开定标会,定标会进入公共资源交易中心进行。\",
\"定标流程(1)签到,宣读定标委员会成员名单;(2)监督小组监督员宣读定标纪律;(3)招标人代表或招标代理机构人员向定标委员会介绍定标项目相关情况;(4)定标委员成员会有疑问的,可以向招标人代表进行提问;(5)阅相关资料;(6)投票;(7)招标人代表或招标代理机构人员进行统计;(8)定标委员会组长宣读得分结果和定标结果;定标委员会成员签署定标报告,会议结束。\",
\"定标原则(1)组建定标委员会:由招标人组建定标委员会负责定标工作,按照定标委员会定标法进行定标。定标委员会成员数量为5人,招标人的法定代表人或其授权代表(为领导班子成员之一)应当参加定标会,并推荐担任定标会组长主持定标会,定标委员会其他成员从招标人组建的定标成员库中随机抽取确定。定标委员会成员与定标候选人有利害关系的,应当回避。所有参加定标会的定标委员会成员的意见应有书面记录,并由所有定标委员会成员签字确认。(2)组建定标监督小组:由招标人组建定标监督小组,对定标委员会的定标活动全过程进行监督,定标监督小组由2人组成,一般为招标人本单位或上级单位纪检监察人员,也可由招标人的法定代表人或主要负责人指定骨干成员参加。定标监督小组有权就定标委员会违反定标规则的行为进行质询。评估是否符合内控机制及价值取向,确保定标过程公正、公平。定标前,招标人或者招标代理机构在定标前可以介绍项目情况、招标情况、对投标人或者项目负责人的考察、质询情况;招标人可以邀请评标专家代表介绍评标情况、专家评审意见及评标结论、提醒注意事项。定标委员会成员有疑问的,可以向招标人或者招标代理机构、评标专家提问。\",
\"定标办法(1)定标会成员根据评标委员会提出书面评标报告,结合定标候选人的投标报价、商务标、技术标、市场信誉等,招标人应当按照充分竞争、合理低价的原则,集体讨论后,采用简单多数原则进行票决,在进入投票范围的定标候选人中,以每人投票支持一个定标候选人的方式,得票最多且过半数的定标候选人为中标人。当没有定标候选人得票超过半数,但有2个定标候选人得票较多时,选择得票较多的2个定标候选人(按上一轮得票多少的顺序选择,在选择第2个定标候选人时出现同票的投标人时,所有同票定标候选人一并纳入下一轮的投票范围)作为二次投票的范围,直至出现得票过半数的定标候选人为止。如果没有2个定标候选人得票较多时,重新投票。(2)定标会由招标人或代理机构的工作人员发放选票、定标会成员填写选票(须说明推荐理由并署名),定标过程公开、公平、公正。定标会成员按有关规定及招标文件约定的定标方法确定一名中标人。投票定标选票招标项目名称:支持的投标人支持理由定标委员签名:时间:本项目采用“评定分离”方法实施招投标活动。本项目定标办法详见第三章附件定标办法。\",
\"定标标准(1)择优要素。招标人在定标前应对评标委员会评审结果与实际情况进行实质性审查核实,重点对投标人的企业实力、企业信誉、履约能力的真实性、准确性、一致性进行核实,招标人应如实记录审查核实情况并作为定标参考。在考虑价格因素时,招标人应坚持投标人投标报价和其履约能力、服务质量等与招标项目相匹配的原则。企业实力包括资质等级、近几年营业额、过往业绩(含业绩影响力、难易程度)等方面。企业信誉包括获得各种荣誉、过往业绩履约情况,同时应重点关注近几年的不良信息,包括建设行政主管部门作出的各种不良处罚以及其他失信记录。对拟派团队履约能力与履约水平考核方式,可以考察团队主要负责人类似业绩情况,也可以对拟派项目负责人进行答辩。为确保可追溯性,答辩工作在有录音、录像场所进行。各项考核动作要针对所有投标人统一进行,不宜针对部分投标人进行考核,以体现公平原则。在同等条件下,择优的相对标准有以下几个方面:1)投标报价:各定标候选人的报价结合其履约能力,服务质量等与招标项目相匹配,经综合比较,价格最合理得优;2)工程业绩:综合比较投标人投标人近五年,完成的单项合同额在2000万元以上的装饰装修项目,主要比较项目难易程度和项目造价,工程业绩总造价高且项目难度大的优于工程业绩总造价低且设计难度小的;若总体难度差异不大且造价类似的情况下,业绩数量多的优于业绩数量少的;3)技术方案:对项目理解程度高、与本项目针对性强、技术方案完善且合理性相应程度高的企业优于项目理解程度一般、技术方案基本完善且进度控制一般的企业;4)企业实力:企业财务指标良好(整体营业收入、资产负债率等)的企业优于财务指标一般得企业;以水平相同的情况下,营业收入的优劣为准;5)企业获奖:近五年(指从投标截止日往前推算五年)类似项目获得国家级奖项优于获得省级奖项;6)企业信誉:无不良行为记录企业优于有不良行为记录企业,不良行为记录较轻企业优于不良行为记录较重企业。定标会在评议时优先进行“比优”,无法比优情况下可进行“比劣”,“比劣”可参考以下等要素进行:1)有无串通投标,围标,以行贿等不正当手段谋取中标行为;2)有无挂靠,以他人名义投标,出让或者出租资格、资质证书供他人投标行为;投标人在招标人的项目中有无严重违约或重大工程质量安全问题;投标人在近一年内经查实有以上行为的不确定为中标人。\"
],
\"评标原则\": \"评标活动遵循公平、公正、科学和择优的原则。\",
\"评标\": \"评标委员会按照第三章“评标办法”规定的方法、评审因素、标准和程序对投标文件进行评审。第三章“评标办法”没有规定的方法、评审因素和标准,不作为评标依据。\",
\"评标结果公示\": \"招标人将自收到评标报告之日起3日内,在投标人须知前附表规定的媒介公示中标候选人。公示期不少于3日。投标人或者其他利害关系人对评标结果有异议的,应当在评标结果公示期间提出。招标人自收到异议之日起3日内作出答复;作出答复前,暂停招标投标活动。异议与答复应当通过“电子交易平台”在“异议与答复”菜单以书面形式进行。\",
\"履约能力的审查(如有)\": \"如果中标候选人的经营、财务状况发生较大变化或者存在违法行为,招标人认为可能影响其履约能力的,将在发出中标通知书前报行政监督部门后,召集原评标委员会按照招标文件规定的标准和方法审查确认。\"
},
\"定标\": {
\"评标结果\": \"(1)评标委员会完成评标后,应当向招标人提出书面评标报告,阐明评标委员会对各投标文件的评审和比较意见,并按照招标文件中规定的评标方法,在投标报价合格的基础上,按照最终得分(保留2位小数)由高到低推荐定标候选人。定标候选人不少于3家,不超过5家。投标人的数量少于或者等于10家时,评标委员会推荐的定标候选人数量不超过3家,经评标委员会评审,符合招标文件要求的定标候选人不足3家时,由评标委员会作出是否具备竞争性,如具备竞争性,可继续推荐定标候选人,招标人可继续定标,否则,招标人应重新招标。定标侯选人进入定标程序。(2)经评标委员会评审,符合招标文件要求的定标候选人不足3家时,由评标委员会作出是否具备竞争性,如具备竞争性,可继续推荐定标候选人,招标人可继续定标,否则,招标人应重新招标。(3)招标人应当自收到评标报告之日起3日内公示定标候选人,公示期不少于3日。对评标结果的异议的提出和处理,适用《招标投标法实施条例》第五十四条的规定。评标结果(定标候选人)公示期间,因异议或投诉导致定标候选人少于招标文件规定的数量时,招标人继续定标还是在原评标委员会评审的基础上递补定标候选人由招标人在招标文件中明确。评标结果(定标候选人)公示期间,有定标候选人因异议或投诉并查实被取消中标资格时,若有效定标候选人不少于3家的,不再递补,招标人继续定标;除评标委员会作出具备竞争性情形外,若有效定标候选人少于3家的,按投标人得分高低补足至3家。对于递补的定标候选人需在黄石市公共资源交易信息网公示不少于3日。\"
}
}
}" +# } # def main_processing(output_folder, downloaded_file_path, file_type, unique_id): # global global_logger @@ -207,10 +215,9 @@ def main_processing(output_folder,downloaded_file_path,file_type,unique_id): # # # deleteKnowledge(processed_data['knowledge_index']) - -#TODO:如果上传的是pdf转过的docx文件,那么提取打勾符号就会有问题 zbtest20 跳转涉及二级跳转 对于跳转到第一章 招标公告的要做额外处理 资格审查位置在第一章后面。如果未截取成功,需要作额外处理 logger不能保存控制台输出 +# TODO:如果上传的是pdf转过的docx文件,那么提取打勾符号就会有问题 zbtest20 跳转涉及二级跳转 对于跳转到第一章 招标公告的要做额外处理 资格审查位置在第一章后面。如果未截取成功,需要作额外处理 logger不能保存控制台输出 if __name__ == "__main__": - output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\test" + output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\test3" # truncate0 = os.path.join(output_folder, "ztb_tobidders_notice_table.pdf") # truncate1=os.path.join(output_folder,"ztb_evaluation_method.pdf") @@ -218,12 +225,9 @@ if __name__ == "__main__": # truncate0_jsonpath = os.path.join(output_folder, "truncate_output3.json") start_time = time.time() - file_type=2 - input_file = "C:\\Users\\Administrator\\Desktop\\招标文件\\test\\zbtest12.docx" - file_path=main_processing(output_folder,input_file,1,"uuidzyzy11") - + file_type = 1 #1:docx 2:pdf 3:其他 + input_file = "C:\\Users\\Administrator\\Desktop\\招标文件\\test3\\zbtest20.docx" + file_path = main_processing(output_folder, input_file, file_type, "uuidzyzy11") end_time = time.time() elapsed_time = end_time - start_time # 计算耗时 print(f"Function execution took {elapsed_time} seconds.") - - diff --git a/flask_app/main/根据条款号整合json.py b/flask_app/main/根据条款号整合json.py index 6c2e73c..b99d671 100644 --- a/flask_app/main/根据条款号整合json.py +++ b/flask_app/main/根据条款号整合json.py @@ -1,23 +1,12 @@ import json import re - -def extract_content_after_special_chars(content): - """ - 提取特定符号后的内容,直到遇到结束符号。 - """ - pattern = r'[\x01\x02☑√团]([^□]+)' - match = re.search(pattern, content) - if match: - return match.group(1).strip() # 提取匹配的内容,并去除多余空格 - return content # 如果没有找到匹配,返回原内容 - def load_json(file_path): """ 加载JSON文件,并统一其中的括号为全角括号。 """ with open(file_path, 'r', encoding='utf-8') as f: - data = json.load(f) + data = json.load(f) #字典 return standardize_brackets_in_json(data) def standardize_brackets_in_json(data): @@ -146,10 +135,17 @@ def process_and_merge_entries(entries_with_numbers, primary_json_path, secondary combined_results = find_entries_in_jsons(entries_with_numbers, primary_json_data, secondary_json_data) return combined_results +def process_and_merge2(entries_with_numbers,json_path): + json_data=load_json(json_path) + combined_results=find_entries_in_jsons(entries_with_numbers,json_data,{}) + return combined_results + + +#3.4.1 投标保证金,提取截止有问题。 if __name__ == "__main__": # Hypothetical entries and file paths for testing # entries_with_numbers = [{'形式评审标准.投标文件签字盖章': '3.7.3(3)'}, {'形式评审标准.多标段投标': '10.1'}, {'形式评审标准.“技术暗标”': '3.7.4(5)'}, {'响应性评审标准.投标内容': '1.3.1'}, {'响应性评审标准.工期': '1.3.2'}, {'响应性评审标准.工程质量': '1.3.3'}, {'响应性评审标准.投标有效期': '3.3.1'}, {'响应性评审标准.投标保证金': '3.4.1'}, {'响应性评审标准.分包计划': '1.11'}] - entries_with_numbers=[{'xxx': '3.6.3(5)'}] + entries_with_numbers=[{'xxx': '4.2.3'}] primary_json_path = 'C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\truncate_output.json' secondary_json_path = 'C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\clause.json' diff --git a/flask_app/main/禁止投标情形.py b/flask_app/main/禁止投标情形.py index 235be36..cc96540 100644 --- a/flask_app/main/禁止投标情形.py +++ b/flask_app/main/禁止投标情形.py @@ -125,12 +125,12 @@ def process_string_list(string_list): else: # 如果没有匹配到内容,返回空列表 return [] -def find_forbidden(truncate_json_path,clause_path,truncate4): #投标人须知前附表 条款 评分前附表和资格审查表中 +def find_forbidden(truncate_json_path,clause_path,truncate3): #投标人须知前附表 条款 评分前附表和资格审查表中 # output_filename="merged.pdf" # paths=[truncate1,truncate4] # merged_filepath=merge_pdfs(paths,output_filename) #暂时废弃,评分前附表中的在'否决投标'中摘录了。 - file_id=upload_file(truncate4) + file_id=upload_file(truncate3) # user_query_forbidden = "该招标文件规定的投标人不得存在的其他情形有哪些,请按json列表格式给我提供信息,键名为'不得存在的其他情形',请你不要回答有关\"信誉要求\"的内容,若文件中未说明,请在键值中填'未知'。" user_query_forbidden = "该招标文件规定的投标人不得存在的其他情形有哪些,请以列表给我提供信息,形如[xx,xx,...],请你不要回答有关\"信誉要求\"的内容,若原文未提及,返回[]。" qianwen_forbidden_str = qianwen_long(file_id, user_query_forbidden) diff --git a/flask_app/main/资格审查模块.py b/flask_app/main/资格审查模块.py index 8bb6794..a3d14fa 100644 --- a/flask_app/main/资格审查模块.py +++ b/flask_app/main/资格审查模块.py @@ -8,7 +8,7 @@ from flask_app.main.通义千问long import upload_file, qianwen_long from concurrent.futures import ThreadPoolExecutor -def combine_review_standards(truncate1,truncate3,knowledge_name,truncate0_jsonpath,clause_path): #评标办法前附表 +def combine_review_standards(truncate1,truncate3,knowledge_name,truncate0_jsonpath,clause_path,input_file,output_folder): #评标办法前附表 # 形式评审、响应评审:千问 print("starting形式响应评审...") file_id=upload_file(truncate1) #评标办法前附表 @@ -21,7 +21,7 @@ def combine_review_standards(truncate1,truncate3,knowledge_name,truncate0_jsonpa # 创建Future对象 future_qualification = executor.submit(process_qualification, qualification_review, truncate3, knowledge_name) future_form_response = executor.submit(process_reviews, original_dict_data, knowledge_name, truncate0_jsonpath, - clause_path) + clause_path,input_file,output_folder) # 等待执行结果 final_qualify_json = future_qualification.result() @@ -32,6 +32,7 @@ def combine_review_standards(truncate1,truncate3,knowledge_name,truncate0_jsonpa if __name__ == "__main__": + input_file="C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹\\zbtest11.pdf" output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\招标test文件夹" # truncate0 = os.path.join(output_folder, "zbtest20_tobidders_notice_table.pdf") truncate2=os.path.join(output_folder,"zbtest20_tobidders_notice.pdf") @@ -40,5 +41,5 @@ if __name__ == "__main__": truncate3=os.path.join(output_folder,"zbtest20_qualification.pdf") clause_path = convert_clause_to_json(truncate2, output_folder) truncate0_jsonpath = os.path.join(output_folder, "truncate_output.json") - res=combine_review_standards(truncate1,truncate3, knowledge_name,truncate0_jsonpath,clause_path) + res=combine_review_standards(truncate1,truncate3, knowledge_name,truncate0_jsonpath,clause_path,input_file,output_folder) print(res) \ No newline at end of file