diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 5eac8bc..8968112 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -4,6 +4,7 @@ + diff --git a/flask_app/main/json_utils.py b/flask_app/main/json_utils.py index 1b98db9..3855972 100644 --- a/flask_app/main/json_utils.py +++ b/flask_app/main/json_utils.py @@ -104,7 +104,7 @@ def rename_outer_key(original_data,new_key): return new_data def transform_json_values(data): if isinstance(data, dict): - return {key: transform_json_values(value) for key, value in data.items()} + return {key.replace(' ', ''): transform_json_values(value) for key, value in data.items()} elif isinstance(data, list): return [transform_json_values(item) for item in data] elif isinstance(data, bool): diff --git a/flask_app/main/start_up.py b/flask_app/main/start_up.py index 699f1ac..cf79ca2 100644 --- a/flask_app/main/start_up.py +++ b/flask_app/main/start_up.py @@ -1,6 +1,6 @@ import logging +import re import shutil -import sys import time import uuid from datetime import datetime, timedelta @@ -143,127 +143,297 @@ def zbparse(): logger.error('Exception occurred: ' + str(e)) return jsonify({'error': str(e)}), 500 -def post_processing(data,includes): +def post_processing(combined_data, includes): # 初始化结果字典,预设'其他'分类为空字典 - result = {"其他": {}} + processed_data = {"其他": {}} + + # 初始化提取的信息字典 + extracted_info = {} + + # 定义一个辅助函数用于获取嵌套字典中的值 + def get_nested(dic, keys, default=None): + for key in keys: + if isinstance(dic, dict): + dic = dic.get(key, default) + else: + return default + return dic + + # 定义一个辅助函数用于递归查找包含特定子字符串的键 + def find_keys_containing(dic, substring): + found_values = [] + if isinstance(dic, dict): + for key, value in dic.items(): + if substring in key: + found_values.append(value) + if isinstance(value, dict): + found_values.extend(find_keys_containing(value, substring)) + elif isinstance(value, list): + for item in value: + if isinstance(item, dict): + found_values.extend(find_keys_containing(item, substring)) + return found_values + + # 定义一个辅助函数用于根据候选键列表提取值(部分匹配) + def extract_field(contact_info, candidate_keys): + for candidate in candidate_keys: + for key, value in contact_info.items(): + if candidate in key and value not in ["未知", ""]: + return value + return "" + + # 定义一个辅助函数用于提取 '投标保证金' + def extract_bid_bond(guarantee_info): + # 定义投标保证金的候选键 + bid_bond_candidates = ["投标保证金", "磋商保证金"] + + # 第一步:查找包含 "投标保证金" 或 "磋商保证金" 的键 + for candidate in bid_bond_candidates: + for key, value in guarantee_info.items(): + if candidate in key: + if isinstance(value, dict): + # 在嵌套字典中查找包含 "金额" 的键 + for sub_key, sub_value in value.items(): + if "金额" in sub_key and sub_value not in ["未知", ""]: + return sub_value + elif isinstance(value, str): + if "金额" in key and value not in ["未知", ""]: + return value + else: + # 如果 value 既不是 dict 也不是 str,忽略 + continue + + # 第二步:如果没有找到包含 "金额" 的键,尝试在所有键值中查找符合模式的值 + amount_pattern = re.compile(r'(?:\d{1,3}(?:[,,]\d{3})*(?:\.\d+)?|\d+(?:\.\d+)?|[\u4e00-\u9fff]+(?:\.\d+)?)\s*(?:元|万元)') + for key, value in guarantee_info.items(): + if isinstance(value, str): + match = amount_pattern.search(value) + if match: + return match.group() + elif isinstance(value, dict): + # 递归查找嵌套字典中的金额 + found_amount = extract_bid_bond(value) + if found_amount: + return found_amount + # 如果都没有找到,则返回空字符串 + return "" + + # 如果 '基础信息' 在 includes 中,则进行字段提取 + if "基础信息" in includes: + base_info = combined_data.get("基础信息", {}) + + # 定义所需字段的映射关系,暂时不包含'联系人'和'联系电话'以及'招标项目地点' + mapping = { + "招标项目名称": [["项目信息", "项目名称"], ["项目信息", "工程名称"]], + "招标项目编号": [["项目信息", "项目编号"], ["项目信息", "招标编号"]], + "开标时间": [["关键时间/内容", "开标时间"]], + "报名截止日期": [["关键时间/内容", "投标文件递交截止日期"]], + "招标项目预算": [["项目信息", "招标控制价"]], + "招标单位名称": [["招标人/代理信息", "招标人"]], + "招标公告地址": [["关键时间/内容", "信息公示媒介"], ["关键时间/内容", "评标结果公示媒介"]] + } + + # 提取并映射字段 + for new_key, paths in mapping.items(): + value = None + for path in paths: + value = get_nested(base_info, path) + if value: + break + extracted_info[new_key] = value if value else "" + + # 特殊处理 '招标项目地点' + # 在 '项目信息' 下查找包含 "地点" 的键 + project_info = base_info.get("项目信息", {}) + location_candidates = find_keys_containing(project_info, "地点") + if location_candidates: + # 选择第一个找到的地点 + extracted_info["招标项目地点"] = location_candidates[0] + else: + extracted_info["招标项目地点"] = "" + + # 特殊处理 '联系人' 和 '联系电话' + # 提取 '项目联系方式' + project_contact = get_nested(base_info, ["招标人/代理信息", "项目联系方式"], {}) + + # 提取 '招标人联系方式' + bidder_contact = get_nested(base_info, ["招标人/代理信息", "招标人联系方式"], {}) + + # 定义候选键列表,按优先级排序 + name_candidates = ["名称", "联系人", "招标"] + phone_candidates = ["电话", "手机", "联系方式"] + + # 提取 '联系人' + contact_names = [project_contact, bidder_contact] + contact_name = "" + for contact in contact_names: + extracted_name = extract_field(contact, name_candidates) + if extracted_name: + contact_name = extracted_name + break + extracted_info["联系人"] = contact_name + + # 提取 '联系电话' + contact_phones = [project_contact, bidder_contact] + contact_phone = "" + for contact in contact_phones: + extracted_phone = extract_field(contact, phone_candidates) + if extracted_phone: + contact_phone = extracted_phone + break + extracted_info["联系电话"] = contact_phone + + # 特殊处理 '投标保证金' + # 提取 '保证金相关' + guarantee_info = get_nested(base_info, ["保证金相关"], {}) + extracted_info["投标保证金"] = extract_bid_bond(guarantee_info) # 遍历原始字典的每一个键值对 - for key, value in data.items(): + for key, value in combined_data.items(): if key in includes: - # 如果键在includes列表中,直接保留这个键值对 - result[key] = value + if key == "基础信息": + # 已经处理 '基础信息',保留在处理后的数据中 + processed_data[key] = value + else: + # 直接保留包含在 includes 列表中的键值对 + processed_data[key] = value else: - # 如果键不在includes列表中,将这个键值对加入到'其他'分类中 - result["其他"][key] = value + # 将不在 includes 列表中的键值对加入到 '其他' 分类中 + processed_data["其他"][key] = value - # 如果'其他'分类没有任何内容,可以选择删除这个键 - if not result["其他"]: - del result["其他"] + # 如果 '其他' 分类没有任何内容,可以选择删除这个键 + if not processed_data["其他"]: + del processed_data["其他"] + + return processed_data, extracted_info - return result # 分段返回 -def process_and_stream(file_url,zb_type): +def process_and_stream(file_url, zb_type): """ - 下载文件并进行处理,支持工程标和货物标的处理。 + 下载文件并进行处理,支持工程标和货物标的处理。 - 参数: - - file_url (str): 文件的URL地址。 - - zb_type (int): 标的类型,1表示工程标,2表示货物标。 + 参数: + - file_url (str): 文件的URL地址。 + - zb_type (int): 标的类型,1表示工程标,2表示货物标。 - 返回: - - generator: 生成处理过程中的流式响应。 - """ + 返回: + - generator: 生成处理过程中的流式响应。 + """ logger = g.logger unique_id = g.unique_id output_folder = f"flask_app/static/output/{unique_id}" filename = "ztbfile" downloaded_filename = os.path.join(output_folder, filename) - # 下载文件 - downloaded = download_file(file_url, downloaded_filename) - if not downloaded: - logger.error("下载文件失败或不支持的文件类型") - error_response = { - 'message': 'File processing failed', - 'filename': None, - 'data': json.dumps({'error': 'File processing failed'}) + start_time = time.time() # 记录开始时间 + + try: + # 下载文件 + downloaded = download_file(file_url, downloaded_filename) + if not downloaded: + logger.error("下载文件失败或不支持的文件类型") + error_response = { + 'message': 'File processing failed', + 'filename': None, + 'data': json.dumps({'error': 'File processing failed'}) + } + yield f"data: {json.dumps(error_response)}\n\n" + return + + downloaded_filepath, file_type = downloaded + + # 检查文件类型 + if file_type == 4: + logger.error("不支持的文件类型") + error_response = { + 'message': 'Unsupported file type', + 'filename': None, + 'data': json.dumps({'error': 'Unsupported file type'}) + } + yield f"data: {json.dumps(error_response)}\n\n" + return + + logger.info("本地文件路径: " + downloaded_filepath) + + combined_data = {} + + # 根据zb_type选择调用的处理函数 + processing_functions = { + 1: engineering_bid_main, + 2: goods_bid_main } - yield f"data: {json.dumps(error_response)}\n\n" - return + processing_func = processing_functions.get(zb_type, engineering_bid_main) - downloaded_filepath, file_type = downloaded + # 从 processing_func 获取数据 + for data in processing_func(output_folder, downloaded_filepath, file_type, unique_id): + if not data.strip(): + logger.error("Received empty data, skipping JSON parsing.") + continue # Skip processing empty data - # 检查文件类型 - if file_type == 4: - logger.error("不支持的文件类型") - error_response = { - 'message': 'Unsupported file type', - 'filename': None, - 'data': json.dumps({'error': 'Unsupported file type'}) - } - yield f"data: {json.dumps(error_response)}\n\n" - return + try: + parsed_data = json.loads(data) + except json.JSONDecodeError as e: + logger.error(f"Failed to decode JSON: {e}") + logger.error(f"Data received: {data}") + continue # Skip data if JSON parsing fails - logger.info("本地文件路径: " + downloaded_filepath) + # 遍历 parsed_data 只提取内层内容进行合并 + for outer_key, inner_dict in parsed_data.items(): + if isinstance(inner_dict, dict): + combined_data.update(inner_dict) + # 日志记录已合并数据 - combined_data = {} + # 每次数据更新后,流式返回当前进度 + response = { + 'message': 'Processing', + 'filename': os.path.basename(downloaded_filepath), + 'data': data + } + yield f"data: {json.dumps(response, ensure_ascii=False)}\n\n" - # 根据zb_type选择调用的处理函数 - processing_functions = { - 1: engineering_bid_main, - 2: goods_bid_main - } - processing_func = processing_functions.get(zb_type, engineering_bid_main) - # 从 main_processing 获取数据 - for data in processing_func(output_folder, downloaded_filepath, file_type, unique_id): - if not data.strip(): - logger.error("Received empty data, skipping JSON parsing.") - continue # Skip processing empty data + # 日志记录已合并数据 + logger.info(f"合并后的数据: {json.dumps(combined_data, ensure_ascii=False, indent=4)}") + + # **保存 combined_data 到 output_folder 下的 'final_result.json'** + output_json_path = os.path.join(output_folder, 'final_result.json') + includes = ["基础信息", "资格审查", "商务评分", "技术评分", "无效标与废标项", "投标文件要求", "开评定标流程"] + final_result, extracted_info = post_processing(combined_data, includes) try: - parsed_data = json.loads(data) - except json.JSONDecodeError as e: - logger.error(f"Failed to decode JSON: {e}") - logger.error(f"Data received: {data}") - continue # Skip data if JSON parsing fails - # 遍历 parsed_data 只提取内层内容进行合并 - for outer_key, inner_dict in parsed_data.items(): - if isinstance(inner_dict, dict): - combined_data.update(inner_dict) - # 日志记录已合并数据 - # 每次数据更新后,流式返回当前进度 - response = { - 'message': 'Processing', + with open(output_json_path, 'w', encoding='utf-8') as json_file: + json.dump(final_result, json_file, ensure_ascii=False, indent=4) + logger.info(f"合并后的数据已保存到 '{output_json_path}'") + except IOError as e: + logger.error(f"保存JSON文件时出错: {e}") + + extracted_info_response = { + 'message': 'extracted_info', 'filename': os.path.basename(downloaded_filepath), - 'data': data + 'data': json.dumps(extracted_info, ensure_ascii=False) } - yield f"data: {json.dumps(response, ensure_ascii=False)}\n\n" - # 日志记录已合并数据 - logger.info(f"合并后的数据: {json.dumps(combined_data, ensure_ascii=False, indent=4)}") - # **保存 combined_data 到 output_folder 下的 'final_result.json'** - output_json_path = os.path.join(output_folder, 'final_result.json') - includes = ["基础信息", "资格审查", "商务评分", "技术评分", "无效标与废标项", "投标文件要求", "开评定标流程"] - result = post_processing(combined_data, includes) - try: - with open(output_json_path, 'w', encoding='utf-8') as json_file: - json.dump(result, json_file, ensure_ascii=False, indent=4) - logger.info(f"合并后的数据已保存到 '{output_json_path}'") - except IOError as e: - logger.error(f"保存JSON文件时出错: {e}") - # 最后发送合并后的完整数据 - complete_response = { - 'message': 'Combined data', - 'filename': os.path.basename(downloaded_filepath), - 'data': json.dumps(result, ensure_ascii=False) - } - yield f"data: {json.dumps(complete_response, ensure_ascii=False)}\n\n" - # 发送最终响应 - final_response = { - 'message': 'File uploaded and processed successfully', - 'filename': os.path.basename(downloaded_filepath), - 'data': 'END' - } - yield f"data: {json.dumps(final_response)}\n\n" + yield f"data: {json.dumps(extracted_info_response, ensure_ascii=False)}\n\n" + + # 最后发送合并后的完整数据 + complete_response = { + 'message': 'Combined_data', + 'filename': os.path.basename(downloaded_filepath), + 'data': json.dumps(final_result, ensure_ascii=False) + } + yield f"data: {json.dumps(complete_response, ensure_ascii=False)}\n\n" + + # 发送最终响应 + final_response = { + 'message': 'File uploaded and processed successfully', + 'filename': os.path.basename(downloaded_filepath), + 'data': 'END' + } + yield f"data: {json.dumps(final_response)}\n\n" + + finally: + end_time = time.time() # 记录结束时间 + duration = end_time - start_time + logger.info(f"Total processing time: {duration:.2f} seconds") + @app.route('/api/test_zbparse', methods=['POST']) def test_zbparse(): diff --git a/flask_app/main/test.py b/flask_app/main/test.py index 0e870ef..b0d5db3 100644 --- a/flask_app/main/test.py +++ b/flask_app/main/test.py @@ -1,51 +1,264 @@ +import json import re -from docx import Document -def preprocess_paragraphs(paragraphs): - processed = [] - index = 0 - while index < len(paragraphs): - current_text = paragraphs[index].text.strip() +combined_data = { + "基础信息": { + "招标人/代理信息": { + "招标人": "广水市公安局", + "招标人联系方式": { + "名称": "广水市公安局", + "地址": "广水市应山办事处应十大道 189号", + "联系电话": "未知" + }, + "招标代理机构": "湖北楚振捷工程项目管理有限公司", + "招标代理机构联系方式": { + "名称": "湖北楚振捷工程项目管理有限公司", + "地址": "广水市永阳一路 41号", + "联系方式": "0722-6256088" + }, +"项目联系方式":{ + "名称":"张三", + "联系电话":"11" + } + }, + "项目信息": { + "项目名称": "广水市公安局视频会议高清化改造项目", + "项目编号": "HBCZJ-2021-CS12", + "项目概况": "广水市公安局视频会议高清化改造项目", + "项目基本情况": { + "项目编号": "HBCZJ-2021-CS12", + "采购计划备案号": "2021-04-000399", + "项目名称": "广水市公安局视频会议高清化改造项目", + "采购方式": "竞争性磋商", + "预算金额": "192万元", + "最高限价": "192万元", + "采购需求": { + "项目概况及内容": "广水市公安局视频会议高清化改造项目。", + "招标范围": "广水市公安局视频会议高清化改造项目,具体内容见磋商文件第三章。", + "项目地点": "具体以合同约定为准。" + }, + "合同履行期限": "以合同签订为准。", + "本项目(是/否)接受联合体投标": "否", + "是否可采购进口产品": "否" + }, + "招标控制价": "192万元", + "投标竞争下浮率": "未知", + "是否允许分包": "未知", + "是否接受联合体投标": "否" + }, + "采购要求": { + "技术要求": "未提供", + "商务要求": "未提供", + "服务要求": "未提供", + "其他要求": "未提供" + }, + "关键时间/内容": { + "投标文件递交截止日期": "2021年 6月 18日 15点 00分", + "开标时间":"111", + "开标地点":"哈哈", + "澄清招标文件的截止时间": "未知", + "投标有效期": "90日历天", + "信息公示媒介": "中国湖北政府采购网:http://www.ccgp-hubei.gov.cn, 中国广水网:http://www.zggsw.gov.cn/", + "投标文件递交地点": "广水市公共资源交易中心五楼 501号开标室" - # 检测是否为空白行 - if current_text == '': - # 确保有前一行和后一行 - if index > 0 and index + 1 < len(paragraphs): - prev_text = paragraphs[index - 1].text.strip() - # print(prev_text) - next_text = paragraphs[index + 1].text.strip() - # print(next_text) - # print("------------------------------") - # 检查前一行是否不以指定标点结尾 - if not prev_text.endswith((',', ',', '。', '!', '?')): - # 检查后一行是否以序号开头 - if re.match(r'^(\d+(\s*\.\s*\d+)*)\s*', prev_text) and not re.match(r'^(\d+(\s*\.\s*\d+)*)\s*、',next_text) and len(prev_text)>30: - # 合并前一行和后一行 - merged_text = prev_text + next_text - # print(merged_text) - # print("---------------------------------") - if processed: - # 用合并后的文本替换已处理的前一行 - processed[-1] = merged_text - else: - processed.append(merged_text) - # 跳过后一行 - index += 2 + }, + "保证金相关": { + "质量保证金": "未知", + "履约保证金": "不提交", + "退还投标保证金": "/", + "投标保证金": { + "价格":"100,000,000.00 元" + } + }, + "其他信息": { + "是否退还投标文件": "否", + "投标费用承担": "供应商应承担所有与准备和参加磋商有关的费用,不论磋商的结果如何,采购人和采购代理机构均无义务和责任承担这些费用。", + "招标代理服务费": { + "支付人": "成交供应商", + "支付标准": "根据国家发展与改革委员会办公厅发改办价格【2003】857 号文的规定,经协商由成交供应商按国家发展和改革委员发改价格【2011】534号文规定货物类取费标准向采购代理机构支付招标代理服务费(包含“招标代理费、评标会务费、评标费”),如本项目各包服务费不足叁仟元则供应商按叁仟元支付服务费。", + "支付方式": "成交服务费由成交供应商在领取成交通知书的同时向代理机构支付,可使用现金或电汇办理", + "支付时间": "领取成交通知书的同时" + }, + "偏离": { + "偏离项要求": "供应商需在响应文件中提供《采购需求响应、偏离说明表/导读表》,具体格式见第六章响应文件格式中的49页。供应商需对采购需求中的各项技术参数和服务要求进行逐项响应,明确表明无偏离、正偏离或负偏离的情况。对于负偏离项,需详细说明偏离的具体内容及原因。", + "偏离项内容": "未知" + }, + "投标预备会": "不召开", + "踏勘现场": "不组织" + } + }, + "资格审查": { }, + "商务评分": { }, + "技术评分": { }, + "zhes":"11111" + # 其他键值对 +} +includes = ["基础信息", "资格审查", "商务评分", "技术评分", "无效标与废标项", "投标文件要求", "开评定标流程"] + +def post_processing(combined_data, includes): + # 初始化结果字典,预设'其他'分类为空字典 + processed_data = {"其他": {}} + + # 初始化提取的信息字典 + extracted_info = {} + + # 定义一个辅助函数用于获取嵌套字典中的值 + def get_nested(dic, keys, default=None): + for key in keys: + if isinstance(dic, dict): + dic = dic.get(key, default) + else: + return default + return dic + + # 定义一个辅助函数用于递归查找包含特定子字符串的键 + def find_keys_containing(dic, substring): + found_values = [] + if isinstance(dic, dict): + for key, value in dic.items(): + if substring in key: + found_values.append(value) + if isinstance(value, dict): + found_values.extend(find_keys_containing(value, substring)) + elif isinstance(value, list): + for item in value: + if isinstance(item, dict): + found_values.extend(find_keys_containing(item, substring)) + return found_values + + # 定义一个辅助函数用于根据候选键列表提取值(部分匹配) + def extract_field(contact_info, candidate_keys): + for candidate in candidate_keys: + for key, value in contact_info.items(): + if candidate in key and value not in ["未知", ""]: + return value + return "" + + # 定义一个辅助函数用于提取 '投标保证金' + def extract_bid_bond(guarantee_info): + # 定义投标保证金的候选键 + bid_bond_candidates = ["投标保证金", "磋商保证金"] + + # 第一步:查找包含 "投标保证金" 或 "磋商保证金" 的键 + for candidate in bid_bond_candidates: + for key, value in guarantee_info.items(): + if candidate in key: + if isinstance(value, dict): + # 在嵌套字典中查找包含 "金额" 的键 + for sub_key, sub_value in value.items(): + if "金额" in sub_key and sub_value not in ["未知", ""]: + return sub_value + elif isinstance(value, str): + if "金额" in key and value not in ["未知", ""]: + return value + else: + # 如果 value 既不是 dict 也不是 str,忽略 continue + + # 第二步:如果没有找到包含 "金额" 的键,尝试在所有键值中查找符合模式的值 + amount_pattern = re.compile(r'(?:\d{1,3}(?:[,,]\d{3})*(?:\.\d+)?|\d+(?:\.\d+)?|[\u4e00-\u9fff]+(?:\.\d+)?)\s*(?:元|万元)') + for key, value in guarantee_info.items(): + if isinstance(value, str): + match = amount_pattern.search(value) + if match: + return match.group() + elif isinstance(value, dict): + # 递归查找嵌套字典中的金额 + found_amount = extract_bid_bond(value) + if found_amount: + return found_amount + # 如果都没有找到,则返回空字符串 + return "" + + # 如果 '基础信息' 在 includes 中,则进行字段提取 + if "基础信息" in includes: + base_info = combined_data.get("基础信息", {}) + + # 定义所需字段的映射关系,暂时不包含'联系人'和'联系电话'以及'招标项目地点' + mapping = { + "招标项目名称": [["项目信息", "项目名称"], ["项目信息", "工程名称"]], + "招标项目编号": [["项目信息", "项目编号"], ["项目信息", "招标编号"]], + "开标时间": [["关键时间/内容", "开标时间"]], + "报名截止日期": [["关键时间/内容", "投标文件递交截止日期"]], + "招标项目预算": [["项目信息", "招标控制价"]], + "招标单位名称": [["招标人/代理信息", "招标人"]], + "招标公告地址": [["关键时间/内容", "信息公示媒介"], ["关键时间/内容", "评标结果公示媒介"]] + } + + # 提取并映射字段 + for new_key, paths in mapping.items(): + value = None + for path in paths: + value = get_nested(base_info, path) + if value: + break + extracted_info[new_key] = value if value else "" + + # 特殊处理 '招标项目地点' + # 在 '项目信息' 下查找包含 "地点" 的键 + project_info = base_info.get("项目信息", {}) + location_candidates = find_keys_containing(project_info, "地点") + if location_candidates: + # 选择第一个找到的地点 + extracted_info["招标项目地点"] = location_candidates[0] else: - # 非空白行,直接添加到处理后的列表 - processed.append(current_text) + extracted_info["招标项目地点"] = "" - index += 1 - return processed + # 特殊处理 '联系人' 和 '联系电话' + # 提取 '项目联系方式' + project_contact = get_nested(base_info, ["招标人/代理信息", "项目联系方式"], {}) -doc_path = 'D:\\flask_project\\flask_app\\static\\output\\015d997e-c32c-49d1-a611-a2e817ace6a1\\ztbfile.docx' -doc = Document(doc_path) + # 提取 '招标人联系方式' + bidder_contact = get_nested(base_info, ["招标人/代理信息", "招标人联系方式"], {}) -# 假设 doc 是您的文档对象 -processed_paragraphs = preprocess_paragraphs(doc.paragraphs) -for i in processed_paragraphs: - print("___________________________") - print(i) + # 定义候选键列表,按优先级排序 + name_candidates = ["名称", "联系人", "招标"] + phone_candidates = ["电话", "手机", "联系方式"] + + # 提取 '联系人' + contact_names = [project_contact, bidder_contact] + contact_name = "" + for contact in contact_names: + extracted_name = extract_field(contact, name_candidates) + if extracted_name: + contact_name = extracted_name + break + extracted_info["联系人"] = contact_name + + # 提取 '联系电话' + contact_phones = [project_contact, bidder_contact] + contact_phone = "" + for contact in contact_phones: + extracted_phone = extract_field(contact, phone_candidates) + if extracted_phone: + contact_phone = extracted_phone + break + extracted_info["联系电话"] = contact_phone + + # 特殊处理 '投标保证金' + # 提取 '保证金相关' + guarantee_info = get_nested(base_info, ["保证金相关"], {}) + extracted_info["投标保证金"] = extract_bid_bond(guarantee_info) + + # 遍历原始字典的每一个键值对 + for key, value in combined_data.items(): + if key in includes: + if key == "基础信息": + # 已经处理 '基础信息',保留在处理后的数据中 + processed_data[key] = value + else: + # 直接保留包含在 includes 列表中的键值对 + processed_data[key] = value + else: + # 将不在 includes 列表中的键值对加入到 '其他' 分类中 + processed_data["其他"][key] = value + + # 如果 '其他' 分类没有任何内容,可以选择删除这个键 + if not processed_data["其他"]: + del processed_data["其他"] + + return processed_data, extracted_info +res1,res2=post_processing(combined_data,includes) +print(json.dumps(res2,ensure_ascii=False,indent=4)) \ No newline at end of file diff --git a/flask_app/main/判断是否分包等.py b/flask_app/main/判断是否分包等.py index 70cad34..e2f6157 100644 --- a/flask_app/main/判断是否分包等.py +++ b/flask_app/main/判断是否分包等.py @@ -46,15 +46,15 @@ def merge_json_to_list(merged): merged['分包'] = '不允许' merged.pop('是否允许分包', None) - # 处理是否递交投标保证金或磋商保证金 guarantee_key = '是否递交投标保证金' if '是否递交投标保证金' in merged else '是否递交磋商保证金' if merged.get(guarantee_key) == '是': chosen_numbers.extend([2, 3]) - else: + merged.pop(guarantee_key, None) + elif merged.get(guarantee_key) == '否': guarantee_type = '投标' if '投标' in guarantee_key else '磋商' merged[f'{guarantee_type}保证金'] = '不提交' merged[f'退还{guarantee_type}保证金'] = '/' - merged.pop(guarantee_key, None) + merged.pop(guarantee_key, None) # 处理是否有履约保证金 if merged.get('是否提交履约保证金') == '是': @@ -82,10 +82,11 @@ def merge_json_to_list(merged): preparation_key = '是否召开投标预备会' if '是否召开投标预备会' in merged else '是否召开投标答疑会' if merged.get(preparation_key) == '是': chosen_numbers.append(7) - else: + merged.pop(preparation_key, None) + elif merged.get(preparation_key) == '否': meeting_type = '预备会' if '预备会' in preparation_key else '答疑会' - merged[f'投标{meeting_type}'] = '不召开' - merged.pop(preparation_key, None) + merged[f'投标{meeting_type}']='不召开' + merged.pop(preparation_key,None) if merged.get('是否允许偏离') == '是': chosen_numbers.append(8) diff --git a/flask_app/main/基础信息整合.py b/flask_app/main/基础信息整合.py index 348cc53..5d14c64 100644 --- a/flask_app/main/基础信息整合.py +++ b/flask_app/main/基础信息整合.py @@ -18,11 +18,13 @@ def aggregate_basic_info(baseinfo_list): - dict: 合并和分类后的基础信息字典。 """ key_groups = { - "招标人/代理信息": ["招标人", "招标人联系方式", "招标代理机构", "招标代理机构联系方式","招标代理服务费"], + "招标人/代理信息": ["招标人", "招标人联系方式", "招标代理机构", "招标代理机构联系方式"], "项目信息": ["项目名称", "招标编号", "项目概况", "招标范围", "招标控制价", "投标竞争下浮率"], "关键时间/内容": [ "投标文件递交截止日期", - "递交方式", + "投标文件递交方式", + "开标时间", + "开标地点", "投标人要求澄清招标文件的截止时间", "投标有效期", "评标结果公示媒介" @@ -30,8 +32,9 @@ def aggregate_basic_info(baseinfo_list): "保证金相关": ["质量保证金", "退还投标保证金"], "其他信息": [ "重新招标、不再招标和终止招标", + "投标费用承担", + "招标代理服务费", "是否退还投标文件", - "投标费用承担" ] } @@ -60,11 +63,17 @@ def aggregate_basic_info(baseinfo_list): def dynamic_key_handling(key_groups, detected_keys): # 检查和调整键组配置 for key in detected_keys: - if "投标保证金" in key or "履约保证金" in key: - key_groups["保证金相关"].append(key) - elif "是否接受联合体" in key: - key_groups["项目信息"].append(key) - elif "联合体投标要求" in key: + # 处理“保证金相关”组,插到"质量保证金"前 + if "保证金" in key: + group = key_groups["保证金相关"] + insert_before = "质量保证金" + if insert_before in group: + index = group.index(insert_before) + if key not in group: # 避免重复插入 + group.insert(index, key) + else: + group.append(key) # 如果没有找到特定键,则追加到末尾 + elif "联合体" in key: key_groups["项目信息"].append(key) elif "分包" in key: key_groups["项目信息"].append(key) diff --git a/flask_app/main/多线程提问.py b/flask_app/main/多线程提问.py index 9a2bf34..f3dca74 100644 --- a/flask_app/main/多线程提问.py +++ b/flask_app/main/多线程提问.py @@ -34,14 +34,33 @@ prom = '请记住以下材料,他们对回答问题有帮助,请你简洁准 def read_questions_from_file(file_path): questions = [] + current_question = "" + current_number = 0 + with open(file_path, 'r', encoding='utf-8') as file: for line in file: line = line.strip() - # 使用正则表达式匹配以数字开头,后接一个点的行 - if re.match(r'\d+\.', line): - # 从点后分割并去除前后空格获取问题部分 - question = line.split('.', 1)[1].strip() - questions.append(question) + if not line: # 跳过空行 + continue + + # 检查是否是新的问题编号 + match = re.match(r'^(\d+)\.', line) + if match: + # 如果有之前的问题,保存它 + if current_question: + questions.append(current_question.strip()) + + # 开始新的问题 + current_number = int(match.group(1)) + current_question = line.split('.', 1)[1].strip() + "\n" + else: + # 继续添加到当前问题 + current_question += line + "\n" + + # 添加最后一个问题 + if current_question: + questions.append(current_question.strip()) + return questions @@ -258,40 +277,40 @@ def multi_threading(queries, knowledge_name="", file_id="", llm_type=1): # 检查是否所有结果都是 None if all(result is None for result in results): return [] + # 过滤掉None值 + results = [r for r in results if r is not None] # 返回一个保证是列表的结构 return results if __name__ == "__main__": - # start_time=time.time() + start_time=time.time() # # 读取问题列表 - # questions =read_questions_from_file('../static/提示词/前两章提问总结.txt') - # for i in questions: - # print(i) - # knowledge_name = "招标解析5word" - # llm_type=1 - # results = multi_threading(questions, knowledge_name) - # end_time = time.time() + baseinfo_file_path = 'D:\\flask_project\\flask_app\\static\\提示词\\前两章提问总结.txt' + questions =read_questions_from_file(baseinfo_file_path) + knowledge_name = "招标解析5word" + llm_type=1 + results = multi_threading(questions, knowledge_name) + end_time = time.time() + if not results: + print("errror!") + else: + print("elapsed time:"+str(end_time-start_time)) + # 打印结果 + for question, response in results: + print(f"Response: {response}") + + # file_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\6.2定版视频会议磋商文件(1)\\6.2定版视频会议磋商文件_1-21.pdf" + # file_id = upload_file(file_path) + # questions=["该招标文件的项目名称是?项目编号(或招标编号)是?采购人(或招标人)是?采购代理机构(或招标代理机构)是?请按json格式给我提供信息,键名分别是'项目名称','项目编号','采购人','采购代理机构',若存在未知信息,在对应的键值中填'未知'。","该招标文件的项目概况是?项目基本情况是?请按json格式给我提供信息,键名分别为'项目概况','项目基本情况',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,而嵌套键值必须与原文保持一致,若存在未知信息,在对应的键值中填'未知'。"] + # results=multi_threading(questions,"",file_id,2) #1代表使用百炼rag 2代表使用qianwen-long # if not results: # print("errror!") # else: - # print("elapsed time:"+str(end_time-start_time)) # # 打印结果 # for question, response in results: # print(f"Question: {question}") # print(f"Response: {response}") - file_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\6.2定版视频会议磋商文件(1)\\6.2定版视频会议磋商文件_1-21.pdf" - file_id = upload_file(file_path) - questions=["该招标文件的项目名称是?项目编号(或招标编号)是?采购人(或招标人)是?采购代理机构(或招标代理机构)是?请按json格式给我提供信息,键名分别是'项目名称','项目编号','采购人','采购代理机构',若存在未知信息,在对应的键值中填'未知'。","该招标文件的项目概况是?项目基本情况是?请按json格式给我提供信息,键名分别为'项目概况','项目基本情况',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,而嵌套键值必须与原文保持一致,若存在未知信息,在对应的键值中填'未知'。"] - results=multi_threading(questions,"",file_id,2) #1代表使用百炼rag 2代表使用qianwen-long - if not results: - print("errror!") - else: - # 打印结果 - for question, response in results: - print(f"Question: {question}") - print(f"Response: {response}") - # ques=["关于'资格要求',本采购文件第一章第二款要求的内容是怎样的?请按json格式给我提供信息,键名为'资格要求',而键值需要完全与原文保持一致,不要擅自总结、删减,如果存在未知信息,请在对应键值处填'未知'。"] # # ques=["该招标文件的工程名称(项目名称)是?招标编号是?招标人是?招标代理机构是?请按json格式给我提供信息,键名分别是'工程名称','招标编号','招标人','招标代理机构',若存在未知信息,在对应的键值中填'未知'。","该招标文件的工程概况(或项目概况)是?招标范围是?请按json格式给我提供信息,键名分别为'工程概况','招标范围',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。"] # results = multi_threading(ques, "6.2视频会议docx") diff --git a/flask_app/main/无效标和废标和禁止投标整合.py b/flask_app/main/无效标和废标和禁止投标整合.py index 6f9e3e3..c4e1a56 100644 --- a/flask_app/main/无效标和废标和禁止投标整合.py +++ b/flask_app/main/无效标和废标和禁止投标整合.py @@ -297,7 +297,7 @@ def handle_query(file_path, user_query, output_file, result_key, keywords, trunc file_id = upload_file(output_file) qianwen_ans = qianwen_long(file_id, user_query) num_list = process_string_list(qianwen_ans) - print(num_list) + print(result_key+"选中的序号:"+str(num_list)) for index in num_list: if index - 1 < len(qianwen_txt): diff --git a/flask_app/static/提示词/初版基础提示词.txt b/flask_app/static/提示词/初版基础提示词.txt new file mode 100644 index 0000000..6e4dda3 --- /dev/null +++ b/flask_app/static/提示词/初版基础提示词.txt @@ -0,0 +1,33 @@ +1.该招标文件的项目名称(或工程名称)是?招标编号(或项目编号)是?招标人是?招标代理机构是?请按json格式给我提供信息,键名分别是'项目名称','招标编号','招标人','招标代理机构',若存在未知信息,在对应的键值中填'未知'。 + +#该招标文件的项目概况(或工程概况)是?招标范围是?招标控制价(可指代投标限价、投资概算金额、工程概算金额、合同估算价,但非监理费用)是?该项目的计划工期(监理服务期)是?该项目是否接受联合体投标?请按json格式给我提供信息,键名分别为'工程概况','招标范围','招标控制价','计划工期','是否接受联合体投标',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 +2.该招标文件的工程概况(或项目概况)是?招标范围是?请按json格式给我提供信息,键名分别为'项目概况','招标范围',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 + +3.该招标文件的招标控制价(可指代投标限价、投资概算金额、工程概算金额、合同估算价,但非监理费用)是?请按json格式给我提供信息,键名为'招标控制价',若存在未知信息,在对应的键值中填'未知'。 + +4.投标文件递交截止日期是?递交方式是?请按json格式给我提供信息,键名分别是'投标文件递交截止日期','投标文件递交方式',若存在未知信息,在对应的键值中填'未知'。 + +5.招标人和招标代理机构的联系方式是?请按json格式给我提供信息,键名分别是'招标人联系方式','招标代理机构联系方式',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 + +##(三个问题分开问)根据第二章投标人须知的内容,该招标文件是否允许分包? 是否需要递交投标保证金?是否有履约保证金(履约担保)?是否有招标代理服务费?你需要留意☑后的内容。请按json格式给我提供信息,键名分别为'是否允许分包','是否递交投标保证金','是否有履约保证金','是否有招标代理服务费',键值仅限于'是','否','未知'。可以一起问,设置摘取分段为8,仍存在问题:pdf转word文件打勾符号可能会无法正常显示,解决思路1:根据原pdf进行提取 + +6.该招标文件的评标结果(定标候选人)公示媒介在哪?请按json格式给我提供信息,键名是'评标结果公示媒介',若存在未知信息,在对应的键值中填'未知'。 + +7.该招标文件的投标竞争下浮率是多少?请按json格式给我提供信息,键名是'投标竞争下浮率',若存在未知信息,在对应的键值中填'未知'。 + +#11.该招标文件的投标竞争下浮率是多少?若请按json格式给我提供信息,键名是'投标竞争下浮率',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 + +8.该项目的投标有效期是什么?请按json格式给我提供信息,键名是'投标有效期',若存在未知信息,在对应的键值中填'未知'。 +#该招标中对于实质性要求(废标项)的内容有哪些?规定投标人不得存在的情形有哪些?文件中提及的否决和无效投标情形有哪些?请以json格式返回结果,键名分别'实质性要求','不得存在的情形','否决和无效投标情形',若存在未知信息,请在对应键值中填'未知',你的回答一切以原文内容为准,不可改动。 + +#8.该招标文件的电子招标文件获取方式是?请按原文段落全部完整内容回答,以json的格式给我提供信息,键名是'电子招标文件获取方式',若存在未知信息,在对应的键值中填'未知'。 + +9.该招标文件中对投标人准备和参加投标活动发生的费用是如何规定的?请以json的格式给我提供信息,键名是'投标费用承担',若存在未知信息,在对应的键值中填'未知'。 + +10.求澄清的招标文件截止时间是?请以json的格式给我提供信息,键名是'投标人要求澄清招标文件的截止时间',若存在未知信息,在对应的键值中填'未知'。 + +11.该文档要求扣留的质量保证金百分比是多少,请以json格式给我提供信息,键名为'质量保证金',如果没有则以'未知'填充。 + +12.该项目是否接受联合体投标?请按json格式给我提供信息,键名为'是否接受联合体投标','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 + +13.该项目的开标时间(开启时间)和开标地点是?请按json格式给我提供信息,键名为'开标时间'和'开标地点',若存在未知信息,在对应的键值中填'未知'。 diff --git a/flask_app/static/提示词/前两章提问总结.txt b/flask_app/static/提示词/前两章提问总结.txt index aa389c3..314cdd1 100644 --- a/flask_app/static/提示词/前两章提问总结.txt +++ b/flask_app/static/提示词/前两章提问总结.txt @@ -1,6 +1,5 @@ 1.该招标文件的项目名称(或工程名称)是?招标编号(或项目编号)是?招标人是?招标代理机构是?请按json格式给我提供信息,键名分别是'项目名称','招标编号','招标人','招标代理机构',若存在未知信息,在对应的键值中填'未知'。 -#该招标文件的项目概况(或工程概况)是?招标范围是?招标控制价(可指代投标限价、投资概算金额、工程概算金额、合同估算价,但非监理费用)是?该项目的计划工期(监理服务期)是?该项目是否接受联合体投标?请按json格式给我提供信息,键名分别为'工程概况','招标范围','招标控制价','计划工期','是否接受联合体投标',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 2.该招标文件的工程概况(或项目概况)是?招标范围是?请按json格式给我提供信息,键名分别为'项目概况','招标范围',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 3.该招标文件的招标控制价(可指代投标限价、投资概算金额、工程概算金额、合同估算价,但非监理费用)是?请按json格式给我提供信息,键名为'招标控制价',若存在未知信息,在对应的键值中填'未知'。 @@ -9,20 +8,11 @@ 5.招标人和招标代理机构的联系方式是?请按json格式给我提供信息,键名分别是'招标人联系方式','招标代理机构联系方式',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 -##8.该项目的开标时间和地点是?请按json格式给我提供信息,键名为'开标时间'和'开标地点',若存在未知信息,在对应的键值中填'未知'。 - -##(三个问题分开问)根据第二章投标人须知的内容,该招标文件是否允许分包? 是否需要递交投标保证金?是否有履约保证金(履约担保)?是否有招标代理服务费?你需要留意☑后的内容。请按json格式给我提供信息,键名分别为'是否允许分包','是否递交投标保证金','是否有履约保证金','是否有招标代理服务费',键值仅限于'是','否','未知'。可以一起问,设置摘取分段为8,仍存在问题:pdf转word文件打勾符号可能会无法正常显示,解决思路1:根据原pdf进行提取 - 6.该招标文件的评标结果(定标候选人)公示媒介在哪?请按json格式给我提供信息,键名是'评标结果公示媒介',若存在未知信息,在对应的键值中填'未知'。 7.该招标文件的投标竞争下浮率是多少?请按json格式给我提供信息,键名是'投标竞争下浮率',若存在未知信息,在对应的键值中填'未知'。 -#11.该招标文件的投标竞争下浮率是多少?若请按json格式给我提供信息,键名是'投标竞争下浮率',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 - 8.该项目的投标有效期是什么?请按json格式给我提供信息,键名是'投标有效期',若存在未知信息,在对应的键值中填'未知'。 -#该招标中对于实质性要求(废标项)的内容有哪些?规定投标人不得存在的情形有哪些?文件中提及的否决和无效投标情形有哪些?请以json格式返回结果,键名分别'实质性要求','不得存在的情形','否决和无效投标情形',若存在未知信息,请在对应键值中填'未知',你的回答一切以原文内容为准,不可改动。 - -#8.该招标文件的电子招标文件获取方式是?请按原文段落全部完整内容回答,以json的格式给我提供信息,键名是'电子招标文件获取方式',若存在未知信息,在对应的键值中填'未知'。 9.该招标文件中对投标人准备和参加投标活动发生的费用是如何规定的?请以json的格式给我提供信息,键名是'投标费用承担',若存在未知信息,在对应的键值中填'未知'。 @@ -31,3 +21,5 @@ 11.该文档要求扣留的质量保证金百分比是多少,请以json格式给我提供信息,键名为'质量保证金',如果没有则以'未知'填充。 12.该项目是否接受联合体投标?请按json格式给我提供信息,键名为'是否接受联合体投标','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 + +13.该项目的开标时间(开启时间)和开标地点是?请按json格式给我提供信息,键名为'开标时间'和'开标地点',若存在未知信息,在对应的键值中填'未知'。 diff --git a/flask_app/static/提示词/基本信息货物标.txt b/flask_app/static/提示词/基本信息货物标.txt index 30aa63f..eb17611 100644 --- a/flask_app/static/提示词/基本信息货物标.txt +++ b/flask_app/static/提示词/基本信息货物标.txt @@ -6,7 +6,22 @@ 4.投标文件(或响应文件)递交截止时间是?递交地点(或方式)是?请按json格式给我提供信息,键名分别是'投标文件递交截止日期','投标文件递交地点'(或'投标文件递交方式'),若存在未知信息,在对应的键值中填'未知'。 -5.采购人(招标人)和采购代理机构(或招标代理机构)的联系方式是?请按json格式给我提供信息,键名分别是'招标人联系方式','招标代理机构联系方式',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 +5.采购人(招标人)和采购代理机构(或招标代理机构)和项目的联系方式是?请按json格式给我提供信息,外层键名分别是'招标人联系方式','招标代理机构联系方式',"项目联系方式",嵌套键名至少包含"名称"和"联系电话",若还有其他字段则添加在后面,若存在未知信息,在对应的键值中填'未知'。示例输出如下: +{ + "招标人联系方式":{ + "名称":"广水市中医医院", + "联系电话":"13972990000", + "地址":"洪山街道" + }, + "招标代理机构联系方式":{ + "名称":"湖北众恒永业工程项目管理有限公司广水分公司", + "联系电话":"13972991234" + }, + "项目联系方式":{ + "名称":"张三", + "联系电话":"未知" + } +} 6.该招标文件的信息公示媒介在哪?请按json格式给我提供信息,键名是'信息公示媒介',若存在未知信息,在对应的键值中填'未知'。 @@ -24,6 +39,8 @@ 13.该招标文件中对投标文件中偏离项的要求或内容是怎样的?请以json格式给我提供信息,外层键名为'偏离',请不要回答具体的技术参数,若存在未知信息,在对应的键值中填'未知'。 +14.该项目的开标时间(或开启时间)和开标地点是?请按json格式给我提供信息,键名为'开标时间'和'开标地点',若存在未知信息,在对应的键值中填'未知'。 + diff --git a/flask_app/static/提示词/是否相关问题.txt b/flask_app/static/提示词/是否相关问题.txt index 71d5c11..a97c7b7 100644 --- a/flask_app/static/提示词/是否相关问题.txt +++ b/flask_app/static/提示词/是否相关问题.txt @@ -1,10 +1,15 @@ -#pdf提取之后的提示词,调用普通通译千问: -#请你依据以上信息回答,是否允许分包? 是否需要递交投标保证金?是否有履约保证金(履约担保)?是否有招标代理服务费?请按json格式给我提供信息,键名分别为'是否允许分包','是否递交投标保证金','是否有履约保证金','是否有招标代理服务费',键值仅限于'是','否','未知'。 1.该招标文件对于分包的要求是怎样的?请按json格式给我提供信息,键名为'分包'。 + 2.根据招标文件第二章投标人须知,该项目投标保证金需要缴纳金额是多少?到账截止时间是?缴纳形式是?请按json格式给我提供信息,外层键名为'投标保证金',嵌套键名分别为'缴纳金额','到账截止时间','缴纳形式',若存在多种缴纳形式,则在'缴纳形式'下以各种缴纳形式作为嵌套键名,再在对应的缴纳形式下嵌套写出缴纳步骤或要求或账户信息,请详细回答,不要遗漏原文信息。 + 3.该招标文件对于投标保证金的退还相关的规章办法是怎样的?请按json格式给我提供信息,键名为'退还投标保证金',若存在嵌套信息,嵌套内容键名以文档中对应字段命名。 + 4.根据投标人须知前附表,该项目对于履约保证金(担保)的要求中,它的履约担保形式是怎样的?它的履约担保金额是多少?请按json格式给我提供信息,外层键名为'履约保证金',嵌套键名分别是'履约担保形式','担保金额',若存在多种履约担保形式,则在'履约担保形式'下以各种履约担保形式作为嵌套键名,若存在未知信息,在对应的键值中填'未知'。 + 5.本项目的招标代理服务费由谁支付?支付标准是什么?支付方式是什么?支付时间是什么?请按json格式给我提供信息,外层键名为'招标代理服务费',嵌套键名分别是'支付人','支付标准','支付方式','支付时间',若存在未知信息,在对应的键值中填'未知'。 + 6.该招标文件对于踏勘现场是怎样的,踏勘时间和踏勘集中地点是?请以json格式给我提供信息,外层键名为'踏勘现场',嵌套键名分别是'踏勘时间','踏勘地点',若存在其他信息,新增嵌套键名'备注',填入其中,若存在未知信息,在对应的键值中填'未知'。 + 7.该招标文件对于投标预备会内容是怎样的,召开时间和召开地点是?请以json格式给我提供信息,外层键名为'投标预备会',嵌套键名分别是'召开时间','召开地方',若存在其他信息,新增嵌套键名'备注',填入其中,若存在未知信息,在对应的键值中填'未知'。 + 8.本项目可偏离的项目和范围是怎样的?请以json格式给我提供信息,外层键名为'偏离'。 \ No newline at end of file diff --git a/flask_app/static/提示词/是否相关问题货物标.txt b/flask_app/static/提示词/是否相关问题货物标.txt index d450beb..3f2fa81 100644 --- a/flask_app/static/提示词/是否相关问题货物标.txt +++ b/flask_app/static/提示词/是否相关问题货物标.txt @@ -1,9 +1,13 @@ -#pdf提取之后的提示词,调用普通通译千问: -#请你依据以上信息回答,是否允许分包? 是否需要递交投标保证金?是否有履约保证金(履约担保)?是否有招标代理服务费?请按json格式给我提供信息,键名分别为'是否允许分包','是否递交投标保证金','是否有履约保证金','是否有招标代理服务费',键值仅限于'是','否','未知'。 1.该招标文件对于分包的要求是怎样的?请按json格式给我提供信息,键名为'分包',若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。。 + 2.根据招标文件第二章投标人须知,该项目投标保证金(或磋商保证金)的内容或要求是什么?请按json格式给我提供信息,外层键名为"投标保证金"(或"磋商保证金"),若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 + 3.该招标文件对于投标保证金的退还相关的规章办法是怎样的?请按json格式给我提供信息,键名为'退还投标保证金',若存在嵌套信息,嵌套内容键名以文档中对应字段命名。 + 4.根据投标人须知前附表,该项目对于履约保证金(担保)的内容或要求是怎样的,请按json格式给我提供信息,外层键名为"履约保证金",若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 + 5.本项目的招标代理服务费(或中标服务费、成交服务费)的相关内容是怎样的,请按json格式给我提供信息,外层键名为'招标代理服务费',若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 + 6.该招标文件对于踏勘现场的内容或要求是怎样的,请按json格式给我提供信息,外层键名为"踏勘现场",若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 + 7.该招标文件对于投标预备会(或投标答疑会)内容是怎样的,请按json格式给我提供信息,外层键名为"投标预备会"(或"投标答疑会"),若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 diff --git a/flask_app/货物标/基础信息解析main.py b/flask_app/货物标/基础信息解析main.py index 1a81940..cad320c 100644 --- a/flask_app/货物标/基础信息解析main.py +++ b/flask_app/货物标/基础信息解析main.py @@ -22,20 +22,22 @@ def aggregate_basic_info(baseinfo_list): - dict: 合并和分类后的基础信息字典。 """ key_groups = { - "招标人/代理信息": ["招标人", "招标人联系方式", "招标代理机构", "招标代理机构联系方式"], + "招标人/代理信息": ["招标人", "招标人联系方式", "招标代理机构", "招标代理机构联系方式","项目联系方式"], "项目信息": ["项目名称", "项目编号", "项目概况", "项目基本情况", "招标控制价", "投标竞争下浮率"], "采购要求": ["技术要求","商务要求","服务要求","其他要求"], "关键时间/内容": [ "投标文件递交截止日期", + "开标时间", + "开标地点", "澄清招标文件的截止时间", "投标有效期", "信息公示媒介" ], "保证金相关": ["质量保证金"], "其他信息": [ - "是否退还投标文件", "投标费用承担", - "招标代理服务费" + "招标代理服务费", + "是否退还投标文件" ] } @@ -63,12 +65,17 @@ def aggregate_basic_info(baseinfo_list): def dynamic_key_handling(key_groups, detected_keys): # 检查和调整键组配置 for key in detected_keys: - # print(key) + # 处理“保证金相关”组 if "保证金" in key: - key_groups["保证金相关"].append(key) - elif "是否接受联合体" in key: - key_groups["项目信息"].append(key) - elif "联合体投标要求" in key: + group = key_groups["保证金相关"] + insert_before = "质量保证金" + if insert_before in group: + index = group.index(insert_before) + if key not in group: # 避免重复插入 + group.insert(index, key) + else: + group.append(key) # 如果没有找到特定键,则追加到末尾 + elif "联合体" in key: key_groups["项目信息"].append(key) elif "分包" in key: key_groups["项目信息"].append(key) @@ -79,7 +86,17 @@ def dynamic_key_handling(key_groups, detected_keys): elif "偏离" in key: key_groups["其他信息"].append(key) elif "递交方式" in key or "递交地点" in key: - key_groups["关键时间/内容"].append(key) + group = key_groups["关键时间/内容"] + insert_after = "投标文件递交截止日期" + if insert_after in group: + index = group.index(insert_after) + # 确保新键不重复 + if key not in group: + group.insert(index + 1, key) + else: + # 如果“投标文件递交截止日期”不存在,则追加到末尾 + if key not in group: + group.append(key) def get_base_info(baseinfo_file_path): file_id = upload_file(baseinfo_file_path) @@ -145,7 +162,7 @@ def combine_basic_info(baseinfo_file_path, procurement_file_path): if __name__ == "__main__": start_time=time.time() - baseinfo_file_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub\\merged_baseinfo.pdf" + baseinfo_file_path = "C:\\Users\\Administrator\\Desktop\\货物标\\truncate_all\\ztbfile_merged_baseinfo\\ztbfile_merged_baseinfo_3-31.pdf" # procurement_file_path = "C:\\Users\\Administrator\\Desktop\\fsdownload\\b4601ea1-f087-4fa2-88ae-336ad4d8e1e9\\tmp\\ztbfile_procurement.pdf" procurement_file_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub\\广水农商行门禁控制主机及基础验证设备采购项目——磋商文件(定稿)(三次)_procurement.pdf" res = combine_basic_info(baseinfo_file_path, procurement_file_path) diff --git a/flask_app/货物标/技术要求提取.py b/flask_app/货物标/技术要求提取.py index 6aca425..a7ccf8f 100644 --- a/flask_app/货物标/技术要求提取.py +++ b/flask_app/货物标/技术要求提取.py @@ -111,7 +111,6 @@ def get_technical_requirements(file_id): # 更新原始采购需求字典 combine_and_update_results(cleaned_res['技术要求'], technical_requirements_combined_res) final_res = postprocess(cleaned_res) - print("更新后的采购需求处理完成.") # 输出最终的 JSON 字符串 return final_res @@ -133,14 +132,15 @@ def test_all_files_in_folder(input_folder, output_folder): output_file_path = os.path.join(output_folder, os.path.splitext(filename)[0] + '.json') # 保存JSON结果到文件 with open(output_file_path, 'w', encoding='utf-8') as json_file: - json_file.write(json_result) + json.dump(json_result, json_file, ensure_ascii=False, indent=4) print(f"结果已保存到: {output_file_path}") except Exception as e: print(f"处理文件 {file_path} 时出错: {e}") if __name__ == "__main__": - truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub\\广水农商行门禁控制主机及基础验证设备采购项目——磋商文件(定稿)(三次)_procurement.pdf" + # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub\\广水农商行门禁控制主机及基础验证设备采购项目——磋商文件(定稿)(三次)_procurement.pdf" + truncate_file="D:\\flask_project\\flask_app\\static\\output\\71334d6b-9478-4e4d-abe0-a5cb3100d681\\ztbfile_procurement.pdf" file_id = upload_file(truncate_file) res=get_technical_requirements(file_id) json_string = json.dumps(res, ensure_ascii=False, indent=4) diff --git a/flask_app/货物标/投标人须知正文提取指定内容货物标版.py b/flask_app/货物标/投标人须知正文提取指定内容货物标版.py index 451ed97..c21b1f4 100644 --- a/flask_app/货物标/投标人须知正文提取指定内容货物标版.py +++ b/flask_app/货物标/投标人须知正文提取指定内容货物标版.py @@ -213,11 +213,11 @@ def extract_from_notice(clause_path, type): final_result=process_nested_data(transformed_data) return final_result -#TODO: 再审视一下zbtest20的处理是否合理 if __name__ == "__main__": - file_path = 'C:\\Users\\Administrator\\Desktop\\招标文件\\' + file_path = 'D:\\flask_project\\flask_app\\static\\output\\87f48f9c-e6ee-4dc1-a981-5a10085c4635\\tmp\\clause1.json' + # file_path = 'D:\\flask_project\\flask_app\\static\\output\\87f48f9c-e6ee-4dc1-a981-5a10085c4635\\clause1.json' try: - res = extract_from_notice(file_path, 1) # 可以改变此处的 type 参数测试不同的场景 + res = extract_from_notice(file_path, 2) # 可以改变此处的 type 参数测试不同的场景 res2=json.dumps(res,ensure_ascii=False,indent=4) print(res2) except ValueError as e: diff --git a/flask_app/货物标/投标人须知正文条款提取成json文件货物标版.py b/flask_app/货物标/投标人须知正文条款提取成json文件货物标版.py index fabedba..5983ffd 100644 --- a/flask_app/货物标/投标人须知正文条款提取成json文件货物标版.py +++ b/flask_app/货物标/投标人须知正文条款提取成json文件货物标版.py @@ -167,6 +167,15 @@ def parse_text_by_heading(text): current_content = [line_content] append_newline = len(new_key.rstrip('.').split('.')) <= 2 last_main_number = new_key.split('.')[0] + # if current_key is None or (current_key != new_key and ( + # len(current_content) == 0 or current_content[-1][-1] != '第')): + # if current_key is not None: + # content_string = ''.join(current_content).strip() + # data[current_key] = data.get(current_key, '') + content_string.replace(' ', '') + # current_key = new_key + # current_content = [line_content] + # append_newline = len(new_key.rstrip('.').split('.')) <= 2 + # last_main_number = new_key.split('.')[0] else: append_newline = handle_content_append(current_content, line_stripped, append_newline, keywords) elif dot_match: @@ -321,17 +330,6 @@ def clean_content(content): return cleaned_content -# def convert_to_json(file_path, start_word, end_phrases): -# if file_path.endswith('.docx'): -# text = extract_text_from_docx(file_path) -# elif file_path.endswith('.pdf'): -# text = extract_text_from_pdf(file_path,start_word,end_phrases) -# else: -# raise ValueError("Unsupported file format") -# # print(text) -# parsed_data = parse_text_by_heading(text) -# return parsed_data - def convert_clause_to_json(file_path,output_folder,type=1,suffix_counter="1.json"): if not os.path.exists(file_path): print(f"The specified file does not exist: {file_path}") @@ -377,15 +375,16 @@ def process_folder(input_folder, output_folder): print(f"Error processing {file_name}: {e}") #TODO:'C:\\Users\\Administrator\\Desktop\\货物标\\output4\\广水农商行门禁控制主机及基础验证设备采购项目——磋商文件(定稿)(三次)_tobidders_notice_part2.pdf' PYPDF2库读取有遗漏 -#TODO:标题'一'应该越来越大,与'1.1'的逻辑分开'; #TODO:911904c3-4d6e-47e0-acd8-b05e582209cb文件夹运行有问题,百炼出错了 + +#TODO: 投标人须知正文这块,序号可能是乱序的,或许可以删除判断序号大小的逻辑,只要出现在开头的序号就作为新的键 eg:2-招标文件。目前将这种情况当特殊处理 if __name__ == "__main__": # file_path = 'D:\\flask_project\\flask_app\\static\\output\\cfd4959d-5ea9-4112-8b50-9e543803f029\\ztbfile_tobidders_notice.pdf' - file_path='C:\\Users\\Administrator\\Desktop\\货物标\\output5\\094定稿-湖北工业大学轻武器模拟射击设备采购项目招标文件_notice.pdf' + file_path='D:\\flask_project\\flask_app\\static\\output\\87f48f9c-e6ee-4dc1-a981-5a10085c4635\\ztbfile_tobidders_notice_part2.pdf' # file_path = 'C:\\Users\\Administrator\\Desktop\\货物标\\output4\\2-招标文件(2020年广水市中小学教师办公电脑系统及多媒体“班班通”设备采购安装项目)_tobidders_notice_part2.pdf' - output_folder = 'C:\\Users\\Administrator\\Desktop\\货物标\\output5\\tmp2' + output_folder = 'D:\\flask_project\\flask_app\\static\\output\\87f48f9c-e6ee-4dc1-a981-5a10085c4635\\tmp' try: - output_path = convert_clause_to_json(file_path,output_folder,2) + output_path = convert_clause_to_json(file_path,output_folder,1) print(f"Final JSON result saved to: {output_path}") except ValueError as e: print("Error:", e) diff --git a/flask_app/货物标/货物标截取pdf.py b/flask_app/货物标/货物标截取pdf.py index 69a90c1..d69c03e 100644 --- a/flask_app/货物标/货物标截取pdf.py +++ b/flask_app/货物标/货物标截取pdf.py @@ -438,18 +438,20 @@ def save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_fo return output_pdf_path #合并封面+招标公告+投标人须知前附表+须知正文 -def merge_selected_pdfs(output_folder, truncate_files, output_path): +def merge_selected_pdfs(output_folder, truncate_files, output_path, base_file_name): """ - 合并 output_folder 中以 _before 结尾的 PDF 文件,以及 truncate_files 中的指定文件。 + 合并 output_folder 中以 {base_file_name}_before.pdf 结尾的 PDF 文件,以及 truncate_files 中的指定文件。 参数: - - output_folder (str): 包含以 _before 结尾的 PDF 文件的文件夹路径。 + - output_folder (str): 包含以 {base_file_name}_before.pdf 结尾的 PDF 文件的文件夹路径。 - truncate_files (list): 包含 PDF 文件路径的列表。 - output_path (str): 合并后的 PDF 文件保存路径。 + - base_file_name (str): 用于匹配文件名的基础名称。 """ - # 1. 查找 output_folder 中以 _before.pdf 结尾的 PDF 文件 - before_pdfs = glob.glob(os.path.join(output_folder, '*_before.pdf')) - print(f"找到 {len(before_pdfs)} 个以 '_before.pdf' 结尾的文件。") + # 1. 查找 output_folder 中以 {base_file_name}_before.pdf 结尾的 PDF 文件 + pattern = f'*{base_file_name}_before.pdf' + before_pdfs = glob.glob(os.path.join(output_folder, pattern)) + print(f"找到 {len(before_pdfs)} 个以 '{base_file_name}_before.pdf' 结尾的文件。") # 2. 获取 truncate_files 中指定的文件(索引5、3、4) selected_indices = [5, 3, 4] # 注意索引从0开始 @@ -534,22 +536,29 @@ 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) -def truncate_pdf_multiple(input_path, output_folder): +def truncate_pdf_multiple(pdf_path, output_folder): + base_file_name = os.path.splitext(os.path.basename(pdf_path))[0] truncate_files = [] for selection in range(1, 6): - files = truncate_pdf_main(input_path, output_folder, selection) - truncate_files.extend(files) - merged_output_path =os.path.join(output_folder,"merged_baseinfo.pdf") - merge_selected_pdfs(output_folder,truncate_files,merged_output_path) - print(merged_output_path) - truncate_files.append(merged_output_path) + files = truncate_pdf_main(pdf_path, output_folder, selection) + if files: + truncate_files.extend(files) + + 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}") + else: + print(f"没有文件需要合并 for {pdf_path}") + return truncate_files # TODO:交通智能系统和招标(1)(1)文件有问题 sele=4的时候excludsion有问题 if __name__ == "__main__": - input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\094定稿-湖北工业大学轻武器模拟射击设备采购项目招标文件.pdf" - output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub" + input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\6.2定版视频会议磋商文件.pdf" + output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\truncate_all" files = truncate_pdf_multiple(input_path, output_folder) print(files) diff --git a/flask_app/货物标/货物标解析main.py b/flask_app/货物标/货物标解析main.py index 4622394..85df0c1 100644 --- a/flask_app/货物标/货物标解析main.py +++ b/flask_app/货物标/货物标解析main.py @@ -9,7 +9,6 @@ from flask_app.货物标.投标人须知正文提取指定内容货物标版 imp from flask_app.货物标.货物标截取pdf import truncate_pdf_multiple from concurrent.futures import ThreadPoolExecutor import concurrent.futures -from flask_app.main.知识库操作 import addfileToKnowledge, deleteKnowledge from flask_app.货物标.投标人须知正文条款提取成json文件货物标版 import convert_clause_to_json from flask_app.货物标.无效标和废标和禁止投标整合main import combine_find_invalid from flask_app.货物标.资格审查main import combine_qualification_review @@ -211,7 +210,7 @@ def goods_bid_main(output_folder, file_path, file_type, unique_id): # 删除知识索引 # deleteKnowledge(index) - +#TODO:目前的无效标这块的键值都删去空格了,所有的键名都删去空格 if __name__ == "__main__": output_folder = "flask_app/static/output/zytest1"