From 4d5dc9ea4ec0fc6282ad10487dd0aa105c729db2 Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Tue, 7 Jan 2025 17:35:11 +0800 Subject: [PATCH] =?UTF-8?q?1.7=20=E5=95=86=E5=8A=A1=E8=A6=81=E6=B1=82?= =?UTF-8?q?=E6=A0=87=E9=A2=98=E5=B8=A6=E6=98=9F=E4=BF=9D=E7=95=99+?= =?UTF-8?q?=E8=AF=84=E5=88=86=E9=A1=B9=E6=89=8B=E5=8A=A8=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E6=80=BB=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flask_app/general/商务技术评分提取.py | 170 ++++++++++++------ flask_app/general/截取pdf通用函数.py | 4 +- flask_app/general/无效标和废标公共代码.py | 39 ++-- .../{download.py => download_old.py} | 0 ...分整合.py => 商务评分技术评分整合old_version.py} | 3 +- ...商务技术评分.py => 工程标商务技术评分_old.py} | 0 ...N版).py => 废弃的投标人须知正文提取(结构化JSON版_old.py} | 0 flask_app/old_version/招标文件解析_old.py | 2 +- flask_app/old_version/解析old_old.py | 2 +- ...标准提取main.py => 评分标准提取main_old.py} | 13 +- flask_app/routes/偏离表main.py | 4 +- flask_app/工程标/截取pdf工程标版.py | 10 +- flask_app/货物标/技术参数要求提取.py | 26 ++- 13 files changed, 171 insertions(+), 102 deletions(-) rename flask_app/old_version/{download.py => download_old.py} (100%) rename flask_app/old_version/{商务评分技术评分整合.py => 商务评分技术评分整合old_version.py} (98%) rename flask_app/old_version/{工程标商务技术评分.py => 工程标商务技术评分_old.py} (100%) rename flask_app/old_version/{废弃的投标人须知正文提取(结构化JSON版).py => 废弃的投标人须知正文提取(结构化JSON版_old.py} (100%) rename flask_app/old_version/{评分标准提取main.py => 评分标准提取main_old.py} (95%) diff --git a/flask_app/general/商务技术评分提取.py b/flask_app/general/商务技术评分提取.py index 48adc7e..00e23f1 100644 --- a/flask_app/general/商务技术评分提取.py +++ b/flask_app/general/商务技术评分提取.py @@ -4,10 +4,10 @@ import re import time from collections import defaultdict -from flask_app.general.doubao import get_total_tokens, read_txt_to_string, doubao_model +from flask_app.general.doubao import read_txt_to_string from flask_app.general.file2markdown import convert_file_to_markdown from flask_app.general.format_change import get_pdf_page_count, pdf2docx -from flask_app.general.json_utils import clean_json_string, extract_content_from_json +from flask_app.general.json_utils import extract_content_from_json from flask_app.general.model_continue_query import process_continue_answers from flask_app.general.通义千问long import upload_file, qianwen_long, qianwen_plus @@ -23,51 +23,49 @@ def remove_unknown_scores(data): return [remove_unknown_scores(item) for item in data] else: return data -def combine_technical_and_business(data, target_values): - # target_values = ['技术', '设计', '实施'] +def combine_technical_and_business(data): data = remove_unknown_scores(data) - extracted_data = {} # 根级别存储所有数据 - technical_found = False - business_found = False + extracted_data = { + '技术评分': { + '技术评分': {} # 初始化技术评分 + }, + '商务评分': { + '商务评分': {}, # 初始化商务评分 + '投标报价评分': {} # 初始化投标报价评分 + # '其他评分' 将在需要时动态添加 + } + } - def extract_nested(data, parent_key='', is_technical=False, is_business=False): - nonlocal technical_found, business_found + def extract_nested(data): if isinstance(data, dict): for key, value in data.items(): - current_key = f"{parent_key}.{key}" if parent_key else key + # 区分 '技术评分' + if key == '技术评分': + total_score=compute_total_score({key:value}) + extracted_data['技术评分']['技术评分'] = value + # 匹配到后,不再递归处理其子项 + continue - # 检查是否为技术标的内容 - if any(target in key for target in target_values): - if not is_technical: - if '技术评分' not in extracted_data: - extracted_data['技术评分'] = {} # 初始化 '技术评分' 字典 - extracted_data['技术评分'][key] = value - technical_found = True - continue + # 区分 '商务评分' + elif key == '商务评分': + extracted_data['商务评分']['商务评分'] = value + # 匹配到后,不再递归处理其子项 + continue - # 默认其他所有内容都归为商务标 + # 区分 '投标报价评分' + elif key == '投标报价评分': + extracted_data['商务评分']['投标报价评分'] = value + # 匹配到后,不再递归处理其子项 + continue + + # 其他键名归为 '其他评分' else: - if not is_business: - if '商务评分' not in extracted_data: - extracted_data['商务评分'] = {} # 确保它是字典 - extracted_data['商务评分'][key] = value - business_found = True - continue - - if isinstance(value, dict) or isinstance(value, list): - extract_nested(value, current_key, is_technical, is_business) - - elif isinstance(data, list): - for index, item in enumerate(data): - extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) + if '其他评分' not in extracted_data['商务评分']: + extracted_data['商务评分']['其他评分'] = {} + extracted_data['商务评分']['其他评分'][key] = value + continue extract_nested(data) - - if not technical_found: - extracted_data['技术评分'] = '' - if not business_found: - extracted_data['商务评分'] = '' - return extracted_data # 防止外键只有一个'一包'的情况 @@ -83,6 +81,72 @@ def process_data_based_on_key(data): # 如果条件不满足,则返回原始字典 return data +def compute_total_score(data): + """ + 计算传入字典的总分。 + + 规则: + - 输入字典只有一个外层键。 + - 遍历该外层键的所有子键: + - 如果子键名中包含 '(XX分)' 或 '(XX分)',提取 XX 并累加到总分中,不再处理其子项。 + - 如果子键名中不包含这样的分数,遍历其子项,查找键名为 '评分' 的键,提取分数并累加。 + - '评分' 的值可以是 'XX分' 或整数。 + - 如果没有找到 '评分' 键,则该项分数为 0。 + """ + total = 0 + + # 确保输入数据为字典且只有一个外层键 + if not isinstance(data, dict) or len(data) != 1: + raise ValueError("输入数据必须是一个只有一个外层键的字典。") + + # 获取唯一的外层键和值 + outer_key, outer_value = next(iter(data.items())) + + # 更新后的正则表达式,匹配中英文括号中的分数,如 '(24分)' 或 '(24分)' + score_pattern = re.compile(r'[((](\d+)分[))]') + + def process_node(node): + nonlocal total + if isinstance(node, dict): + for key, value in node.items(): + # 检查键名中是否包含 '(XX分)' 或 '(XX分)' + match = score_pattern.search(key) + if match: + score = int(match.group(1)) + total += score + # 匹配到后,不再递归处理其子项 + continue + elif key == '评分': + if isinstance(value, str): + # 提取 '评分' 键的值中的数字,如 '20分' + match_score = re.match(r'(\d+)分', value) + if match_score: + score = int(match_score.group(1)) + total += score + else: + # 如果 '评分' 值不符合格式,默认加 0 + total += 0 + elif isinstance(value, int): + # 如果 '评分' 键的值是整数,直接累加 + total += value + else: + # 如果 '评分' 键的值既不是字符串也不是整数,默认加 0 + total += 0 + else: + # 如果键名不包含分数,递归处理其子项 + process_node(value) + elif isinstance(node, list): + for item in node: + process_node(item) + else: + # 如果是其他类型的数据,忽略 + pass + + # 开始递归处理外层键的值 + process_node(outer_value) + + return total + def reorganize_data(input_dict, include=None): """ 重组输入字典,将“技术评分”和“商务评分”提升为最外层键, @@ -167,7 +231,6 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): 格式要求: 1.总体结构: -JSON 的最外层包含三个键:技术评分、商务评分 和 投标报价评分。 - -最外层三个键名后需附加括号,括号中注明该大项评分的总分,如'技术评分(18分)',若无具体评分,则无需添加该括号。 -每个大项(如技术评分、商务评分)下包含具体的评分项,评分项按以下规则表示。 2.评分项表示规则: -层级嵌套规则: @@ -179,7 +242,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): -字典个数: 默认为1个字典,若某评分因素包括多个评分标准(多个表格单元格),可以用多个并列字典表示。 -字典结构如下: - 评分:该评分标准的总分(如 8分);不能是一个范围数字(如0-8分);若为定性指标(如“合格制”),可标明相应的定性指标;无评分时可删去'评分'键值对。 + 评分:该评分标准的总分(如 8分),字符串类型;不能是一个范围数字(如0-8分);若为定性指标(如“合格制”),可标明相应的定性指标;无评分时可删去'评分'键值对。 要求:说明评分标准或要求。 -禁止情况: 禁止将同个单元格内的内容拆分至多个字典中;禁止遗漏单元格内任何信息,包括注的内容。 @@ -208,7 +271,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): 以下为示例输出,仅供格式参考: { "一包": { - "技术评分(26分)": { + "技术评分": { "实施方案(16分)":{ "总体实施方案":[ { @@ -231,7 +294,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): ], "备注": "技术标采用暗标形式,暗标不得出现投标人名称、人员姓名。" }, - "商务评分(9分)": { + "商务评分": { "主要监理岗位的职责": [ { "评分": "4分", @@ -267,7 +330,6 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): 格式要求: 1.总体结构: -JSON 的最外层包含三个键:'技术评分'、'商务评分' 和 '投标报价评分'。 - -最外层三个键名后需附加括号,括号中注明该大项评分的总分,如'技术评分(18分)',若无具体评分,则无需添加该括号。 -每个大项(如技术评分、商务评分)下包含具体的评分项,评分项按以下规则表示。 2.评分项表示规则: -层级嵌套规则: @@ -279,7 +341,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): -字典个数: 默认为1个字典,若某评分因素包括多个评分标准(多个表格单元格),可以用多个并列字典表示。 -字典结构如下: - 评分:该评分标准的总分(如 8分);不能是一个范围数字(如0-8分);若为定性指标(如“合格制”),可标明相应的定性指标;无评分时可删去'评分'键值对。 + 评分:该评分标准的总分(如 8分),字符串类型;不能是一个范围数字(如0-8分);若为定性指标(如“合格制”),可标明相应的定性指标;无评分时可删去'评分'键值对。 要求:说明评分标准或要求。 -禁止情况: 禁止将同个单元格内的内容拆分至多个字典中;禁止遗漏单元格内任何信息,包括注的内容。 @@ -308,7 +370,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): 以下为示例输出,仅供格式参考: { "一包": { - "技术评分(18分)": { + "技术评分": { "产品技术响应(8分)":{ "常规参数符合":[ { @@ -331,7 +393,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): ], "备注": "注:若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求,则本项技术部分不得分。" }, - "商务评分(9分)": { + "商务评分": { "主要监理岗位的职责": [ { "评分": "4分", @@ -390,9 +452,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): temp_final.update(continued_results) result_data = process_data_based_on_key(temp_final) # 处理不知名外键的情况 include = ['一包', '二包', '三包', '四包', '五包'] - target_values = ['技术', '设计', '实施'] updated_jsons = {} - # 检查是否有外层键匹配 include 列表 if any(key for key in result_data if any(included in key for included in include)): # 检查result_data中的任何键是否包含include列表中的任意一个项。 @@ -400,11 +460,10 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): for key in result_data: if any(item in key for item in include): inner_dict = result_data[key] - updated_jsons[key] = combine_technical_and_business(inner_dict, - target_values) # 对于分包,单独对分包内的'技术评分''商务评分'作处理 + updated_jsons[key] = combine_technical_and_business(inner_dict) # 对于分包,单独对分包内的'技术评分''商务评分'作处理 else: # 没有匹配的项,对整个字典运行 - updated_jsons = combine_technical_and_business(result_data, target_values) + updated_jsons = combine_technical_and_business(result_data) final_res = reorganize_data(updated_jsons, include) # 重新组织字典,尤其是分包的情况 return final_res @@ -450,16 +509,19 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): #目前评分这块如果表格过长,会有问题,可以考虑textin+doubao,小于20页用text,>20页转word->qianwen-long +#TODO:代码计算总分,商务评分修改 +#TODO:废标项,增加对表格的提取+排除重复项 + if __name__ == "__main__": start_time=time.time() # truncate_file=r"C:\Users\Administrator\Desktop\招标文件-采购类\tmp2\2024-新疆-塔城地区公安局食药环分局快检实验室项目_evaluation_method.pdf" - evaluation_method_path = r'C:\Users\Administrator\Desktop\招标文件\招标04_evaluation_method.pdf' - invalid_path=r'C:\Users\Administrator\Desktop\文件解析问题\文件解析问题\1414cb9c-7bf4-401c-8761-2acde151b9c2\ztbfile.docx' + evaluation_method_path = r'D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\invalid_del.docx' + invalid_path=r'D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\invalid_del.docx' # 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\\fsdownload\\2d481945-1f82-45a5-8e56-7fafea4a7793\\ztbfile_evaluation_method.pdf" # truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\ztbfile_evaluation_method.pdf" - res = combine_evaluation_standards(evaluation_method_path,invalid_path,1) + res = combine_evaluation_standards(evaluation_method_path,invalid_path,2) print(json.dumps(res, ensure_ascii=False, indent=4)) end_time=time.time() print("elapsed time:"+str(end_time-start_time)) \ No newline at end of file diff --git a/flask_app/general/截取pdf通用函数.py b/flask_app/general/截取pdf通用函数.py index 886a32c..97f0bf3 100644 --- a/flask_app/general/截取pdf通用函数.py +++ b/flask_app/general/截取pdf通用函数.py @@ -151,11 +151,11 @@ def get_invalid_file(file_path, output_folder, common_header,begin_page): regex.MULTILINE ), regex.compile( - r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:响应|投标).*?格式.*', + r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:响应|投标|应答).*?格式.*', regex.MULTILINE ), regex.compile( - r"\s*(投标文件|响应文件|响应性文件)(?:的)?格式\s*", + r"\s*(投标文件|响应文件|响应性文件|应答文件)(?:的)?格式\s*", regex.MULTILINE ) ] diff --git a/flask_app/general/无效标和废标公共代码.py b/flask_app/general/无效标和废标公共代码.py index 029bb17..fcf82ab 100644 --- a/flask_app/general/无效标和废标公共代码.py +++ b/flask_app/general/无效标和废标公共代码.py @@ -4,7 +4,8 @@ import re import regex import time from concurrent.futures import ThreadPoolExecutor -from flask_app.general.doubao import doubao_model, generate_full_user_query +from flask_app.general.doubao import generate_full_user_query +from flask_app.general.通义千问long import qianwen_plus from flask_app.general.通用功能函数 import process_string_list from collections import OrderedDict from docx import Document @@ -206,9 +207,6 @@ def preprocess_paragraphs(paragraphs): continue else: if flag: - # 当前段落不以数字序号开头,且 flag 为 True - if "供应商公章" in current_text: - print("yes") if not list_item_pattern.match(current_text): if processed: # **新增逻辑开始** @@ -220,8 +218,8 @@ def preprocess_paragraphs(paragraphs): pattern_numeric_header_fallback.match(next_non_empty_text) ) - if is_next_numbered: - # 只有在下一个段落以数字序号开头时,才将当前段落追加到上一个段落 + if is_next_numbered and len(processed[-1]) > 30: + # 只有在下一个段落以数字序号开头且上一个段落长度大于30时,才将当前段落追加到上一个段落 processed[-1] = processed[-1] + ' ' + current_text else: # 否则,不追加,而是作为新的段落添加 @@ -747,7 +745,7 @@ def handle_query(file_path, user_query, output_file, result_key, keywords): # 生成用户查询 user_query = generate_full_user_query(output_file, user_query) - model_ans = doubao_model(user_query) # 豆包模型返回结果 + model_ans = qianwen_plus(user_query) # 豆包模型返回结果 # file_id = upload_file(output_file) # model_ans = qianwen_long(file_id, user_query) num_list = process_string_list(model_ans) # 处理模型返回的序号 @@ -806,7 +804,7 @@ def combine_find_invalid(invalid_docpath, output_dir): 要求与指南: 文本中可能存在无关的信息,请准确筛选符合条件的信息,并将符合条件的信息的序号返回。 输出格式: - 以 [x, x, x] 的形式返回,x 为符合条件的信息的序号。 + 以 [x, x, x] 的形式返回,x 为符合条件的信息的序号,为自然数。 如果文本中没有符合条件的信息,请返回 []。 特殊情况: 如果某序号的内容明显分为几部分且一部分内容符合筛选条件,但其他部分明显是无关内容,请返回符合部分的字符串内容代替序号。 @@ -825,7 +823,7 @@ def combine_find_invalid(invalid_docpath, output_dir): 要求与指南: 文本中可能存在无关的信息,请准确筛选符合条件的信息,并将符合条件的信息的序号返回。 输出格式: - 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的信息的序号。 + 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的信息的序号,为自然数。 如果文本中没有任何符合条件的废标情况,请返回 []。 示例输出,仅供格式参考: [1,3,4,6] @@ -835,16 +833,21 @@ def combine_find_invalid(invalid_docpath, output_dir): "废标项" ), ( - r'不\s*得(?!\s*分)|禁\s*止\s*投\s*标', - """以下是从招标文件中摘取的内容,文本中序号分明,文本内的条款以'...............'分割。每条条款规定了各方不得存在的情形。请根据以下要求进行筛选: + r'不\s*得(?!\s*(分|力))|禁\s*止\s*投\s*标', + """以下是从招标文件中摘取的内容,文本中序号分明,文本内的条款以'...............'分割。条款规定了各方不得存在的情形。请根据以下要求进行筛选: +**投标相关主体与非投标相关主体的定义**: + 投标相关主体:包括但不限于“投标人”、“中标人”、“供应商”、“联合体投标各方”、“响应人”、“应答人”或其他描述投标方的词语。 + 非投标相关主体:包括但不限于“招标人”、“采购人”、“评标委员会”或其他描述非投标方的词语。 **筛选要求**: -1. **仅包含**明确描述投标主体禁止情形的条款,不包含笼统或未具体说明情形的条款。若条款内容诸如'投标人不得存在的其他关联情形'这样笼统的内容,而未说明具体的情形,则无需添加这条条款。 -2. **仅筛选**出主语为“投标人”、“中标人”、“供应商”、“联合体投标各方”或其他投标相关主体的条款,**或**描述的情形针对投标相关主体的条款。 -3. **排除**主语为“招标人”、“采购人”、“评标委员会”或其他非投标相关主体的条款。 -4. **特殊情况**:如果条款中包含“磋商小组”,且在语境中“磋商小组”指代或包含“投标方”,则应将其考虑在内;否则,排除该条款。 +1. **仅筛选**明确描述投标相关主体禁止情形或不得存在的情形的条款,不包含笼统或未具体说明情形的条款。例如: + 若条款内容包含'投标人不得存在的其他关联情形'这样的笼统描述,而未说明具体的情形,则无需添加该条款。 +2. **排除**仅描述非投标相关主体行为限制或禁止情形的条款,例如“招标人不得泄露信息”或“评标委员会不得收受贿赂”,则无需返回。 +3. 若条款同时描述了对投标相关主体与非投标相关主体的行为限制、禁止情形,也需返回。 +4. **特殊情况**:如果条款中包含“磋商小组”、”各方“等既能指代投标相关主体又能指代非投标相关主体的词汇: + 若在语境中其指代或包含投标相关主体,则应将其考虑在内;否则,排除该条款。 **输出格式**: - 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的条款的序号。 + 返回结果以 [x, x, x] 的形式,其中 x 为符合条件的条款的序号,为自然数。 如果没有符合条件的条款,返回 `[]`。 **示例**: - **符合条件**: @@ -903,10 +906,10 @@ if __name__ == '__main__': # doc_path = r'C:\Users\Administrator\Desktop\new招标文件\tmp\2024-贵州-贵州省罗甸县 2024 年度广州市协作资金龙坪镇、边阳镇产业路硬化建设项目.docx' pdf_path = r'C:\Users\Administrator\Desktop\货物\test\磋商采购文件-恩施市森林火灾风险普查样品检测服务_invalid.pdf' - output_dir = r"C:\Users\Administrator\Desktop\货物\test" + output_dir = r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\tmp" # invalid_added = insert_mark(pdf_path) # invalid_added_docx = pdf2docx(invalid_added) - invalid_added_docx=r'C:\Users\Administrator\Desktop\货物\test\invalid_added.docx' + invalid_added_docx=r'D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\invalid_added.docx' results = combine_find_invalid(invalid_added_docx, output_dir) end_time = time.time() print("Results:", json.dumps(results, ensure_ascii=False, indent=4)) diff --git a/flask_app/old_version/download.py b/flask_app/old_version/download_old.py similarity index 100% rename from flask_app/old_version/download.py rename to flask_app/old_version/download_old.py diff --git a/flask_app/old_version/商务评分技术评分整合.py b/flask_app/old_version/商务评分技术评分整合old_version.py similarity index 98% rename from flask_app/old_version/商务评分技术评分整合.py rename to flask_app/old_version/商务评分技术评分整合old_version.py index 2f81642..3758959 100644 --- a/flask_app/old_version/商务评分技术评分整合.py +++ b/flask_app/old_version/商务评分技术评分整合old_version.py @@ -86,8 +86,7 @@ def combine_evaluation_standards(evaluation_method): evaluation_res = qianwen_long(file_id, user_query_2) # print(evaluation_res) - target_values1 = ['技术标','技术部分','设计', '实施',"技术评分"] - update_json = combine_technical_and_business(clean_json_string(evaluation_res), target_values1) + update_json = combine_technical_and_business(clean_json_string(evaluation_res)) return update_json #商务标技术标整合 if __name__ == "__main__": # evaluation_method="C:\\Users\\Administrator\\Desktop\\招标文件\\output2\\zbtest3_evaluation_method.pdf" diff --git a/flask_app/old_version/工程标商务技术评分.py b/flask_app/old_version/工程标商务技术评分_old.py similarity index 100% rename from flask_app/old_version/工程标商务技术评分.py rename to flask_app/old_version/工程标商务技术评分_old.py diff --git a/flask_app/old_version/废弃的投标人须知正文提取(结构化JSON版).py b/flask_app/old_version/废弃的投标人须知正文提取(结构化JSON版_old.py similarity index 100% rename from flask_app/old_version/废弃的投标人须知正文提取(结构化JSON版).py rename to flask_app/old_version/废弃的投标人须知正文提取(结构化JSON版_old.py diff --git a/flask_app/old_version/招标文件解析_old.py b/flask_app/old_version/招标文件解析_old.py index 2ef6fd8..0d4d1ae 100644 --- a/flask_app/old_version/招标文件解析_old.py +++ b/flask_app/old_version/招标文件解析_old.py @@ -13,7 +13,7 @@ from flask_app.工程标.投标人须知正文提取指定内容工程标 import import concurrent.futures from flask_app.old_version.基础信息整合_old import combine_basic_info from flask_app.old_version.资格审查模块old_old import combine_review_standards -from flask_app.old_version.商务评分技术评分整合 import combine_evaluation_standards +from flask_app.old_version.商务评分技术评分整合old_version import combine_evaluation_standards from flask_app.general.format_change import pdf2docx, docx2pdf from flask_app.general.docx截取docx import copy_docx diff --git a/flask_app/old_version/解析old_old.py b/flask_app/old_version/解析old_old.py index e34424a..0256b5a 100644 --- a/flask_app/old_version/解析old_old.py +++ b/flask_app/old_version/解析old_old.py @@ -12,7 +12,7 @@ from flask_app.工程标.投标人须知正文提取指定内容工程标 import import concurrent.futures from flask_app.工程标.基础信息整合工程标 import combine_basic_info from flask_app.工程标.资格审查模块 import combine_review_standards -from flask_app.old_version.商务评分技术评分整合 import combine_evaluation_standards +from flask_app.old_version.商务评分技术评分整合old_version import combine_evaluation_standards from flask_app.general.format_change import pdf2docx, docx2pdf,doc2docx from flask_app.general.docx截取docx import copy_docx diff --git a/flask_app/old_version/评分标准提取main.py b/flask_app/old_version/评分标准提取main_old.py similarity index 95% rename from flask_app/old_version/评分标准提取main.py rename to flask_app/old_version/评分标准提取main_old.py index 1fcdd34..1c0eabe 100644 --- a/flask_app/old_version/评分标准提取main.py +++ b/flask_app/old_version/评分标准提取main_old.py @@ -1,10 +1,8 @@ # -*- encoding:utf-8 -*- import json -import re import time -from collections import defaultdict - -from flask_app.general.商务技术评分提取 import combine_technical_and_business, parse_json_with_duplicates, \ +from flask_app.general.json_utils import clean_json_string +from flask_app.general.商务技术评分提取 import combine_technical_and_business, \ process_data_based_on_key, reorganize_data from flask_app.general.通义千问long import upload_file, qianwen_long @@ -98,10 +96,9 @@ def combine_evaluation_standards(truncate_file): evaluation_res = qianwen_long(file_id, user_query) #有些重复的键名,只有qianwen_long_text能保留 # print(evaluation_res) # 清理和处理响应 - cleaned_evaluation_res = parse_json_with_duplicates(evaluation_res) #处理重复键名的情况 + cleaned_evaluation_res = clean_json_string(evaluation_res) #处理重复键名的情况 result_data = process_data_based_on_key(cleaned_evaluation_res) #处理不知名外键的情况 include = ['一包', '二包', '三包', '四包', '五包'] - target_values = ['技术', '设计', '实施'] updated_jsons = {} # 检查是否有外层键匹配 include 列表 @@ -110,10 +107,10 @@ def combine_evaluation_standards(truncate_file): for key in result_data: if any(item in key for item in include): inner_dict = result_data[key] - updated_jsons[key] = combine_technical_and_business(inner_dict, target_values) #对于分包,单独对分包内的'技术评分''商务评分'作处理 + updated_jsons[key] = combine_technical_and_business(inner_dict) #对于分包,单独对分包内的'技术评分''商务评分'作处理 else: # 没有匹配的项,对整个字典运行 - updated_jsons = combine_technical_and_business(result_data, target_values) + updated_jsons = combine_technical_and_business(result_data) final_res=reorganize_data(updated_jsons,include) #重新组织字典,尤其是分包的情况 return final_res else: diff --git a/flask_app/routes/偏离表main.py b/flask_app/routes/偏离表main.py index 1c28196..b31d6d1 100644 --- a/flask_app/routes/偏离表main.py +++ b/flask_app/routes/偏离表main.py @@ -275,7 +275,7 @@ def extract_business_deviation(busi_requirements_dict): print("商务要求已更新!") renamed_requirements = rename_outer_keys(updated_requirements) business_requirements_string = json.dumps(renamed_requirements, ensure_ascii=False, indent=4) - print(business_requirements_string) + # print(business_requirements_string) prompt_template1 = """以下文本是项目采购需求的商务要求部分,请帮我将信息重新组织,键名为'商务要求',键值为字符串列表,其中每个字符串为一条商务要求,保留三角▲、五角星★(若有),但是去除开头的序号(若有)。 **角色** 你是一个专业的招投标业务专家,擅长从招标文件中总结商务要求的部分,并逐条列出,作为编写商务要求偏离表的前置准备。 @@ -283,7 +283,7 @@ def extract_business_deviation(busi_requirements_dict): **要求与指南**: 1. 每条内容需要有实际的含义、要求,不能光有标题性质的表述如'售后服务期限(质保期)及要求'。 2. 你的回答内容需从所给文本中整理,尽量不改变原文的表达(除非以下要求与指南3.的特殊情况),请勿擅自添加三角▲、五角星★。 - 3. 若输入文本中存在嵌套键值对格式,且键值本身语义完整且符合'商务要求',可直接将其添加至'商务要求'的键值中;若键值字符串本身语义表达不完整,可将键值对用冒号':'拼接之后作为一条商务要求,若键值字符串以★、▲或其他特殊符号开头,将符号移至拼接后的开头,例如'"★交货地点:采购人指定地点"'。 + 3. 若输入文本中存在嵌套键值对格式,且键值本身语义完整且符合'商务要求',可直接将其添加至'商务要求'的键值中;若键值字符串本身语义表达不完整、不明确,可将键值对用冒号':'拼接之后作为一条商务要求,若键值字符串以★、▲或其他特殊符号开头,将符号移至拼接后的开头,例如'"★交货地点:采购人指定地点"'。 4. 对于以三角▲或五角星★或其他特殊符号开头的字符串: a. 如果该字符串仅为标题性质的表述且不具备实际商务要求的含义,请根据语义关联性将其开头的三角▲或五角星★添加到紧随其后的若干(可为一)内容之后,形成完整的商务要求,并确保整个内容连贯。 注:默认在该字符串后面的一个字符串开头添加三角▲或五角星★,若有明确的序号或者语义表示了其后若干字符串之间的相关性,那么可在这些字符串开头都添加三角▲或五角星★,作为若干商务要求。 diff --git a/flask_app/工程标/截取pdf工程标版.py b/flask_app/工程标/截取pdf工程标版.py index 3b5f859..fe4a52f 100644 --- a/flask_app/工程标/截取pdf工程标版.py +++ b/flask_app/工程标/截取pdf工程标版.py @@ -360,9 +360,9 @@ def truncate_pdf_main_engineering(input_path, output_folder, selection, logger, is_secondary_match = (idx == 2) begin_page = last_begin_index if last_begin_index != 0 else { 1: 0, # 公告 - 2: 10, # 评标 + 2: 5, # 评标 3: 5, # 资格 - 4: 3, # 前附表 + 4: 1, # 前附表 5: 0 # 无效标 }.get(selection, 0) @@ -443,12 +443,12 @@ if __name__ == "__main__": logger = get_global_logger("123") start_time = time.time() # input_path = r"C:\Users\Administrator\Desktop\new招标文件\工程标" - pdf_path = r"C:\Users\Administrator\Desktop\工程\test\2022-广东-鹏华基金管理有限公司深圳深业上城办公室装修项目.pdf" + pdf_path=r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\ztbfile.pdf" # pdf_path = r"C:\Users\Administrator\Desktop\招标文件\招标02.pdf" # input_path=r"C:\Users\Administrator\Desktop\招标文件\招标test文件夹\zbtest8.pdf" - output_folder = r"C:\Users\Administrator\Desktop\fsdownload\305c1fea-e0bd-4135-955f-38fb46388166\tmp" - selection = 5 # 例如:1 - 招标公告, 2 - 评标办法, 3 -资格审查条件 4-投标人须知前附表+正文 5-无效标 + output_folder = r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\tmp" + selection = 4 # 例如:1 - 招标公告, 2 - 评标办法, 3 -资格审查条件 4-投标人须知前附表+正文 5-无效标 generated_files = truncate_pdf_main_engineering(pdf_path, output_folder, selection, logger) print(generated_files) # print("生成的文件:", generated_files) diff --git a/flask_app/货物标/技术参数要求提取.py b/flask_app/货物标/技术参数要求提取.py index 3838c4f..e6f35a6 100644 --- a/flask_app/货物标/技术参数要求提取.py +++ b/flask_app/货物标/技术参数要求提取.py @@ -294,7 +294,7 @@ def generate_prompt(judge_res, full_text=None): base_prompt = ''' 任务:你负责解析采购文件,提取采购需求,并以JSON格式返回,不要遗漏该项目需要采购的货物、设备或系统。 -输出格式: +**输出格式**: 1.JSON格式,外层键名为需要采购的货物、设备或系统名称(一级键)。 2.层次关系用嵌套键值对表示: -采购活动可能将目标划分为多个系统或货物。若文档通过大标题或表格层次或序号标明这种归属关系,请在JSON中以嵌套形式表示: @@ -310,14 +310,17 @@ def generate_prompt(judge_res, full_text=None): -若同一层级下存在名称相同但采购要求(如型号、参数、功能)不同的货物,请在名称后添加编号以作区分,防止出现重复键名;默认情况下,无需在名称后添加编号,只有在名称相同时才需要添加编号。编号规则:'名称-编号',编号从1递增。例子:若同层级下存在两种名称相同但不同型号或参数的'交换机',那么键名分别是'交换机-1'和'交换机-2'。如果名称不同(如路由器和交换机),则无需编号。 4.最内层键值应为空列表[]。 -要求与指南: +**特殊情况**: + -如果文件中未明确说明需要采购的货物、设备或系统,请直接返回空字典 {{}}。不要生成任何无关内容。 + +**要求与指南**: 1. 精准定位:请运用文档理解能力,定位文件中的采购需求部分。 -若有采购清单,请直接根据采购清单上的货物(或系统)名称给出结果,若没有采购清单,则从表格或文本中摘取采购信息。 -注意采购目标通常在诸如'名称'列,且每个目标占据一个单元格,你无需提取诸如'说明'、'规格'、'参数'、'描述'等其他列的内容,即你不需要给出详细的采购要求,更不要将这些单元格内的描述拆分作为其的子键,你仅返回采购的货物或系统或模块名称; 2. 采购目标:采购种类通常有硬件(如设备、货物)和软件(如系统软件、应用APP),一次采购活动可以同时包含这两种类型。 3. 软件类采购:对于软件系统或应用采购,若有多个系统且序号分明,请不要遗漏;若明确列出系统模块,提取模块名称并作为系统的子键,无需在模块下再细分功能。 4. 完整性: - -若采购的货物或系统或模块名称前存在三角▲,△、五角★,☆,注意是名称前而非具体的技术参数或采购要求前,在返回名称时请保留前面的▲,△或★,☆符号,如'★高清摄像机'。 + -若采购的货物或系统或模块名称前存在三角▲,△、五角★,☆或其他特殊符号,注意是名称前而非具体的技术参数或采购要求前,在返回名称时请保留前面的▲,△或★,☆符号,如'★高清摄像机'。 -确保系统内的所有货物设备均被按层次提取,对于有明确清单且按序号划分的采购需求,请勿遗漏每一个序号所代表的货物或设备或系统,也不要添加未提及的内容。 -若某货物(或系统、模块)在“主要设备功能指标”或类似标题下有详细参数说明,但未在前面清单或表格中诸如'名称'的列中列出,也需将该货物名添加到结果中。 @@ -350,6 +353,8 @@ def generate_prompt(judge_res, full_text=None): "XX管理系统":[], //其他系统 }} +示例输出3,无明确的采购需求: +{{}} ''' if '否' not in judge_res and full_text: # 添加文件内容部分 @@ -486,7 +491,7 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1): judge_res = doubao_model(judge_query) if '否' in judge_res or model_type == 2: model_type = 2 # 使用qianwen-long+invalid_path - print("no!调用invalid_path") + print("processed_filepath中无采购需求!调用invalid_path") if invalid_path.lower().endswith('.pdf'): # 确保上传的是docx upload中一定是docx,但是get_deviation中可能上传的是pdf invalid_path = pdf2docx(invalid_path) file_id = upload_file(invalid_path) @@ -499,11 +504,14 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1): print(model_res) cleaned_res = clean_json_string(model_res) #转字典 + if not cleaned_res: + print("本项目没有采购需求!!!") + return {"采购需求": {}} preprocessed_data=preprocess_data(cleaned_res) #确保最内层为[] processed_data=truncate_system_keys(preprocessed_data) #限制深度 key_paths, grouped_paths, good_list, data_copy= generate_key_paths(processed_data) # 提取需要采购的货物清单 key_list:交通监控视频子系统.高清视频抓拍像机 ... grouped_paths是同一系统下同时有'交换机-1'和'交换机-2',提取'交换机' ,输出eg:{'交通标志.标志牌铝板', '交通信号灯.交换机'} modified_data=rename_keys(data_copy) - user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★和序号,不可擅自增删内容,尤其是不可擅自添加序号。 + user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。 **重要限制**: - **仅提取技术参数或采购要求,不包括任何商务要求**。商务要求通常涉及供应商资格、报价条款、交货时间、质保等内容,是整体的要求;而技术参数或采购要求则具体描述产品的技术规格、功能、性能指标等。 - **商务要求的关键词示例**(仅供参考,不限于此):报价、交货、合同、资质、认证、服务、保修期等。如果内容包含上述关键词,请仔细甄别是否属于商务要求。 @@ -538,7 +546,7 @@ def get_technical_requirements(invalid_path,processed_filepath,model_type=1): {} """ - user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\";键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。 + user_query_template_two="""请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。由于该货物存在 {} 种不同的采购要求或技术参数,请逐一列出,并以 JSON 格式返回结果。请以'货物名-编号'区分多种型号,编号为从 1 开始的自然数,依次递增,即第一个键名为\"{}-1\";键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★或其他特殊符号(若有)和序号(若有),不可擅自增删内容,尤其是不可擅自添加序号。 要求与指南: 1. 你的键值应该全面,不要遗漏。 @@ -660,11 +668,11 @@ if __name__ == "__main__": # invalid_path="D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile.pdf" # file_id = upload_file(truncate_file) truncate_file=r'C:\Users\Administrator\Desktop\new招标文件\output5\广水市公安局音视频监控系统设备采购项目_procurement.pdf' - invalid_path=r"D:\flask_project\flask_app\static\output\output1\000aac0d-4aa4-4bc3-a9f9-76ff82ec2470\invalid_added.docx" + invalid_path=r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\invalid_del.docx" # file_id=upload_file(truncate_file) # processed_filepath = convert_file_to_markdown(truncate_file) - processed_filepath=r"C:\Users\Administrator\Desktop\文件解析问题\文件解析问题\baada43d-24f6-459d-8a81-219d130f20da\extract1.txt" - res=get_technical_requirements(invalid_path,processed_filepath) + processed_filepath=r"D:\flask_project\flask_app\static\output\output1\f91db70d-8d96-44a5-b840-27d2f1ecbe95\extract1.txt" + res=get_technical_requirements(invalid_path,processed_filepath,1) json_string = json.dumps(res, ensure_ascii=False, indent=4) print(json_string) # # input_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output1"