From dd06b7b199c3c0030226b1468e57b8f0d932d00f Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Tue, 15 Oct 2024 20:57:58 +0800 Subject: [PATCH] =?UTF-8?q?10.15=E8=B4=A7=E7=89=A9=E6=A0=87=E5=AE=8C?= =?UTF-8?q?=E6=95=B4=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flask_app/main/json_utils.py | 2 +- flask_app/main/test.py | 129 ++++------ flask_app/main/判断是否分包等.py | 36 +-- flask_app/main/商务标技术标整合.py | 2 - flask_app/main/基础信息整合.py | 44 ++-- flask_app/main/招标文件解析.py | 2 - flask_app/main/无效标和废标和禁止投标整合.py | 17 +- flask_app/main/通义千问long.py | 2 +- flask_app/static/提示词/前两章提问总结.txt | 10 +- flask_app/static/提示词/基本信息货物标.txt | 14 +- .../static/提示词/是否相关问题货物标.txt | 9 + flask_app/货物标/商务服务其他要求提取.py | 6 +- flask_app/货物标/基础信息解析main.py | 221 ++++++++++-------- flask_app/货物标/技术要求提取.py | 26 ++- flask_app/货物标/提取采购需求main.py | 5 +- flask_app/货物标/提示词/prompt1.txt | 1 + .../货物标/无效标和废标和禁止投标整合main.py | 74 +++++- flask_app/货物标/评分标准提取main.py | 3 +- flask_app/货物标/货物标截取pdf.py | 146 +++++++++--- flask_app/货物标/货物标解析main.py | 143 +++++++----- flask_app/货物标/资格审查main.py | 47 ++-- 21 files changed, 571 insertions(+), 368 deletions(-) create mode 100644 flask_app/static/提示词/是否相关问题货物标.txt diff --git a/flask_app/main/json_utils.py b/flask_app/main/json_utils.py index efbf3b3..1b98db9 100644 --- a/flask_app/main/json_utils.py +++ b/flask_app/main/json_utils.py @@ -101,7 +101,7 @@ def rename_outer_key(original_data,new_key): # 创建一个新的字典,使用新的键名 new_data = {new_key: original_value} - return json.dumps(new_data,ensure_ascii=False) + return new_data def transform_json_values(data): if isinstance(data, dict): return {key: transform_json_values(value) for key, value in data.items()} diff --git a/flask_app/main/test.py b/flask_app/main/test.py index a9f34be..0e870ef 100644 --- a/flask_app/main/test.py +++ b/flask_app/main/test.py @@ -1,92 +1,51 @@ -import json -from collections import defaultdict +import re -def parse_json_with_duplicates(json_string): - """ - 解析具有重复键的 JSON 字符串,将所有重复的键值对存储为列表。 +from docx import Document +def preprocess_paragraphs(paragraphs): + processed = [] + index = 0 + while index < len(paragraphs): + current_text = paragraphs[index].text.strip() - Args: - json_string (str): 需要解析的 JSON 字符串。 + # 检测是否为空白行 + 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 + continue + else: + # 非空白行,直接添加到处理后的列表 + processed.append(current_text) - Returns: - dict: 解析后的字典,重复的键对应的值为列表。 - """ - def custom_object_pairs_hook(pairs): - d = defaultdict(list) - for key, value in pairs: - # 如果值是字典或列表,递归处理 - if isinstance(value, dict): - value = process_dict(value) - elif isinstance(value, list): - value = process_list(value) - d[key].append(value) - # 将有多个值的键转换为列表,单个值的键保持原样 - return {key: (values if len(values) > 1 else values[0]) for key, values in d.items()} + index += 1 + return processed - def process_dict(d): - """ - 递归处理字典,确保所有重复键的值为列表。 +doc_path = 'D:\\flask_project\\flask_app\\static\\output\\015d997e-c32c-49d1-a611-a2e817ace6a1\\ztbfile.docx' +doc = Document(doc_path) - Args: - d (dict): 需要处理的字典。 +# 假设 doc 是您的文档对象 +processed_paragraphs = preprocess_paragraphs(doc.paragraphs) +for i in processed_paragraphs: + print("___________________________") + print(i) - Returns: - dict: 处理后的字典。 - """ - return custom_object_pairs_hook(d.items()) - def process_list(l): - """ - 递归处理列表,确保列表中的所有字典也被处理。 - - Args: - l (list): 需要处理的列表。 - - Returns: - list: 处理后的列表。 - """ - return [process_dict(item) if isinstance(item, dict) else item for item in l] - - return json.loads(json_string, object_pairs_hook=custom_object_pairs_hook) - -# 示例使用 -input_string = ''' -{ - "商务评分": { - "综合实力": { - "评分": "5分", - "要求": "投标人具备有效期内的 ISO9001质量体系认证证书、具备有效期内的OHSA18001职业健康安全体系认证证书、具备有效期内的 IS014001环境管理体系认证证书、具备有效期内的 ISO20000信息技术服务体系认证证书、具备有效期内的 ISO27001信息安全体系认证证书,全部满足得 5分,每缺一项扣 1分,(开标时需提供原件)。" - }, - "综合实力": { - "评分": "2分", - "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" - }, - "综合实力": { - "评分": "2分", - "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" - }, - "综合实力": { - "评分": "3分", - "要求": "投标人需具有健全的信息技术运维服务能力,通过ITSS信息技术服务运维标准符合性认证得 3分,投标时需提供相关证书原件予以证明,否则不得分。" - }, - "综合实力": { - "评分": "2分", - "要求": "投标人具备 CCRC信息安全服务安全集成三级及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" - }, - "类似业绩": { - "评分": "4分", - "要求": "近三年(自投标截止时间前推 36个月,以合同签订日期为准)中标人作为独立承包人有已完成的类似业绩项目(建设内容含应包含会议系统设备采购项目及改造),每提供一份业绩得 2分,最多可得 4分。(业绩证明材料须提供中标公示截图、中标通知书、合同复印件,开标时备查,否则不得分。)" - }, - "质量保证": { - "评分": "2分", - "要求": "投标人所投的MCU及视频会议终端设备产品,如果不是自己生产的,需提供制造商出具的授权及满足招标质保要求的售后服务承诺函,提供得 2分;(开标时提供授权书及售后服务承诺函原件予以证明,否则不得分。)" - } - } -} -''' - -# 解析具有重复键的 JSON 字符串 -parsed_data = parse_json_with_duplicates(input_string) - -# 打印结果 -print(json.dumps(parsed_data, ensure_ascii=False, indent=4)) diff --git a/flask_app/main/判断是否分包等.py b/flask_app/main/判断是否分包等.py index 6677f0c..70cad34 100644 --- a/flask_app/main/判断是否分包等.py +++ b/flask_app/main/判断是否分包等.py @@ -46,22 +46,23 @@ def merge_json_to_list(merged): merged['分包'] = '不允许' merged.pop('是否允许分包', None) - # 处理是否递交投标保证金 - if merged.get('是否递交投标保证金') == '是': + # 处理是否递交投标保证金或磋商保证金 + guarantee_key = '是否递交投标保证金' if '是否递交投标保证金' in merged else '是否递交磋商保证金' + if merged.get(guarantee_key) == '是': chosen_numbers.extend([2, 3]) - merged.pop('是否递交投标保证金', None) - elif merged.get('是否递交投标保证金') == '否': - merged['投标保证金'] = '不提交' - merged['退还投标保证金'] = '/' - merged.pop('是否递交投标保证金', None) + else: + guarantee_type = '投标' if '投标' in guarantee_key else '磋商' + merged[f'{guarantee_type}保证金'] = '不提交' + merged[f'退还{guarantee_type}保证金'] = '/' + merged.pop(guarantee_key, None) # 处理是否有履约保证金 - if merged.get('是否有履约保证金') == '是': + if merged.get('是否提交履约保证金') == '是': chosen_numbers.append(4) - merged.pop('是否有履约保证金', None) - elif merged.get('是否有履约保证金') == '否': + merged.pop('是否提交履约保证金', None) + elif merged.get('是否提交履约保证金') == '否': merged['履约保证金'] = '不提交' - merged.pop('是否有履约保证金', None) + merged.pop('是否提交履约保证金', None) # 处理是否有招标代理服务费 if merged.get('是否有招标代理服务费') == '是': @@ -78,12 +79,13 @@ def merge_json_to_list(merged): merged['踏勘现场']='不组织' merged.pop('是否组织踏勘现场', None) - if merged.get('是否召开投标预备会') == '是': + preparation_key = '是否召开投标预备会' if '是否召开投标预备会' in merged else '是否召开投标答疑会' + if merged.get(preparation_key) == '是': chosen_numbers.append(7) - merged.pop('是否召开投标预备会',None) - elif merged.get('是否召开投标预备会') == '否': - merged['投标预备会']='不召开' - merged.pop('是否召开投标预备会', None) + else: + meeting_type = '预备会' if '预备会' in preparation_key else '答疑会' + merged[f'投标{meeting_type}'] = '不召开' + merged.pop(preparation_key, None) if merged.get('是否允许偏离') == '是': chosen_numbers.append(8) @@ -123,7 +125,7 @@ def judge_whether_main(file_path,output_folder): #传入招标文件中‘投 read_pdf_and_judge_main(file_path, output_json_path) #提取打勾符号 qianwen_answer = qianwen_ask(output_json_path, user_query1) # 调用普通千问判断是、否、未知 user_query2 = construct_judge_questions(qianwen_answer) # 提取回答为”未知“的键 - # 判断user_query是否为空 + # 判断user_query2是否为空 if user_query2: file_id = upload_file(file_path) res = qianwen_long(file_id, user_query2) #整个前附表一起传问千问long diff --git a/flask_app/main/商务标技术标整合.py b/flask_app/main/商务标技术标整合.py index e454a3a..6393282 100644 --- a/flask_app/main/商务标技术标整合.py +++ b/flask_app/main/商务标技术标整合.py @@ -153,8 +153,6 @@ def combine_evaluation_standards(truncate2): # target_values2=['投标报价','商务标','商务部分','报价部分','业绩','信誉','分值','计算公式','信用','人员','资格','奖项','认证','荣誉'] # update_json=combine_technical_and_business(clean_json_string(evaluation_res),target_values1,target_values2) update_json = combine_technical_and_business(clean_json_string(evaluation_res), target_values1) - # evaluation_combined_res = json.dumps(update_json,ensure_ascii=False,indent=4) - # return evaluation_combined_res return update_json #商务标技术标整合 if __name__ == "__main__": truncate2="C:\\Users\\Administrator\\Desktop\\招标文件\\招标01_evaluation_method.pdf" diff --git a/flask_app/main/基础信息整合.py b/flask_app/main/基础信息整合.py index 3b8c2e1..348cc53 100644 --- a/flask_app/main/基础信息整合.py +++ b/flask_app/main/基础信息整合.py @@ -1,10 +1,12 @@ import json -from flask_app.main.json_utils import clean_json_string, nest_json_under_key,rename_outer_key, combine_json_results +from flask_app.main.json_utils import clean_json_string, nest_json_under_key,rename_outer_key from flask_app.main.投标人须知正文提取指定内容 import extract_from_notice from flask_app.main.判断是否分包等 import judge_whether_main, read_questions_from_judge from flask_app.main.多线程提问 import read_questions_from_file, multi_threading from flask_app.main.通义千问long import upload_file + + def aggregate_basic_info(baseinfo_list): """ 将基础信息列表中的数据进行合并和分类。 @@ -13,12 +15,11 @@ def aggregate_basic_info(baseinfo_list): - baseinfo_list (list): 包含多个基础信息的列表。 返回: - - list: 合并和分类后的基础信息列表。 + - dict: 合并和分类后的基础信息字典。 """ - combined_baseinfo_list = [] key_groups = { - "招标人/代理信息": ["招标人", "招标人联系方式", "招标代理机构", "招标代理机构联系方式"], - "项目信息": ["工程名称", "招标编号", "工程概况", "招标范围", "招标控制价", "投标竞争下浮率"], + "招标人/代理信息": ["招标人", "招标人联系方式", "招标代理机构", "招标代理机构联系方式","招标代理服务费"], + "项目信息": ["项目名称", "招标编号", "项目概况", "招标范围", "招标控制价", "投标竞争下浮率"], "关键时间/内容": [ "投标文件递交截止日期", "递交方式", @@ -30,7 +31,7 @@ def aggregate_basic_info(baseinfo_list): "其他信息": [ "重新招标、不再招标和终止招标", "是否退还投标文件", - "费用承担" + "投标费用承担" ] } @@ -39,9 +40,9 @@ def aggregate_basic_info(baseinfo_list): # 合并所有基础信息并收集相关键 for baseinfo in baseinfo_list: - json_data = clean_json_string(baseinfo) - combined_data.update(json_data) - relevant_keys_detected.update(json_data.keys()) + # json_data = clean_json_string(baseinfo) + combined_data.update(baseinfo) + relevant_keys_detected.update(baseinfo.keys()) # 动态调整键组 dynamic_key_handling(key_groups, relevant_keys_detected) @@ -49,10 +50,12 @@ def aggregate_basic_info(baseinfo_list): # 按键组分类并嵌套 for group_name, keys in key_groups.items(): group_data = {key: combined_data.get(key, "未提供") for key in keys} - combined_json = nest_json_under_key(group_data, group_name) - combined_baseinfo_list.append(combined_json) + combined_data[group_name] = group_data + # Optionally remove original keys to avoid duplication + for key in keys: + combined_data.pop(key, None) - return combined_baseinfo_list + return combined_data def dynamic_key_handling(key_groups, detected_keys): # 检查和调整键组配置 @@ -83,9 +86,9 @@ def judge_consortium_bidding(baseinfo_list): accept_bidding = True # 从字典中移除特定键值对 json_data.pop("是否接受联合体投标", None) - # 将修改后的 json 数据转换回 JSON 字符串(如果需要) - updated_info = json.dumps(json_data) - updated_list.append(updated_info) + # # 将修改后的 json 数据转换回 JSON 字符串(如果需要) + # updated_info = json.dumps(json_data) + updated_list.append(json_data) # 更新原始列表,如果你想保留修改 baseinfo_list[:] = updated_list return accept_bidding @@ -104,6 +107,7 @@ def combine_basic_info(knowledge_name, truncate0, output_folder, clause_path): """ baseinfo_list = [] baseinfo_file_path = 'flask_app/static/提示词/前两章提问总结.txt' + # baseinfo_file_path = 'D:\\flask_project\\flask_app\\static\\提示词\\前两章提问总结.txt' questions = read_questions_from_file(baseinfo_file_path) res1 = multi_threading(questions, knowledge_name) @@ -121,6 +125,7 @@ def combine_basic_info(knowledge_name, truncate0, output_folder, clause_path): baseinfo_list.append(merged) judge_file_path = 'flask_app/static/提示词/是否相关问题.txt' + # judge_file_path = 'D:\\flask_project\\flask_app\\static\\提示词\\是否相关问题.txt' judge_questions = read_questions_from_judge(judge_file_path, chosen_numbers) judge_consortium = judge_consortium_bidding(baseinfo_list) # 通过招标公告判断是否接受联合体投标 @@ -138,16 +143,13 @@ def combine_basic_info(knowledge_name, truncate0, output_folder, clause_path): print("基础信息整合: multi_threading error!") else: for question, response in res2: - baseinfo_list.append(response) + baseinfo_list.append(clean_json_string(response)) rebidding_situation = extract_from_notice(clause_path, 3) # "重新招标, 不再招标和终止招标"需从投标人须知正文提取 update_json = rename_outer_key(rebidding_situation, "重新招标、不再招标和终止招标") baseinfo_list.append(update_json) - - aggregated_baseinfo = aggregate_basic_info(baseinfo_list) # 整合基础信息核心代码 - baseinfo_combined_res = combine_json_results(aggregated_baseinfo) # 返回值是字典 - - return {"基础信息": baseinfo_combined_res} + aggregated_baseinfo = aggregate_basic_info(baseinfo_list) # 现在是一个字典 + return {"基础信息": aggregated_baseinfo} if __name__ == "__main__": knowledge_name = "ztb" diff --git a/flask_app/main/招标文件解析.py b/flask_app/main/招标文件解析.py index 7b88e23..ed85a43 100644 --- a/flask_app/main/招标文件解析.py +++ b/flask_app/main/招标文件解析.py @@ -1,8 +1,6 @@ # -*- encoding:utf-8 -*- import json import logging -import os -import sys import time from concurrent.futures import ThreadPoolExecutor from flask_app.main.截取pdf import truncate_pdf_multiple diff --git a/flask_app/main/无效标和废标和禁止投标整合.py b/flask_app/main/无效标和废标和禁止投标整合.py index 1db1254..6f9e3e3 100644 --- a/flask_app/main/无效标和废标和禁止投标整合.py +++ b/flask_app/main/无效标和废标和禁止投标整合.py @@ -152,18 +152,23 @@ def clean_dict_datas(extracted_contents, keywords,excludes): #让正则表达 else: # 如果没有找到关键词,保留原文本 cleaned_text = data - all_texts1.append(cleaned_text) # 将处理后的文本添加到结果列表 + # 删除空格 + cleaned_text_no_spaces = cleaned_text.replace(' ', '').replace(' ', '') + all_texts1.append(cleaned_text_no_spaces) # 将处理后的文本添加到结果列表 else: # print(text_list) new_text_list=preprocess_text_list(text_list) # print(new_text_list) pattern = r'^\s*([((]\d+[))]|[A-Za-z]?\d+\s*(\.\s*\d+)*(\s|\.|、|.)?|[一二三四五六七八九十]+、)' - data = re.sub(pattern, '', new_text_list[0]).strip() #去除序号 + # data = re.sub(pattern, '', new_text_list[0]).strip() #去除序号 + data = re.sub(pattern, '', new_text_list[0]).replace(' ','').strip() # 将修改后的第一个元素和剩余的元素连接起来 new_text_list[0] = data # 更新列表中的第一个元素 joined_text = "\n".join(new_text_list) # 如果列表中有多个元素,则连接它们 - all_texts2.append(joined_text) # 将每个列表的内容添加到 all_texts 中 + # 删除空格 + joined_text_no_spaces = joined_text.replace(' ', '').replace(' ', '') + all_texts2.append(joined_text_no_spaces) # 将每个列表的内容添加到 all_texts 中 return all_texts1,all_texts2 #all_texts1要额外用gpt all_text2直接返回结果 def find_sentences_with_keywords(data, keywords, follow_up_keywords): @@ -208,13 +213,15 @@ def find_sentences_with_keywords(data, keywords, follow_up_keywords): full_text = ' '.join(split_sentences[start_index:]).strip() # pattern = r'^\s*([((]\d+[))]|[A-Za-z]?\d+(\.\d+)*(\s|\.|、)?)' pattern = r'^\s*([((]\d+[))]|[A-Za-z]?\d+\s*(\.\s*\d+)*(\s|\.|、|.)?|[一二三四五六七八九十]+、)' - data=re.sub(pattern,'',full_text) + # data=re.sub(pattern,'',full_text) + data = re.sub(pattern, '', full_text).replace(' ','').strip() sentences2.append(data) # 存储有后续关键词的情况 i = end_index if found_next_section else len(split_sentences) else: # pattern = r'^\s*([((]\d+[))]|[A-Za-z]?\d+(\.\d+)*(\s|\.|、)?)' pattern = r'^\s*([((]\d+[))]|[A-Za-z]?\d+\s*(\.\s*\d+)*(\s|\.|、|.)?|[一二三四五六七八九十]+、)' - data = re.sub(pattern, '', sentence).replace('\n','').strip() + # data = re.sub(pattern, '', sentence).replace('\n','').strip() + data = re.sub(pattern, '', sentence).replace('\n', '').replace(' ','').strip() sentences1.append(data) # 存储没有后续关键词的情况 i += 1 else: diff --git a/flask_app/main/通义千问long.py b/flask_app/main/通义千问long.py index f2e5f54..3ce9d97 100644 --- a/flask_app/main/通义千问long.py +++ b/flask_app/main/通义千问long.py @@ -28,7 +28,7 @@ def qianwen_long(file_id, user_query): completion = client.chat.completions.create( model="qwen-long", top_p=0.5, - temperature=0.5, + temperature=0.4, messages=[ { 'role': 'system', diff --git a/flask_app/static/提示词/前两章提问总结.txt b/flask_app/static/提示词/前两章提问总结.txt index 081e1e8..aa389c3 100644 --- a/flask_app/static/提示词/前两章提问总结.txt +++ b/flask_app/static/提示词/前两章提问总结.txt @@ -1,11 +1,11 @@ -1.该招标文件的工程名称(项目名称)是?招标编号是?招标人是?招标代理机构是?请按json格式给我提供信息,键名分别是'工程名称','招标编号','招标人','招标代理机构',若存在未知信息,在对应的键值中填'未知'。 +1.该招标文件的项目名称(或工程名称)是?招标编号(或项目编号)是?招标人是?招标代理机构是?请按json格式给我提供信息,键名分别是'项目名称','招标编号','招标人','招标代理机构',若存在未知信息,在对应的键值中填'未知'。 -#该招标文件的工程概况(或项目概况)是?招标范围是?招标控制价(可指代投标限价、投资概算金额、工程概算金额、合同估算价,但非监理费用)是?该项目的计划工期(监理服务期)是?该项目是否接受联合体投标?请按json格式给我提供信息,键名分别为'工程概况','招标范围','招标控制价','计划工期','是否接受联合体投标',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 -2.该招标文件的工程概况(或项目概况)是?招标范围是?请按json格式给我提供信息,键名分别为'工程概况','招标范围',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 +#该招标文件的项目概况(或工程概况)是?招标范围是?招标控制价(可指代投标限价、投资概算金额、工程概算金额、合同估算价,但非监理费用)是?该项目的计划工期(监理服务期)是?该项目是否接受联合体投标?请按json格式给我提供信息,键名分别为'工程概况','招标范围','招标控制价','计划工期','是否接受联合体投标',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 +2.该招标文件的工程概况(或项目概况)是?招标范围是?请按json格式给我提供信息,键名分别为'项目概况','招标范围',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 3.该招标文件的招标控制价(可指代投标限价、投资概算金额、工程概算金额、合同估算价,但非监理费用)是?请按json格式给我提供信息,键名为'招标控制价',若存在未知信息,在对应的键值中填'未知'。 -4.投标文件递交截止日期是?递交方式是?请按json格式给我提供信息,键名分别是'投标文件递交截止日期','递交方式',若存在未知信息,在对应的键值中填'未知'。 +4.投标文件递交截止日期是?递交方式是?请按json格式给我提供信息,键名分别是'投标文件递交截止日期','投标文件递交方式',若存在未知信息,在对应的键值中填'未知'。 5.招标人和招标代理机构的联系方式是?请按json格式给我提供信息,键名分别是'招标人联系方式','招标代理机构联系方式',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 @@ -24,7 +24,7 @@ #8.该招标文件的电子招标文件获取方式是?请按原文段落全部完整内容回答,以json的格式给我提供信息,键名是'电子招标文件获取方式',若存在未知信息,在对应的键值中填'未知'。 -9.该招标文件对投标人准备和参加投标活动发生的费用是如何规定的?请以json的格式给我提供信息,键名是'费用承担',若存在未知信息,在对应的键值中填'未知'。 +9.该招标文件中对投标人准备和参加投标活动发生的费用是如何规定的?请以json的格式给我提供信息,键名是'投标费用承担',若存在未知信息,在对应的键值中填'未知'。 10.求澄清的招标文件截止时间是?请以json的格式给我提供信息,键名是'投标人要求澄清招标文件的截止时间',若存在未知信息,在对应的键值中填'未知'。 diff --git a/flask_app/static/提示词/基本信息货物标.txt b/flask_app/static/提示词/基本信息货物标.txt index 22081be..30aa63f 100644 --- a/flask_app/static/提示词/基本信息货物标.txt +++ b/flask_app/static/提示词/基本信息货物标.txt @@ -1,12 +1,12 @@ -1.该招标文件的项目名称是?项目编号(或招标编号)是?采购人(或招标人)是?采购代理机构(或招标代理机构)是?请按json格式给我提供信息,键名分别是'项目名称','项目编号','采购人','采购代理机构',若存在未知信息,在对应的键值中填'未知'。 +1.该招标文件的项目名称是?项目编号(或招标编号)是?采购人(或招标人)是?采购代理机构(或招标代理机构)是?请按json格式给我提供信息,键名分别是'项目名称','项目编号','招标人','招标代理机构',若存在未知信息,在对应的键值中填'未知'。 2.该招标文件的项目概况是?项目基本情况是?请按json格式给我提供信息,键名分别为'项目概况','项目基本情况',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,而嵌套键值必须与原文保持一致,若存在未知信息,在对应的键值中填'未知'。 3.该招标文件的最高限价(或招标控制价)是?请按json格式给我提供信息,键名为'招标控制价',若存在未知信息,在对应的键值中填'未知'。 -4.投标文件(或响应文件)递交截止时间是?递交地点(或方式)是?请按json格式给我提供信息,键名分别是'投标文件递交截止日期','递交地点'(或'递交方式'),若存在未知信息,在对应的键值中填'未知'。 +4.投标文件(或响应文件)递交截止时间是?递交地点(或方式)是?请按json格式给我提供信息,键名分别是'投标文件递交截止日期','投标文件递交地点'(或'投标文件递交方式'),若存在未知信息,在对应的键值中填'未知'。 -5.采购人(招标人)和采购代理机构(或招标代理机构)的联系方式是?请按json格式给我提供信息,键名分别是'采购人联系方式','采购代理机构联系方式',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 +5.采购人(招标人)和采购代理机构(或招标代理机构)的联系方式是?请按json格式给我提供信息,键名分别是'招标人联系方式','招标代理机构联系方式',若存在嵌套信息,嵌套内容键名以文件中对应字段命名,若存在未知信息,在对应的键值中填'未知'。 6.该招标文件的信息公示媒介在哪?请按json格式给我提供信息,键名是'信息公示媒介',若存在未知信息,在对应的键值中填'未知'。 @@ -14,13 +14,17 @@ 8.该项目的投标有效期(或响应文件有效期)是什么?请按json格式给我提供信息,键名是'投标有效期',若存在未知信息,在对应的键值中填'未知'。 -9.该招标文件对投标人准备和参加投标活动发生的费用是如何规定的?请以json的格式给我提供信息,键名是'费用承担',若存在未知信息,在对应的键值中填'未知'。 +9.该招标文件中对投标人(或供应商)准备和参加投标活动中发生的费用是如何规定的?请以json的格式给我提供信息,键名是'投标费用承担',若存在未知信息,在对应的键值中填'未知'。 -10.求澄清的招标文件截止时间是?请以json的格式给我提供信息,键名是'投标人要求澄清招标文件的截止时间',若存在未知信息,在对应的键值中填'未知'。 +10.招标人(或招标代理机构)对招标文件的澄清(或答疑)的截止时间是?请以json的格式给我提供信息,键名是'澄清招标文件的截止时间',若存在未知信息,在对应的键值中填'未知'。 11.该文档要求扣留的质量保证金百分比是多少,请以json格式给我提供信息,键名为'质量保证金',如果没有则以'未知'填充。 12.该项目是否接受联合体投标?请按json格式给我提供信息,键名为'是否接受联合体投标','是否接受联合体投标'的键值仅限于'是'、'否'、'未知'。 +13.该招标文件中对投标文件中偏离项的要求或内容是怎样的?请以json格式给我提供信息,外层键名为'偏离',请不要回答具体的技术参数,若存在未知信息,在对应的键值中填'未知'。 + + + diff --git a/flask_app/static/提示词/是否相关问题货物标.txt b/flask_app/static/提示词/是否相关问题货物标.txt new file mode 100644 index 0000000..d450beb --- /dev/null +++ b/flask_app/static/提示词/是否相关问题货物标.txt @@ -0,0 +1,9 @@ +#pdf提取之后的提示词,调用普通通译千问: +#请你依据以上信息回答,是否允许分包? 是否需要递交投标保证金?是否有履约保证金(履约担保)?是否有招标代理服务费?请按json格式给我提供信息,键名分别为'是否允许分包','是否递交投标保证金','是否有履约保证金','是否有招标代理服务费',键值仅限于'是','否','未知'。 +1.该招标文件对于分包的要求是怎样的?请按json格式给我提供信息,键名为'分包',若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。。 +2.根据招标文件第二章投标人须知,该项目投标保证金(或磋商保证金)的内容或要求是什么?请按json格式给我提供信息,外层键名为"投标保证金"(或"磋商保证金"),若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 +3.该招标文件对于投标保证金的退还相关的规章办法是怎样的?请按json格式给我提供信息,键名为'退还投标保证金',若存在嵌套信息,嵌套内容键名以文档中对应字段命名。 +4.根据投标人须知前附表,该项目对于履约保证金(担保)的内容或要求是怎样的,请按json格式给我提供信息,外层键名为"履约保证金",若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 +5.本项目的招标代理服务费(或中标服务费、成交服务费)的相关内容是怎样的,请按json格式给我提供信息,外层键名为'招标代理服务费',若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 +6.该招标文件对于踏勘现场的内容或要求是怎样的,请按json格式给我提供信息,外层键名为"踏勘现场",若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 +7.该招标文件对于投标预备会(或投标答疑会)内容是怎样的,请按json格式给我提供信息,外层键名为"投标预备会"(或"投标答疑会"),若需要以嵌套键值对返回结果,那么嵌套键名为你对相应要求的总结,而对应键值需要完全与原文保持一致。 diff --git a/flask_app/货物标/商务服务其他要求提取.py b/flask_app/货物标/商务服务其他要求提取.py index d2a82ed..13e60ea 100644 --- a/flask_app/货物标/商务服务其他要求提取.py +++ b/flask_app/货物标/商务服务其他要求提取.py @@ -37,7 +37,7 @@ def find_exists(truncate_file, required_keys): else: relevant_text = text[start_index:] relevant_text=re.sub(r'\s+', '', relevant_text) #删除换行符 空格 - print(relevant_text) + # print(relevant_text) # Custom logic for "服务要求" matched_requirements = [] punctuation = r"[,。?!、;:,.?!]*" @@ -60,7 +60,7 @@ def find_exists(truncate_file, required_keys): def generate_queries(truncate_file, required_keys): key_list = find_exists(truncate_file, required_keys) queries = [] - user_query_template = "这是一份货物标中采购要求部分的内容,请告诉我\"{}\"是什么,请以json格式返回结果,外层键名是\"{}\",内层键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减,注意你无需回答具体设备的技术要求," + user_query_template = "这是一份货物标中采购要求部分的内容,请告诉我\"{}\"是什么,请以json格式返回结果,外层键名是\"{}\",内层键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减,注意你无需回答具体设备的技术要求。" for key in key_list: query_base = user_query_template.format(key, key) other_keys = [k for k in key_list if k != key] @@ -68,7 +68,7 @@ def generate_queries(truncate_file, required_keys): query_base += "也不需要回答\"{}\"中的内容,".format("\"和\"".join(other_keys)) query_base += "若相关要求不存在,在键值中填'未知'。" queries.append(query_base) - print(query_base) + # print(query_base) return queries diff --git a/flask_app/货物标/基础信息解析main.py b/flask_app/货物标/基础信息解析main.py index d7c3864..f4ebec5 100644 --- a/flask_app/货物标/基础信息解析main.py +++ b/flask_app/货物标/基础信息解析main.py @@ -1,14 +1,70 @@ +# -*- encoding:utf-8 -*- import json +import threading +import time -from flask_app.main.json_utils import clean_json_string, nest_json_under_key,rename_outer_key, combine_json_results +from flask_app.main.json_utils import clean_json_string +from flask_app.main.基础信息整合 import judge_consortium_bidding from flask_app.main.多线程提问 import read_questions_from_file, multi_threading -from flask_app.main.通义千问long import upload_file +from flask_app.main.通义千问long import upload_file, qianwen_long +from flask_app.main.判断是否分包等 import merge_json_to_list, read_questions_from_judge +from flask_app.货物标.提取采购需求main import fetch_procurement_reqs +def aggregate_basic_info(baseinfo_list): + """ + 将基础信息列表中的数据进行合并和分类。 + + 参数: + - baseinfo_list (list): 包含多个基础信息的列表。 + + 返回: + - dict: 合并和分类后的基础信息字典。 + """ + key_groups = { + "招标人/代理信息": ["招标人", "招标人联系方式", "招标代理机构", "招标代理机构联系方式"], + "项目信息": ["项目名称", "项目编号", "项目概况", "项目基本情况", "招标控制价", "投标竞争下浮率"], + "采购要求": ["技术要求","商务要求","服务要求","其他要求"], + "关键时间/内容": [ + "投标文件递交截止日期", + "澄清招标文件的截止时间", + "投标有效期", + "信息公示媒介" + ], + "保证金相关": ["质量保证金"], + "其他信息": [ + "是否退还投标文件", + "投标费用承担", + "招标代理服务费" + ] + } + + combined_data = {} + relevant_keys_detected = set() + + # 合并所有基础信息并收集相关键 + for baseinfo in baseinfo_list: + combined_data.update(baseinfo) + relevant_keys_detected.update(baseinfo.keys()) + + # 动态调整键组 + dynamic_key_handling(key_groups, relevant_keys_detected) + + # 按键组分类并嵌套 + for group_name, keys in key_groups.items(): + group_data = {key: combined_data.get(key, "未提供") for key in keys} + combined_data[group_name] = group_data + # Optionally remove original keys to avoid duplication + for key in keys: + combined_data.pop(key, None) + + return combined_data + def dynamic_key_handling(key_groups, detected_keys): # 检查和调整键组配置 for key in detected_keys: - if "投标保证金" in key or "履约保证金" in key: + # print(key) + if "保证金" in key: key_groups["保证金相关"].append(key) elif "是否接受联合体" in key: key_groups["项目信息"].append(key) @@ -18,106 +74,81 @@ def dynamic_key_handling(key_groups, detected_keys): key_groups["项目信息"].append(key) elif "踏勘现场" in key: key_groups["其他信息"].append(key) - elif "投标预备会" in key: + elif "投标预备会" in key or "投标答疑会" in key: key_groups["其他信息"].append(key) elif "偏离" in key: key_groups["其他信息"].append(key) -def aggregate_basic_info(baseinfo_list): - """ - 将基础信息列表中的数据进行合并和分类。 + elif "递交方式" in key or "递交地点" in key: + key_groups["关键时间/内容"].append(key) - 参数: - - baseinfo_list (list): 包含多个基础信息的列表。 +def get_base_info(baseinfo_file_path): + file_id = upload_file(baseinfo_file_path) + baseinfo_file_path='flask_app/static/提示词/基本信息货物标.txt' + # baseinfo_file_path = 'D:\\flask_project\\flask_app\\static\\提示词\\基本信息货物标.txt' + questions = read_questions_from_file(baseinfo_file_path) + more_query = "请你根据招标文件信息,回答以下问题:是否组织踏勘现场?是否召开投标预备会(或投标答疑会)?是否退还投标文件?是否允许分包? 是否需要递交投标保证金(或磋商保证金)?是否需要提交履约保证金(或履约担保)?是否有招标代理服务费(或中标、成交服务费)?请按json格式给我提供信息,键名分别为'是否组织踏勘现场','是否召开投标预备会'(或'是否召开投标答疑会'),'是否退还投标文件',是否允许分包','是否递交投标保证金'(或'是否递交磋商保证金'),'是否提交履约保证金','是否有招标代理服务费',键值仅限于'是','否','未知',若存在矛盾信息,请回答'未知'。" + questions.append(more_query) + baseinfo_results = multi_threading(questions, "", file_id, 2) # 1代表使用百炼rag 2代表使用qianwen-long + baseinfo_list = [res for _, res in baseinfo_results] if baseinfo_results else [] + chosen_numbers, merged = merge_json_to_list(clean_json_string(baseinfo_list.pop())) + baseinfo_list.append(merged) - 返回: - - list: 合并和分类后的基础信息列表。 - """ - combined_baseinfo_list = [] - key_groups = { - "招标人/代理信息": ["招标人", "招标人联系方式", "招标代理机构", "招标代理机构联系方式"], - "项目信息": ["工程名称", "招标编号", "工程概况", "招标范围", "招标控制价", "投标竞争下浮率"], - "关键时间/内容": [ - "投标文件递交截止日期", - "递交方式", - "投标人要求澄清招标文件的截止时间", - "投标有效期", - "评标结果公示媒介" - ], - "保证金相关": ["质量保证金", "退还投标保证金"], - "其他信息": [ - "重新招标、不再招标和终止招标", - "是否退还投标文件", - "费用承担" - ] - } + judge_file_path = 'flask_app/static/提示词/是否相关问题.txt' + # judge_file_path = 'D:\\flask_project\\flask_app\\static\\提示词\\是否相关问题货物标.txt' + judge_questions = read_questions_from_judge(judge_file_path, chosen_numbers) + # print(judge_questions) + judge_consortium = judge_consortium_bidding(baseinfo_list) # 通过招标公告判断是否接受联合体投标 - combined_data = {} - relevant_keys_detected = set() + if judge_consortium: + judge_consortium_question = ( + "该招标文件对于联合体投标的要求是怎样的,请按json格式给我提供信息," + "外层键名为'联合体投标要求',其中有一个嵌套键值对为:\"是否接受联合体投标\":\"是\"" + ) + judge_questions.append(judge_consortium_question) + res2 = multi_threading(judge_questions, "", file_id, 2) # 调用千问-long + if not res2: + print("基础信息整合: multi_threading error!") + else: + for question, response in res2: + baseinfo_list.append(clean_json_string(response)) + return baseinfo_list - # 合并所有基础信息并收集相关键 - for baseinfo in baseinfo_list: - json_data = clean_json_string(baseinfo) - combined_data.update(json_data) - relevant_keys_detected.update(json_data.keys()) +def combine_basic_info(baseinfo_file_path, procurement_file_path): + baseinfo_list = [] + temp_list = [] + procurement_reqs = {} + # 定义一个线程函数来获取基础信息 + def get_base_info_thread(): + nonlocal temp_list + temp_list = get_base_info(baseinfo_file_path) + # 定义一个线程函数来获取采购需求 + def fetch_procurement_reqs_thread(): + nonlocal procurement_reqs + procurement_reqs = fetch_procurement_reqs(procurement_file_path) + # 创建并启动获取基础信息的线程 + thread1 = threading.Thread(target=get_base_info_thread) + thread1.start() + # 等待一秒后启动获取采购需求的线程 + time.sleep(1) + thread2 = threading.Thread(target=fetch_procurement_reqs_thread) + thread2.start() + # 等待两个线程都完成 + thread1.join() + thread2.join() + # 合并结果 + baseinfo_list += temp_list # temp_list 是一个列表 + baseinfo_list.append(procurement_reqs) # procurement_reqs 是一个字典 + aggregated_baseinfo = aggregate_basic_info(baseinfo_list) - # 动态调整键组 - dynamic_key_handling(key_groups, relevant_keys_detected) + return {"基础信息": aggregated_baseinfo} - # 按键组分类并嵌套 - for group_name, keys in key_groups.items(): - group_data = {key: combined_data.get(key, "未提供") for key in keys} - combined_json = nest_json_under_key(group_data, group_name) - combined_baseinfo_list.append(combined_json) - - return combined_baseinfo_list - -def combine_basic_info(knowledge_name,output_folder,clause_path): - # file_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\6.2定版视频会议磋商文件(1)\\6.2定版视频会议磋商文件_1-21.pdf" - # file_id = upload_file(file_path) - # baseinfo_file_path='flask_app/static/提示词/前两章提问总结.txt' - # questions=read_questions_from_file(baseinfo_file_path) - # 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}") - baseinfo_combined_res={ - "招标人/代理信息": { - "招标人": "黄石临空建设管理有限公司", - "招标人联系方式": { - "名称": "黄石临空建设管理有限公司", - "地址": "大冶市还地桥镇", - "联系人": "王先生", - "电话": "13545510946", - "传真": "未知", - "电子邮件": "未知", - "网址": "未知", - "开户银行": "未知", - "账号": "未知" - }, - "招标代理机构": "湖北民成工程项目管理有限公司", - "招标代理机构联系方式": { - "名称": "湖北民成工程项目管理有限公司", - "地址": "大冶市港湖还建楼 20栋二单元 102室", - "联系人": "尹工", - "电话": "18327823905", - "传真": "未知", - "电子邮件": "未知", - "网址": "未知", - "开户银行": "未知", - "账号": "未知" - } - } - } - return {"基础信息":baseinfo_combined_res} if __name__ == "__main__": - knowledge_name = "ztb" - output_folder="C:\\Users\Administrator\Desktop\\fsdownload\\3424b7cb-1f85-44b4-a432-44539b870405" - truncate0="C:\\Users\Administrator\Desktop\\fsdownload\\3424b7cb-1f85-44b4-a432-44539b870405\\ztbfile_tobidders_notice_table.pdf" - clause_path="C:\\Users\Administrator\Desktop\\fsdownload\\3424b7cb-1f85-44b4-a432-44539b870405\\clause1.json" - res=combine_basic_info(knowledge_name,output_folder,clause_path) - print(json.dumps(res,ensure_ascii=False,indent=4)) \ No newline at end of file + start_time=time.time() + baseinfo_file_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub\\merged_baseinfo.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) + print(json.dumps(res, ensure_ascii=False, indent=4)) + end_time=time.time() + print("elasped time:"+str(end_time-start_time)) diff --git a/flask_app/货物标/技术要求提取.py b/flask_app/货物标/技术要求提取.py index f44bd2b..6aca425 100644 --- a/flask_app/货物标/技术要求提取.py +++ b/flask_app/货物标/技术要求提取.py @@ -66,9 +66,24 @@ def postprocess(data): # 递归处理顶层数据 return {key: convert_dict(val) if isinstance(val, dict) else val for key, val in data.items()} def get_technical_requirements(file_id): - user_query1 = "这是一份货物标中采购要求部分的内容,请告诉我需要采购的系统(货物),如果有采购清单,请直接根据清单上的货物名称给出结果,若没有采购清单,你要从文中摘取需要采购的系统(货物),采购需求中可能包含层次关系,如大系统中包含若干子系统,你需要保留这种层次关系,给出系统(货物)名称,请以json格式返回,外层键名为\"采购需求\",嵌套键名为对应的系统名称或货物名称,需与原文保持一致,无需给出采购数量和单位,如有未知内容,在对应键值处填\"未知\"。" + user_query1 = """ + 这是一份货物标中采购要求部分的内容,请告诉我需要采购的系统(或货物),如果有采购清单,请直接根据清单上的货物名称给出结果,若没有采购清单,你要从文中摘取需要采购的系统(或货物),采购需求中可能包含层次关系,如某大系统中包含若干货物,那么需要用嵌套键值对表示这种关系,请以json格式返回,最外层键名为'采购需求',嵌套键名为对应的系统名称或货物名称,需与原文保持一致,无需给出采购数量和单位,如有未知内容,在对应键值处填'未知'。以下为示例输出: + { + "采购需求": { + "交通信号灯": {}, + "交通监控视频子系统": { + "高清视频抓拍像机":{}, + "补光灯":{} + }, + "交通诱导子系统": {}, + "电子警察子系统": {}, + } +} + + """ res = qianwen_long(file_id, user_query1) cleaned_res = clean_json_string(res) + print(res) keys_list ,no_keys_added= generate_key_paths(cleaned_res['采购需求']) # 提取需要采购的货物清单 if '采购需求' in cleaned_res: cleaned_res['技术要求'] = cleaned_res.pop('采购需求') @@ -78,9 +93,10 @@ def get_technical_requirements(file_id): user_query_template = "这是一份货物标中采购要求部分的内容,请你给出\"{}\"的技术参数(或采购要求)和数量,请以json格式返回结果,外层键名为\"{}\", 键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减。" queries = [] for key in keys_list: - # 替换 user_query2 中的 "网络硬盘录像机" 为当前 key - new_query = user_query_template.format(key, key) - print(new_query) + # 将键中的 '.' 替换为 '下的' + modified_key = key.replace('.', '下的') + # 使用修改后的键填充第一个占位符,原始键填充第二个占位符 + new_query = user_query_template.format(modified_key, key) queries.append(new_query) results = multi_threading(queries, "", file_id, 2) technical_requirements = [] @@ -124,7 +140,7 @@ def test_all_files_in_folder(input_folder, output_folder): if __name__ == "__main__": - truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output1\\2-招标文件_procurement.pdf" + truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub\\广水农商行门禁控制主机及基础验证设备采购项目——磋商文件(定稿)(三次)_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/货物标/提取采购需求main.py b/flask_app/货物标/提取采购需求main.py index c773223..056f881 100644 --- a/flask_app/货物标/提取采购需求main.py +++ b/flask_app/货物标/提取采购需求main.py @@ -23,18 +23,17 @@ def fetch_procurement_reqs(truncate_file): business_requirements = future_business.result() # 构建最终的嵌套结构,确保四个键平级 procurement_reqs = { - "采购要求": { "技术要求": technical_requirements.get("技术要求", {}), "商务要求": business_requirements.get("商务要求", {}), "服务要求": business_requirements.get("服务要求", {}), "其他要求": business_requirements.get("其他要求", {}) - } } return procurement_reqs if __name__ == "__main__": output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\货物标output" - file_path="C:\\Users\\Administrator\\Desktop\\货物标\\output1\\2-招标文件(2020年广水市中小学教师办公电脑系统及多媒体“班班通”设备采购安装项目)_procurement.pdf" + # file_path="C:\\Users\\Administrator\\Desktop\\货物标\\output1\\2-招标文件(2020年广水市中小学教师办公电脑系统及多媒体“班班通”设备采购安装项目)_procurement.pdf" + file_path="C:\\Users\\Administrator\\Desktop\\货物标\\output1\\磋商文件_procurement.pdf" res=fetch_procurement_reqs(file_path) print(json.dumps(res, ensure_ascii=False, indent=4)) diff --git a/flask_app/货物标/提示词/prompt1.txt b/flask_app/货物标/提示词/prompt1.txt index efbc686..5452af0 100644 --- a/flask_app/货物标/提示词/prompt1.txt +++ b/flask_app/货物标/提示词/prompt1.txt @@ -8,6 +8,7 @@ #这是一份货物标中采购要求部分的内容,请你给出所需的设备名称以及设备的具体型号参数要求,请以json格式返回结果,外层键名为采购要求。 这是一份货物标中采购要求部分的内容,请你给出\"{}\"的具体型号参数要求和数量,请以json格式返回结果,外层键名为\"{}\", 键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减。 #这是一份货物标中采购要求部分的内容,请你给出"网络硬盘录像机"的具体型号参数要求,请以json格式返回结果,外层键名为"网络硬盘录像机",键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减。 +user_query1 = "这是一份货物标中采购要求部分的内容,请告诉我需要采购的系统(货物),如果有采购清单,请直接根据清单上的货物名称给出结果,若没有采购清单,你要从文中摘取需要采购的系统(货物),采购需求中可能包含层次关系,如大系统中包含若干子系统,你需要保留这种层次关系,给出系统(货物)名称,请以json格式返回,外层键名为\"采购需求\",嵌套键名为对应的系统名称或货物名称,需与原文保持一致,无需给出采购数量和单位,如有未知内容,在对应键值处填\"未知\"。" 这是一份货物标中采购要求部分的内容,请告诉我商务要求和其他要求是什么,请以json格式返回结果,外层键名分别是"商务要求"和"其他要求",内层键值对中的键是你对该要求的总结,而值需要完全与原文保持一致,不可擅自总结删减,注意你无需回答具体设备的技术要求,若相关要求不存在,在键值中填"未知"。 diff --git a/flask_app/货物标/无效标和废标和禁止投标整合main.py b/flask_app/货物标/无效标和废标和禁止投标整合main.py index 6ff353b..b2bcf8e 100644 --- a/flask_app/货物标/无效标和废标和禁止投标整合main.py +++ b/flask_app/货物标/无效标和废标和禁止投标整合main.py @@ -11,6 +11,46 @@ from docx import Document #如果当前段落有序号,则向下匹配直接遇到相同的序号样式 #如果当前段落无序号,则向下匹配序号,把若干同类的序号都摘出来。 +def preprocess_paragraphs(paragraphs): + processed = [] + index = 0 + while index < len(paragraphs): + current_text = paragraphs[index].text.strip() + + # 检测是否为空白行 + 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'^\s*([((]\d+[))]|[A-Za-z]\.\s*|[A-Za-z]?\d+\s*(\.\s*\d+)*(\s|\.|、|.)?|[一二三四五六七八九十]+、)', prev_text) \ + and not re.match(r'^\s*([((]\d+[))]|[A-Za-z]\.\s*|[A-Za-z]?\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 + continue + else: + # 非空白行,直接添加到处理后的列表 + processed.append(current_text) + + index += 1 + return processed + def extract_text_with_keywords(doc_path, keywords, follow_up_keywords): from collections import OrderedDict from docx import Document @@ -117,9 +157,11 @@ def extract_text_with_keywords(doc_path, keywords, follow_up_keywords): return current_index + processed_paragraphs = preprocess_paragraphs(doc.paragraphs) index = 0 - while index < len(doc.paragraphs): - index = extract_from_text(doc.paragraphs[index].text.strip(), index) + while index < len(processed_paragraphs): + index = extract_from_text(processed_paragraphs[index].strip(), index) + index += 1 return extracted_paragraphs @@ -175,7 +217,9 @@ def clean_dict_datas(extracted_contents, keywords,excludes): #让正则表达 else: # 如果没有找到关键词,保留原文本 cleaned_text = data - all_texts1.append(cleaned_text) # 将处理后的文本添加到结果列表 + # 删除空格 + cleaned_text_no_spaces = cleaned_text.replace(' ', '').replace(' ', '') + all_texts1.append(cleaned_text_no_spaces) # 将处理后的文本添加到结果列表 else: new_text_list=preprocess_text_list(text_list) @@ -186,7 +230,9 @@ def clean_dict_datas(extracted_contents, keywords,excludes): #让正则表达 # 将修改后的第一个元素和剩余的元素连接起来 new_text_list[0] = data # 更新列表中的第一个元素 joined_text = "\n".join(new_text_list) # 如果列表中有多个元素,则连接它们 - all_texts2.append(joined_text) # 将每个列表的内容添加到 all_texts 中 + # 删除空格 + joined_text_no_spaces = joined_text.replace(' ', '').replace(' ', '') + all_texts2.append(joined_text_no_spaces) # 将每个列表的内容添加到 all_texts 中 return all_texts1,all_texts2 #all_texts1要额外用gpt all_text2直接返回结果 @@ -262,8 +308,16 @@ def extract_table_with_keywords(data, keywords, follow_up_keywords): continue # 分割句子,保证句子完整性(按标点符号和序号分割)eg:(?=\d+\.\d+):匹配诸如 1.1、2.2 之类的序号,并在序号前进行分割。 - split_sentences = re.split(r'(?<=[。!?\!\?])|(?=\d+\.\d+)|(?=\d+[\、\.])|(?=[((]\d+[))])', item) - + split_sentences = re.split( + r'(?<=[。!?!?\?])|' # 在中文句号、感叹号或问号后分割 + r'(?=\d+\.\d+)|' # 在类似1.1的数字序号前分割 + r'(?=\d+[\s、\.])|' # 在数字后跟空格、顿号或点号前分割 + r'(?=[((]\d+[))])|' # 在括号包围的数字前分割 + r'(?=[A-Za-z]\.\s*)|' # 在字母加点(如A.、a.)前分割 + r'(?=[A-Za-z]?\d+\s*(?:\.\s*\d+)*)|' # 在可选字母加数字或多级编号前分割 + r'(?=[一二三四五六七八九十]+、)', # 在中文数字加顿号(如一、、二、)前分割 + item + ) i = 0 while i < len(split_sentences): sentence = split_sentences[i].strip() @@ -292,15 +346,15 @@ def extract_table_with_keywords(data, keywords, follow_up_keywords): else: full_text = ' '.join(split_sentences[start_index:]).strip() # pattern = r'^\s*([((]\d+[))]|[A-Za-z]?\d+(\.\d+)*(\s|\.|、)?)' - pattern = r'^\s*([((]\d+[))]|[A-Za-z]?\d+\s*(\.\s*\d+)*(\s|\.|、|.)?|[一二三四五六七八九十]+、)' - full_text = re.sub(pattern, '', full_text) + pattern = r'^\s*([((]\d+[))]|[A-Za-z]\.\s*|[A-Za-z]?\d+\s*(\.\s*\d+)*(\s|\.|、|.)?|[一二三四五六七八九十]+、)' + full_text = re.sub(pattern, '', full_text).replace(' ','').strip() #删去了空格 sentences2.append(full_text) # 存储有后续关键词的情况 i = end_index if found_next_section else len(split_sentences) else: # 没有后续关键词的情况 # pattern = r'^\s*([((]\d+[))]|[A-Za-z]?\d+(\.\d+)*(\s|\.|、)?)' - pattern = r'^\s*([((]\d+[))]|[A-Za-z]?\d+\s*(\.\s*\d+)*(\s|\.|、|.)?|[一二三四五六七八九十]+、)' - cleaned_sentence = re.sub(pattern, '', sentence).replace('\n', '').strip() + pattern = r'^\s*([((]\d+[))]|[A-Za-z]\.\s*|[A-Za-z]?\d+\s*(\.\s*\d+)*(\s|\.|、|.)?|[一二三四五六七八九十]+、)' + cleaned_sentence = re.sub(pattern, '', sentence).replace('\n', '').replace(' ','').strip() sentences1.append(cleaned_sentence) # 存储没有后续关键词的情况 i += 1 else: diff --git a/flask_app/货物标/评分标准提取main.py b/flask_app/货物标/评分标准提取main.py index fd27165..2213a70 100644 --- a/flask_app/货物标/评分标准提取main.py +++ b/flask_app/货物标/评分标准提取main.py @@ -225,6 +225,7 @@ if __name__ == "__main__": # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\2-招标文件_evaluation_method.pdf" # truncate_file = "C:\\Users\\Administrator\\Desktop\\货物标\\output2\\2-招标文件(统计局智能终端二次招标)_evaluation_method.pdf" # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\广水市妇幼招标文件最新(W改)_evaluation_method.pdf" - truncate_file = "C:\\Users\\Administrator\\Desktop\\货物标\\output2\\622二次视频会议磋商文件_evaluation_method.pdf" + # truncate_file = "C:\\Users\\Administrator\\Desktop\\fsdownload\\2d481945-1f82-45a5-8e56-7fafea4a7793\\ztbfile_evaluation_method.pdf" + truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\招标文件(实高电子显示屏)_evaluation_method.pdf" res = combine_evaluation_standards(truncate_file) print(json.dumps(res, ensure_ascii=False, indent=4)) diff --git a/flask_app/货物标/货物标截取pdf.py b/flask_app/货物标/货物标截取pdf.py index 23ab916..69a90c1 100644 --- a/flask_app/货物标/货物标截取pdf.py +++ b/flask_app/货物标/货物标截取pdf.py @@ -1,3 +1,5 @@ +import glob + import fitz from PyPDF2 import PdfReader, PdfWriter import re # 导入正则表达式库 @@ -5,7 +7,6 @@ import os # 用于文件和文件夹操作 from flask_app.main.format_change import docx2pdf - def clean_page_content(text, common_header): # 首先删除抬头公共部分 if common_header: # 确保有公共抬头才进行替换 @@ -21,7 +22,8 @@ def clean_page_content(text, common_header): return text -#PYPDF2版本 + +# PYPDF2版本 def extract_common_header(pdf_path): pdf_document = PdfReader(pdf_path) headers = [] @@ -58,7 +60,7 @@ def extract_common_header(pdf_path): return '\n'.join(common_headers) -#fitz库版本 +# fitz库版本 # def extract_common_header(pdf_path): # doc = fitz.open(pdf_path) # headers = [] @@ -103,6 +105,7 @@ def convert_to_pdf(file_path): return docx2pdf(file_path) return file_path + def judge_file_exist(original_path, new_suffix): # 提取目录路径和原始文件名 directory = os.path.dirname(original_path) @@ -121,6 +124,8 @@ def judge_file_exist(original_path, new_suffix): return new_file_path else: return None + +#合并PDF def merge_pdfs(paths, output_path): pdf_writer = PdfWriter() last_page_text = None # 用于存储上一个PDF的最后一页的文本 @@ -148,6 +153,8 @@ def merge_pdfs(paths, output_path): # 写入合并后的PDF到文件 with open(output_path, 'wb') as out: pdf_writer.write(out) + + def merge_and_cleanup(output_pdf_path, suffix_to_merge): another_file_path = judge_file_exist(output_pdf_path, suffix_to_merge) if another_file_path: @@ -156,6 +163,7 @@ def merge_and_cleanup(output_pdf_path, suffix_to_merge): os.remove(another_file_path) print(f"文件 {another_file_path} 已删除。") + def process_files(file_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix): pdf_path = convert_to_pdf(file_path) result = extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix) @@ -167,6 +175,7 @@ def process_files(file_path, output_folder, begin_pattern, begin_page, end_patte return result return None + def process_input(input_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix): if not os.path.exists(output_folder): os.makedirs(output_folder) @@ -196,7 +205,7 @@ def process_input(input_path, output_folder, begin_pattern, begin_page, end_patt return generated_files -#默认逻辑是start_page匹配上就不再设置了,一般不匹配上目录的原因是设置了begin_page=5,但是匹配'第一章 招标公告'的时候start_page可能会错误匹配到目录。 +# 默认逻辑是start_page匹配上就不再设置了,一般不匹配上目录的原因是设置了begin_page=5,但是匹配'第一章 招标公告'的时候start_page可能会错误匹配到目录。 def extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, common_header, exclusion_pattern=None, output_suffix="normal"): start_page = None @@ -231,7 +240,8 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter if output_suffix == "tobidders_notice": exclusion_pattern = re.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据') start_page, mid_page, end_page = extract_pages_tobidders_notice(pdf_document, begin_pattern, end_pattern, - begin_page, common_header,exclusion_pattern) + begin_page, common_header, + exclusion_pattern) if start_page is None or mid_page is None or end_page is None: print(f"first: {output_suffix} 未找到起始或结束页在文件 {pdf_path} 中!尝试备用提取策略。") return extract_pages_twice_tobidders_notice(pdf_path, output_folder, output_suffix, common_header) @@ -244,7 +254,8 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter # 原有的处理逻辑保持不变 if output_suffix == "qualification1": exclusion_pattern = re.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据') - start_page, end_page = extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, common_header, exclusion_pattern,output_suffix) + start_page, end_page = extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, + common_header, exclusion_pattern, output_suffix) if start_page is None or end_page is None: print(f"first: {output_suffix} 未找到起始或结束页在文件 {pdf_path} 中!尝试备用提取策略。") return extract_pages_twice(pdf_path, output_folder, output_suffix, common_header) @@ -255,7 +266,9 @@ def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_patter print(f"Error processing {pdf_path}: {e}") return None -def extract_pages_tobidders_notice(pdf_document, begin_pattern, end_pattern, begin_page, common_header,exclusion_pattern): + +def extract_pages_tobidders_notice(pdf_document, begin_pattern, end_pattern, begin_page, common_header, + exclusion_pattern): start_page = None mid_page = None end_page = None @@ -274,6 +287,7 @@ def extract_pages_tobidders_notice(pdf_document, begin_pattern, end_pattern, beg break return start_page, mid_page, end_page + def get_patterns_for_procurement(): begin_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:服务|项目|商务).*?要求|' @@ -305,18 +319,21 @@ def get_patterns_for_qualification(): end_pattern_new = re.compile( r'^附件\s*\d+|^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE) - return begin_pattern_new, end_pattern_new + return begin_pattern_new, end_pattern_new + def get_patterns_for_notice(): begin_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:公告|邀请书).*', re.MULTILINE ) end_pattern = re.compile( - r'^(?:第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:投标人须知|磋商须知|供应商须知)+|(?:一\s*、\s*)?(?:投标人须知|磋商须知|供应商须知)前附表)', re.MULTILINE + r'^(?:第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:投标人须知|磋商须知|供应商须知)+|(?:一\s*、\s*)?(?:投标人须知|磋商须知|供应商须知)前附表)', + re.MULTILINE ) return begin_pattern, end_pattern -def extract_pages_twice_tobidders_notice(pdf_path, output_folder, output_suffix, common_header): #投标人须知前附表/正文二次提取 + +def extract_pages_twice_tobidders_notice(pdf_path, output_folder, output_suffix, common_header): # 投标人须知前附表/正文二次提取 begin_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:(?:投标人|磋商|供应商|谈判供应商|磋商供应商)须知前附表)+' ) @@ -332,14 +349,17 @@ def extract_pages_twice_tobidders_notice(pdf_path, output_folder, output_suffix, return None, None # 提取第二部分 start_page2 = end_page1 # 第二部分的开始页就是第一部分的结束页 - _, end_page2 = extract_pages_generic(pdf_document, end_pattern, end_pattern, start_page2 - 1, common_header,exclusion_pattern) + _, end_page2 = extract_pages_generic(pdf_document, end_pattern, end_pattern, start_page2 - 1, common_header, + exclusion_pattern) if end_page2 is None: print(f"second: {output_suffix} 未找到第二部分的结束页在文件 {pdf_path} 中!") return None, None # 保存提取的页面 - path1 = save_extracted_pages(pdf_document, start_page1, end_page1, pdf_path, output_folder, "tobidders_notice_part1") - path2 = save_extracted_pages(pdf_document, start_page2, end_page2, pdf_path, output_folder, "tobidders_notice_part2") + path1 = save_extracted_pages(pdf_document, start_page1, end_page1, pdf_path, output_folder, + "tobidders_notice_part1") + path2 = save_extracted_pages(pdf_document, start_page2, end_page2, pdf_path, output_folder, + "tobidders_notice_part2") return path1, path2 @@ -347,30 +367,31 @@ def extract_pages_twice(pdf_path, output_folder, output_suffix, common_header): exclusion_pattern = re.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据') pdf_document = PdfReader(pdf_path) patterns = None - begin_page=0 + begin_page = 0 if output_suffix == "procurement": patterns = [get_patterns_for_procurement()] - begin_page=5 - elif output_suffix == "evaluation_method" or output_suffix=="qualification2" or output_suffix=="qualification3": + begin_page = 5 + elif output_suffix == "evaluation_method" or output_suffix == "qualification2" or output_suffix == "qualification3": patterns = [get_patterns_for_evaluation_method()] begin_page = 5 elif output_suffix == "qualification1": patterns = [get_patterns_for_qualification()] # This now returns a tuple of pattern pairs begin_page = 5 elif output_suffix == "notice": - patterns=[get_patterns_for_notice()] + patterns = [get_patterns_for_notice()] begin_page = 0 # Try each set of patterns until a valid range is found for pattern_pair in patterns: - start_page, end_page = extract_pages_generic(pdf_document, pattern_pair[0], pattern_pair[1], begin_page, common_header, - exclusion_pattern,output_suffix) + start_page, end_page = extract_pages_generic(pdf_document, pattern_pair[0], pattern_pair[1], begin_page, + common_header, + exclusion_pattern, output_suffix) if start_page is not None and end_page is not None: break if start_page is None or end_page is None: if output_suffix == "qualification1": print(f"second: {output_suffix} 未找到起始或结束页在文件 {pdf_path} 中!") print("third:尝试提取评分办法章节...") - temp=truncate_pdf_main(pdf_path,output_folder,2,"qualification2") + temp = truncate_pdf_main(pdf_path, output_folder, 2, "qualification2") if len(temp) > 0: return temp[0] else: @@ -381,9 +402,33 @@ def extract_pages_twice(pdf_path, output_folder, output_suffix, common_header): return save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix) +# def save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix): +# if output_suffix=='notice': +# print(start_page) +# base_file_name = os.path.splitext(os.path.basename(pdf_path))[0] +# output_pdf_path = os.path.join(output_folder, f"{base_file_name}_{output_suffix}.pdf") +# output_doc = PdfWriter() +# for page_num in range(start_page, end_page + 1): +# output_doc.add_page(pdf_document.pages[page_num]) +# with open(output_pdf_path, 'wb') as f: +# output_doc.write(f) +# print(f"{output_suffix} 已截取并保存页面从 {start_page} 到 {end_page} 为 {output_pdf_path}") +# return output_pdf_path + + def save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix): base_file_name = os.path.splitext(os.path.basename(pdf_path))[0] output_pdf_path = os.path.join(output_folder, f"{base_file_name}_{output_suffix}.pdf") + + if output_suffix == 'notice' and start_page - 1 >= 0: + before_pdf_path = os.path.join(output_folder, f"{base_file_name}_before.pdf") + before_doc = PdfWriter() + for page_num in range(0, start_page): + before_doc.add_page(pdf_document.pages[page_num]) + with open(before_pdf_path, 'wb') as f: + before_doc.write(f) + print(f"已保存页面从 0 到 {start_page - 1} 为 {before_pdf_path}") + output_doc = PdfWriter() for page_num in range(start_page, end_page + 1): output_doc.add_page(pdf_document.pages[page_num]) @@ -392,6 +437,41 @@ def save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_fo print(f"{output_suffix} 已截取并保存页面从 {start_page} 到 {end_page} 为 {output_pdf_path}") return output_pdf_path +#合并封面+招标公告+投标人须知前附表+须知正文 +def merge_selected_pdfs(output_folder, truncate_files, output_path): + """ + 合并 output_folder 中以 _before 结尾的 PDF 文件,以及 truncate_files 中的指定文件。 + + 参数: + - output_folder (str): 包含以 _before 结尾的 PDF 文件的文件夹路径。 + - truncate_files (list): 包含 PDF 文件路径的列表。 + - output_path (str): 合并后的 PDF 文件保存路径。 + """ + # 1. 查找 output_folder 中以 _before.pdf 结尾的 PDF 文件 + before_pdfs = glob.glob(os.path.join(output_folder, '*_before.pdf')) + print(f"找到 {len(before_pdfs)} 个以 '_before.pdf' 结尾的文件。") + + # 2. 获取 truncate_files 中指定的文件(索引5、3、4) + selected_indices = [5, 3, 4] # 注意索引从0开始 + selected_truncate_pdfs = [] + for idx in selected_indices: + if idx < len(truncate_files): + selected_truncate_pdfs.append(truncate_files[idx]) + print(f"选中 truncate_files[{idx}]: {truncate_files[idx]}") + else: + print(f"truncate_files 列表中没有索引为 {idx} 的元素。") + + # 3. 合并所有 PDF 文件 + all_pdfs_to_merge = before_pdfs + selected_truncate_pdfs + print(f"总共将要合并的 PDF 文件数量: {len(all_pdfs_to_merge)}") + + if not all_pdfs_to_merge: + print("没有找到要合并的 PDF 文件。") + return + + # 调用 merge_pdfs 函数 + merge_pdfs(all_pdfs_to_merge, output_path) + def truncate_pdf_main(input_path, output_folder, selection, output_suffix="default"): if selection == 1: @@ -423,17 +503,18 @@ def truncate_pdf_main(input_path, output_folder, selection, output_suffix="defau r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE ) local_output_suffix = "qualification1" - elif selection ==4: #投标人须知前附表和正文 - begin_page=1 + elif selection == 4: # 投标人须知前附表和正文 + begin_page = 1 begin_pattern = re.compile( - r'^(?:第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:投标人|磋商|供应商|谈判供应商|磋商供应商)须知+|(?:一\s*、\s*)?(?:投标人|磋商|供应商)须知前附表)', re.MULTILINE + r'^(?:第[一二三四五六七八九十百千]+(?:章|部分)\s*(?:投标人|磋商|供应商|谈判供应商|磋商供应商)须知+|(?:一\s*、\s*)?(?:投标人|磋商|供应商)须知前附表)', + re.MULTILINE ) - end_pattern=re.compile( + end_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE ) local_output_suffix = "tobidders_notice" - elif selection==5: - begin_page=0 + elif selection == 5: # 招标公告 + begin_page = 0 begin_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:公告|邀请书).*' ) @@ -453,21 +534,24 @@ 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): 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) return truncate_files # TODO:交通智能系统和招标(1)(1)文件有问题 sele=4的时候excludsion有问题 if __name__ == "__main__": - input_path = "C:\\Users\\Administrator\\Desktop\\fsdownload\\b4601ea1-f087-4fa2-88ae-336ad4d8e1e9\\ztbfile.pdf" - output_folder = "C:\\Users\\Administrator\\Desktop\\fsdownload\\b4601ea1-f087-4fa2-88ae-336ad4d8e1e9" - files=truncate_pdf_multiple(input_path,output_folder) + input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\094定稿-湖北工业大学轻武器模拟射击设备采购项目招标文件.pdf" + output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub" + files = truncate_pdf_multiple(input_path, output_folder) print(files) - # selection = 1 # 例如:1 - 商务技术服务要求, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2(与评标办法一致) 4.投标人须知前附表part1 投标人须知正文part2 + # selection = 1 # 例如:1 - 商务技术服务要求, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2(与评标办法一致) 4.投标人须知前附表part1 投标人须知正文part2 5-公告 # generated_files = truncate_pdf_main(input_path, output_folder, selection) diff --git a/flask_app/货物标/货物标解析main.py b/flask_app/货物标/货物标解析main.py index aa2933f..4622394 100644 --- a/flask_app/货物标/货物标解析main.py +++ b/flask_app/货物标/货物标解析main.py @@ -1,4 +1,4 @@ -#竞磋 竞谈 磋商 询价 邀请 单一来源 +# 竞磋 竞谈 磋商 询价 邀请 单一来源 import json import time @@ -15,16 +15,21 @@ from flask_app.货物标.无效标和废标和禁止投标整合main import comb from flask_app.货物标.资格审查main import combine_qualification_review from flask_app.货物标.评分标准提取main import combine_evaluation_standards import logging + + def get_global_logger(unique_id): if unique_id is None: return logging.getLogger() # 获取默认的日志器 logger = logging.getLogger(unique_id) return logger -logger=None + +logger = None # 创建全局线程池 executor = ThreadPoolExecutor() + + def preprocess_files(output_folder, file_path, file_type, unique_id): logger.info("starting 文件预处理...") logger.info("output_folder..." + output_folder) @@ -36,25 +41,27 @@ def preprocess_files(output_folder, file_path, file_type, unique_id): elif file_type == 2: # pdf pdf_path = file_path docx_path = pdf2docx(pdf_path) # 将pdf转换为docx以供上传到知识库 - elif file_type ==3: #doc - pdf_path=docx2pdf(file_path) - docx_path=pdf2docx(pdf_path) + elif file_type == 3: # doc + pdf_path = docx2pdf(file_path) + docx_path = pdf2docx(pdf_path) else: logger.error("Unsupported file type provided. Preprocessing halted.") return None - # 异步上传知识库 - future_knowledge = executor.submit(addfileToKnowledge, docx_path, "招标解析" + unique_id) + # # 异步上传知识库 + # future_knowledge = executor.submit(addfileToKnowledge, docx_path, "招标解析" + unique_id) # 调用截取PDF多次 - truncate_files = truncate_pdf_multiple(pdf_path, output_folder) #index: 0->商务技术服务要求 1->评标办法 2->资格审查 3->投标人须知前附表 4->投标人须知正文 + truncate_files = truncate_pdf_multiple(pdf_path, + output_folder) # index: 0->商务技术服务要求 1->评标办法 2->资格审查 3->投标人须知前附表 4->投标人须知正文 # 处理各个部分 - invalid_docpath = docx_path #docx截取无效标部分 - procurement_path = truncate_files[0] #商务技术服务要求 - evaluation_method_path = truncate_files[1] #评标办法 - qualification_path=truncate_files[2] #资格审查 - tobidders_notice_path = truncate_files[4] #投标人须知正文 - notice_path=truncate_files[5] + invalid_docpath = docx_path # docx截取无效标部分 + procurement_path = truncate_files[0] # 商务技术服务要求 + evaluation_method_path = truncate_files[1] # 评标办法 + qualification_path = truncate_files[2] # 资格审查 + tobidders_notice_path = truncate_files[4] # 投标人须知正文 + notice_path = truncate_files[5] + merged_baseinfo_path = truncate_files[6] # 合并封面+招标公告+投标人须知前附表+须知正文 clause_path = convert_clause_to_json(tobidders_notice_path, output_folder) # 投标人须知正文条款pdf->json logger.info("文件预处理done") @@ -66,23 +73,27 @@ def preprocess_files(output_folder, file_path, file_type, unique_id): 'procurement_path': procurement_path, 'evaluation_method_path': evaluation_method_path, 'qualification_path': qualification_path, - 'notice_path':notice_path, - 'knowledge_future': future_knowledge, # 返回 Future 对象 + 'notice_path': notice_path, + # 'knowledge_future': future_knowledge, # 返回 Future 对象 'clause_path': clause_path, - 'invalid_docpath': invalid_docpath + 'invalid_docpath': invalid_docpath, + 'merged_baseinfo_path': merged_baseinfo_path } -def fetch_project_basic_info(knowledge_name, output_folder, clause_path): # 投标人须知前附表 + +def fetch_project_basic_info(merged_baseinfo_path, procurement_file_path): # 投标人须知前附表 logger.info("starting基础信息...") - basic_res = combine_basic_info(knowledge_name,output_folder,clause_path) + basic_res = combine_basic_info(merged_baseinfo_path, procurement_file_path) logger.info("基础信息done") return basic_res -def fetch_qualification_review(output_folder,qualification_path,notice_path,knowledge_name): #资格审查 + +def fetch_qualification_review(output_folder, qualification_path, notice_path,merged_baseinfo_path): # 资格审查 logger.info("starting资格审查...") - review_standards_res = combine_qualification_review(output_folder,qualification_path,notice_path, knowledge_name) + review_standards_res = combine_qualification_review(output_folder, qualification_path, notice_path,merged_baseinfo_path) logger.info("资格审查done") return review_standards_res + def fetch_evaluation_standards(evaluation_method_path): # 评标细则 logger.info("starting 商务评分和技术评分...") # 获取评标办法前附表的字典结果 @@ -98,7 +109,8 @@ def fetch_evaluation_standards(evaluation_method_path): # 评标细则 "commercial_standards": commercial_standards } -#TODO:doc文档转换 + +# TODO:doc文档转换 def fetch_invalid_requirements(invalid_docpath, output_folder): # 废标项要求:千问 logger.info("starting无效标与废标...") @@ -111,16 +123,18 @@ def fetch_bidding_documents_requirements(clause_path): logger.info("starting投标文件要求...") fetch_bidding_documents_requirements_json = extract_from_notice(clause_path, 1) logger.info("投标文件要求done...") - return {"投标文件要求":fetch_bidding_documents_requirements_json} + return {"投标文件要求": fetch_bidding_documents_requirements_json} + # 开评定标流程 def fetch_bid_opening(clause_path): logger.info("starting开评定标流程...") fetch_bid_opening_json = extract_from_notice(clause_path, 2) logger.info("开评定标流程done...") - return {"开评定标流程":fetch_bid_opening_json} + return {"开评定标流程": fetch_bid_opening_json} -def goods_bid_main(output_folder,file_path, file_type, unique_id): + +def goods_bid_main(output_folder, file_path, file_type, unique_id): global logger logger = get_global_logger(unique_id) @@ -132,10 +146,19 @@ def goods_bid_main(output_folder,file_path, file_type, unique_id): with concurrent.futures.ThreadPoolExecutor() as executor: # 立即启动不依赖 knowledge_name 和 index 的任务 futures = { - 'evaluation_standards': executor.submit(fetch_evaluation_standards, processed_data['evaluation_method_path']), - 'invalid_requirements': executor.submit(fetch_invalid_requirements, processed_data['invalid_docpath'],output_folder), - 'bidding_documents_requirements': executor.submit(fetch_bidding_documents_requirements, processed_data['clause_path']), - 'opening_bid': executor.submit(fetch_bid_opening, processed_data['clause_path']) + 'evaluation_standards': executor.submit(fetch_evaluation_standards, + processed_data['evaluation_method_path']), + 'invalid_requirements': executor.submit(fetch_invalid_requirements, processed_data['invalid_docpath'], + output_folder), + 'bidding_documents_requirements': executor.submit(fetch_bidding_documents_requirements, + processed_data['clause_path']), + 'opening_bid': executor.submit(fetch_bid_opening, processed_data['clause_path']), + 'base_info': executor.submit(fetch_project_basic_info, processed_data['merged_baseinfo_path'], + processed_data['procurement_path']), + 'qualification_review': executor.submit(fetch_qualification_review, output_folder, + processed_data['qualification_path'], + processed_data['notice_path'], + processed_data['merged_baseinfo_path']), } # 提前处理这些不依赖的任务,按完成顺序返回 @@ -148,8 +171,10 @@ def goods_bid_main(output_folder,file_path, file_type, unique_id): technical_standards = result["technical_standards"] commercial_standards = result["commercial_standards"] # 分别返回技术标和商务标 - yield json.dumps({'technical_standards': transform_json_values(technical_standards)}, ensure_ascii=False) - yield json.dumps({'commercial_standards': transform_json_values(commercial_standards)}, ensure_ascii=False) + yield json.dumps({'technical_standards': transform_json_values(technical_standards)}, + ensure_ascii=False) + yield json.dumps({'commercial_standards': transform_json_values(commercial_standards)}, + ensure_ascii=False) else: # 处理其他任务的结果 yield json.dumps({key: transform_json_values(result)}, ensure_ascii=False) @@ -157,41 +182,43 @@ def goods_bid_main(output_folder,file_path, file_type, unique_id): logger.error(f"Error processing {key}: {exc}") yield json.dumps({'error': f'Error processing {key}: {str(exc)}'}, ensure_ascii=False) - # 只有在需要 knowledge_name 和 index 时才等待 future_knowledge 完成 - try: - knowledge_name = "招标解析" + unique_id - index = processed_data['knowledge_future'].result() # 阻塞等待知识库上传任务完成 - - # 提交依赖 knowledge_name 和 index 的任务 - future_dependencies = { - 'base_info': executor.submit(fetch_project_basic_info, knowledge_name, - output_folder, processed_data['clause_path']), - 'qualification_review': executor.submit(fetch_qualification_review,output_folder, processed_data['qualification_path'],processed_data['notice_path'],knowledge_name), - } - # 按完成顺序返回依赖任务的结果 - for future in concurrent.futures.as_completed(future_dependencies.values()): - key = next(k for k, v in future_dependencies.items() if v == future) - try: - result = future.result() - yield json.dumps({key: transform_json_values(result)}, ensure_ascii=False) - except Exception as exc: - logger.error(f"Error processing {key}: {exc}") - yield json.dumps({'error': f'Error processing {key}: {str(exc)}'}, ensure_ascii=False) - - except Exception as e: - logger.error(f"Error uploading to knowledge base: {e}") - yield json.dumps({'error': f'Knowledge upload failed: {str(e)}'}, ensure_ascii=False) + # # 只有在需要 knowledge_name 和 index 时才等待 future_knowledge 完成 + # try: + # knowledge_name = "招标解析" + unique_id + # index = processed_data['knowledge_future'].result() # 阻塞等待知识库上传任务完成 + # + # # 提交依赖 knowledge_name 和 index 的任务 + # future_dependencies = { + # 'base_info': executor.submit(fetch_project_basic_info,processed_data['merged_baseinfo_path'], processed_data['procurement_path']), + # 'qualification_review': executor.submit(fetch_qualification_review, output_folder, + # processed_data['qualification_path'], + # processed_data['notice_path'], processed_data['merged_baseinfo_path']), + # } + # # 按完成顺序返回依赖任务的结果 + # for future in concurrent.futures.as_completed(future_dependencies.values()): + # key = next(k for k, v in future_dependencies.items() if v == future) + # try: + # result = future.result() + # yield json.dumps({key: transform_json_values(result)}, ensure_ascii=False) + # except Exception as exc: + # logger.error(f"Error processing {key}: {exc}") + # yield json.dumps({'error': f'Error processing {key}: {str(exc)}'}, ensure_ascii=False) + # + # except Exception as e: + # logger.error(f"Error uploading to knowledge base: {e}") + # yield json.dumps({'error': f'Knowledge upload failed: {str(e)}'}, ensure_ascii=False) # 删除知识索引 - deleteKnowledge(index) + # deleteKnowledge(index) + if __name__ == "__main__": output_folder = "flask_app/static/output/zytest1" start_time = time.time() - file_type = 1 #1:docx 2:pdf 3:其他 + file_type = 1 # 1:docx 2:pdf 3:其他 input_file = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\6.2定版视频会议磋商文件.pdf" goods_bid_main(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.") \ No newline at end of file + print(f"Function execution took {elapsed_time} seconds.") diff --git a/flask_app/货物标/资格审查main.py b/flask_app/货物标/资格审查main.py index 3d57260..c49a7b7 100644 --- a/flask_app/货物标/资格审查main.py +++ b/flask_app/货物标/资格审查main.py @@ -214,14 +214,21 @@ def preprocess_value(value): def generate_questions(input_list): template = ( - "关于'{key}',{value}的内容是怎样的?请按json格式给我提供信息,键名为'{key}',而键值需要完全与原文保持一致,不要擅自总结、删减,如果存在未知信息,请在对应键值处填'未知'。" + "关于{modified_key},{value}的内容是怎样的?请按json格式给我提供信息," + "键名为'{original_key}',而键值需要完全与原文保持一致,不要擅自总结、删减," + "如果存在未知信息,请在对应键值处填'未知'。" ) questions = [] for input_dict in input_list: - for key, value in input_dict.items(): - processed_value = preprocess_value(value) - question = template.format(key=key, value=processed_value) + for original_key, value in input_dict.items(): + # 将第一个 '.' 替换为 '中的' + if '.' in original_key: + modified_key = original_key.replace('.', '中的', 1) + else: + modified_key = original_key # 如果没有 '.', 保持不变 + processed_value = preprocess_value(value) # 假设这是你需要的预处理函数 + question = template.format(modified_key=modified_key, original_key=original_key, value=processed_value) questions.append(question) return questions @@ -319,7 +326,7 @@ def process_match_keys(match_keys, clause_path_file): #处理如'符合本采购文件第一章第二款要求'的情况,跳转到指定地方摘取内容 -def process_additional_queries(combined_res, match_keys, output_folder, notice_path, knowledge_name): +def process_additional_queries(combined_res, match_keys, output_folder, notice_path, merged_baseinfo_path): """ 处理额外的查询并更新结果。 @@ -339,18 +346,21 @@ def process_additional_queries(combined_res, match_keys, output_folder, notice_p if updated_match_keys != match_keys: form_response_dict = update_json_data(combined_res, updated_match_keys) else: + # 招标公告没找到内容,继续问大模型 ques = generate_questions(match_keys) - results = multi_threading(ques, knowledge_name) - - for _, response in results: - if response and len(response) > 1: - try: - temp = extract_content_from_json(response[1]) - updated_match_keys.append(temp) - except Exception as e: - print(f"形式响应评审:Error processing response: {e}") - else: - print(f"形式响应评审:Warning: Missing or incomplete response data.") + file_id = upload_file(merged_baseinfo_path) + qianwen_results = multi_threading(ques, "", file_id, 2) # 1代表使用百炼rag 2代表使用qianwen-long + updated_match_keys = [clean_json_string(res) for _, res in qianwen_results] if qianwen_results else [] + # results = multi_threading(ques, knowledge_name) + # for _, response in results: + # if response and len(response) > 1: + # try: + # temp = extract_content_from_json(response[1]) + # updated_match_keys.append(temp) + # except Exception as e: + # print(f"形式响应评审:Error processing response: {e}") + # else: + # print(f"形式响应评审:Warning: Missing or incomplete response data.") form_response_dict = update_json_data(combined_res, updated_match_keys) @@ -408,6 +418,7 @@ if __name__ == "__main__": output_folder="C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub" qualification_path = "C:\\Users\\Administrator\\Desktop\\货物标\\output3\\094定稿-湖北工业大学轻武器模拟射击设备采购项目招标文件_qualification2.pdf" notice_path="C:\\Users\\Administrator\\Desktop\\货物标\\output5\\094定稿-湖北工业大学轻武器模拟射击设备采购项目招标文件_notice.pdf" - knowledge_name = "6.2视频会议docx" - res = combine_qualification_review(output_folder,qualification_path, notice_path,knowledge_name) + # knowledge_name = "6.2视频会议docx" + baseinfo_path="C:\\Users\\Administrator\\Desktop\\货物标\\zboutpub\\merged_baseinfo.pdf" + res = combine_qualification_review(output_folder,qualification_path, notice_path,baseinfo_path) print(json.dumps(res, ensure_ascii=False, indent=4))