diff --git a/flask_app/testdir/判断截取位置.py b/flask_app/general/判断截取位置.py similarity index 90% rename from flask_app/testdir/判断截取位置.py rename to flask_app/general/判断截取位置.py index e2432c6..b158562 100644 --- a/flask_app/testdir/判断截取位置.py +++ b/flask_app/general/判断截取位置.py @@ -5,7 +5,7 @@ from flask_app.general.多线程提问 import multi_threading,read_questions_fro from flask_app.general.通义千问long import upload_file,qianwen_long # 从json文件中读取数据 -with open('test.json', 'r', encoding='utf-8') as f: +with open('../testdir/test.json', 'r', encoding='utf-8') as f: data_dict = json.load(f) # 定义目标名称列表 @@ -27,7 +27,7 @@ target_names = [ # 定义user_query模板 def generate_user_query(target, chapters, keywords): #章节名格式通常是如'三、联合体协议书'这样的序号+标题。现在我需要将{target}贴在该章节的最后面,但是在下一章之前,目前我需要定位到插入的位置, - template = f"""这是投标文件格式要求的部分,作为投标方,我需要把不同的资格证明材料填充到指定区域,请你根据该文件回答:{target}应该附在该文件哪个地方?以下是可能匹配的章节名称:{', '.join([f"'{chapter}'" for chapter in chapters])};或者可能匹配的关键字:{', '.join([f"'{kw}'" for kw in keywords])},你需要根据以上规则确定{target}需要插入的位置,请你返回给我插入位置的上下文,以便于我定位原文,上文是插入章节或小节末尾的内容,下文应该是下一章节或小节开头的内容,上下文合在一起应该是连续的,字数都限制在20字以内。你的回答以json格式返回,键名分别是'上文','下文',上下文内容应完全与原文保持一致,不得擅自删减总结,输出格式示例如下: + template = f"""这是投标文件格式要求的部分,作为投标方,我需要把不同的资格证明材料填充到指定区域,请你根据该文件回答:{target}应该附在该文件哪个地方?以下是可能匹配的章节名称:{', '.join([f"'{chapter}'" for chapter in chapters])};或者可能匹配的关键字:{', '.join([f"'{kw}'" for kw in keywords])},你需要根据以上规则确定{target}需要插入的位置,请你返回给我插入位置的上下文,以便于我定位原文,上文是插入章节或小节末尾的内容,下文应该是下一章节或下一小节开头的内容,上下文合在一起应该是连续的,字数都限制在20字以内。你的回答以json格式返回,键名分别是'上文','下文',上下文内容应完全与原文保持一致,不得擅自删减总结,输出格式示例如下: {{ "上文":"上文测试投标人: (盖单位章) 年 月 日", diff --git a/flask_app/main/商务标技术标整合.py b/flask_app/main/商务评分技术评分整合.py similarity index 100% rename from flask_app/main/商务标技术标整合.py rename to flask_app/main/商务评分技术评分整合.py diff --git a/flask_app/main/工程标解析main.py b/flask_app/main/工程标解析main.py index 5912a0f..d2b64db 100644 --- a/flask_app/main/工程标解析main.py +++ b/flask_app/main/工程标解析main.py @@ -12,7 +12,7 @@ from flask_app.main.投标人须知正文提取指定内容 import extract_from_ import concurrent.futures from flask_app.main.基础信息整合快速版 import combine_basic_info from flask_app.main.资格审查模块 import combine_review_standards -from flask_app.main.商务标技术标整合 import combine_evaluation_standards +from flask_app.main.商务评分技术评分整合 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/招标文件解析.py b/flask_app/old_version/招标文件解析.py index 6ea9a11..5ae1c2b 100644 --- a/flask_app/old_version/招标文件解析.py +++ b/flask_app/old_version/招标文件解析.py @@ -13,7 +13,7 @@ from flask_app.main.投标人须知正文提取指定内容 import extract_from_ import concurrent.futures from flask_app.old_version.基础信息整合 import combine_basic_info from flask_app.old_version.资格审查模块old import combine_review_standards -from flask_app.main.商务标技术标整合 import combine_evaluation_standards +from flask_app.main.商务评分技术评分整合 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/testdir/test3.py b/flask_app/testdir/test3.py index 4d29483..abfedcb 100644 --- a/flask_app/testdir/test3.py +++ b/flask_app/testdir/test3.py @@ -1,84 +1,228 @@ -def extract_matching_keys(data_dict, good_list): +import json +import re +from collections import defaultdict + + +def process_data_based_on_key(data): + exclude_word = ["包", "未知", "评分因素"] + # 获取字典的键列表 + keys = list(data.keys()) + # 检查键的数量是否为1并且 exclude_word 中的任何词包含在 keys[0] 中 + if len(keys) == 1 and any(word in keys[0] for word in exclude_word): + # 返回内层的字典 + return data[keys[0]] + + # 如果条件不满足,则返回原始字典 + return data + +def parse_json_with_duplicates(raw_string): """ - 递归遍历data_dict,查找good_list中存在的键,并将匹配的键及其值添加到结果字典中。 + 解析具有重复键的 JSON 字符串,将所有重复的键值对存储为列表。 - 参数: - - data_dict (dict): 要遍历的嵌套字典。 - - good_list (list): 包含要查找的键的列表。 + Args: + json_string (str): 需要解析的 JSON 字符串。 - 返回: - - dict: 包含所有匹配键及其值的字典。 + Returns: + dict: 解析后的字典,重复的键对应的值为列表。 + + eg:输入:"综合实力": { + "评分": "2分", + "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" + }, + "综合实力": { + "评分": "2分", + "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" + } + 输出:"综合实力": [ + { + "评分": "2分", + "要求": "投标人具备电子与智能化工程专业承包二级资质及以上证书得 2分,不能够提供不得分(开标时需提供原件)。" + }, + { + "评分": "2分", + "要求": "投标人具有建筑机电安装工程专业承包三级资质或以上资质得 2分,否则不得分。(证书开标原件备查)。" + }] """ - result = {} - def recurse(current_dict): - if isinstance(current_dict, dict): - for key, value in current_dict.items(): - if key in good_list: - result[key] = value - # 递归遍历子字典 - recurse(value) - elif isinstance(current_dict, list): - for item in current_dict: - recurse(item) - # 如果current_dict不是dict或list,则无需进一步处理 + 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()} - recurse(data_dict) - return result + def process_dict(d): + """ + 递归处理字典,确保所有重复键的值为列表。 -# 示例数据 -data_dict = { - "门禁管理系统": { - "门禁控制器": { - "系统架构": "1.系统采用 CS架构,支持 Web端登录,支持远程软硬件重启升级、维护、管理。", - "联网方式": "2.★支持 TCP/IP、4G、RS485联网方式、有线与无线(WIFI)可自动切换 ,控制器支持百台到上千台设备组网,需满足大楼多门点的管理数量要求。", - "安装方式": "3.★安装方式支持壁挂安装。", - "开门方式": "4.★支持卡、密码、首卡、双卡、主卡/副卡、胁迫码、多卡分组、指纹识别+中心确认等开门方式。全面支持读卡器、指纹、人脸、指静脉、RS485识读设备的混合接入。" - }, - "出门按钮": { - "安装方式": "标准 86底盒安装方式,支持常开,常闭方式", - "结构": "塑料面板", - "性能": "最大耐电流 1.25A电压 250V", - "服务要求": "产品 需全 国联 保,享 受三 包服 务,保 修期: 3年。" - }, - "单门电磁锁": { - "1)通电上锁,断电开锁,开门时间可调节。": "1)通电上锁,断电开锁,开门时间可调节。", - "2)稳定性强,连续工作锁体无故障,使用更安全。": "2)稳定性强,连续工作锁体无故障,使用更安全。" - }, - "指纹仪": { - "传感器类型": "1.先进半导体传感器,全面领先于普通光学识别技术,更好的判别假指纹、湿指、污损破皮手指。", - "显示及提示": "2.直观的 LCD液晶显示及语音验证提示,配针孔摄像机,实现视频联动抓拍,手动布撤防,实现与监控中心门禁系统实现对讲(外置语音盒)。", - "验证方式": "3.支持 IC卡、指纹、卡加密码、编号多种组合验证方式。" - }, - "门禁服务器": { - "CPU": "1颗 intel至强系列处理器,核数≥12核,主频≥2.2GHz", - "内存": "32G*2 DDR4,16根内存插槽,最大支持扩展至 2TB内存", - "硬盘": "4块 600G 10K 2.5寸 SAS硬盘" + Args: + d (dict): 需要处理的字典。 + + Returns: + dict: 处理后的字典。 + """ + return custom_object_pairs_hook(d.items()) + + def process_list(l): + """ + 递归处理列表,确保列表中的所有字典也被处理。 + + Args: + l (list): 需要处理的列表。 + + Returns: + list: 处理后的列表。 + """ + return [process_dict(item) if isinstance(item, dict) else item for item in l] + + """输入字符串,提取 { 和 } 之间的内容,并将其解析为字典""" + if not raw_string.strip(): + return {} + match = re.search(r'\{[\s\S]*\}', raw_string) + if match: + try: + json_string = match.group(0) + return json.loads(json_string, object_pairs_hook=custom_object_pairs_hook) + except json.JSONDecodeError as e: + print(f"json_utils: extract_content_from_json: JSON decode error: {e}") + return {} + else: + print("json_utils: extract_content_from_json: No valid JSON content found.") + return {} + +def combine_technical_and_business(data, target_values): + extracted_data = {} # 根级别存储所有数据 + technical_found = False + business_found = False + + def extract_nested(data, parent_key='', is_technical=False, is_business=False): + nonlocal technical_found, business_found + if isinstance(data, dict): + for key, value in data.items(): + current_key = f"{parent_key}.{key}" if parent_key else key + + # 检查是否为技术标的内容 + if any(target in key for target in target_values): + if not is_technical: + extracted_data[key] = value + technical_found = True + continue + + # 默认其他所有内容都归为商务标 + else: + if not is_business: + if '商务评分' not in extracted_data: + extracted_data['商务评分'] = {} + extracted_data['商务评分'][key] = value + business_found = True + continue + + if isinstance(value, dict) or isinstance(value, list): + extract_nested(value, current_key, is_technical, is_business) + + elif isinstance(data, list): + for index, item in enumerate(data): + extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) + + extract_nested(data) + + if not technical_found: + extracted_data['技术评分'] = '' + if not business_found: + extracted_data['商务评分'] = '' + + return extracted_data +raw_data=""" +{ + "一包": { + "技术评分": { + "主要监理岗位的职责": { + "评分": "4分", + "要求": "1、总监理工程师的职责全面、清晰、合理得 1.2-2分;一般的1.2分。2、其他主要监理人员及岗位的职责全面、清晰、合理得 1.2-2分;一般的 1.2分。" + }, + "备注": "注:若不满足“与公安部、省公安厅、随州市公安局高清视频会议系统无缝对接互联互通”的要求,则本项技术部分(50分)不得分。" + }, + "商务评分": { + "控制系统内主板": { + "评分": "10分", + "要求": "所投电梯控制系统内主板为制造商原厂原品牌制造生产且为进口部件得 10分。(提供进口部件报关单及原产地证明扫描件加盖公章,否则不得分)" + }, + "制造商技术实力": [ + { + "评分": "3分", + "要求": "一级证书得3分,二级证书得1分,其他不得分" + }, + { + "评分": "2分", + "要求": "行业销量排名连续前 2 名,得 2 分,第 4-6 名得 0.5 分,其他不得分" + } + ] + }, + "投标报价评审": { + "投标报价是否出现违反计价规范": { + "评分": "合格/不合格", + "要求": "A:投标报价未违反计价规范的,评审意见为“合格”;B:投标报价违反计价规范的,评审意见为“不合格”" + } + } } - }, - "摄像系统": { - "佳能摄像机": { - "清晰度": "1k" - }, - "尼康录影机": { - "画质": "2k" - } - } } +""" +def reorganize_data(input_dict, include=None): + """ + 重组输入字典,将“技术评分”和“商务评分”提升为最外层键, + 并将包含在 include 列表中的包名的数据嵌套在相应的评分类别下。 -good_list = [ - "门禁控制器", - "出门按钮", - "单门电磁锁", - "指纹仪", - "门禁服务器", - "佳能摄像机", - "尼康录影机" -] + 如果 input_dict 的顶层键不包含任何 include 列表中的项,则返回原始字典。 -# 使用函数提取匹配的键 -filtered_dict = extract_matching_keys(data_dict, good_list) + :param input_dict: 原始输入字典 + :param include: 包名列表,例如 ['一包', '二包', '三包'] + :return: 重组后的字典 + """ + if include is None: + include = [] -# 打印结果 -import pprint -pprint.pprint(filtered_dict) + # 检查是否有任何顶层键包含在 include 列表中 + has_include = any(key in include for key in input_dict.keys()) + + if not has_include: + # 没有包含任何指定的包名,直接返回原始字典 + return input_dict + + # 初始化新的字典结构 + reorganized = { + "技术评分": {}, + "商务评分": {} + } + + # 遍历每一个包(如 "一包", "二包") + for package, categories in input_dict.items(): + # 处理技术评分 + if "技术评分" in categories: + reorganized["技术评分"][package] = categories["技术评分"] + + # 处理商务评分 + if "商务评分" in categories: + reorganized["商务评分"][package] = categories["商务评分"] + + return reorganized +include = ['一包', '二包', '三包', '四包', '五包'] +target_values = ['技术', '设计', '实施'] +cleaned_evaluation_res = parse_json_with_duplicates(raw_data) #处理重复键名的情况 +result_data = process_data_based_on_key(cleaned_evaluation_res) +updated_jsons = {} +if any(key for key in result_data if any(included in key for included in include)): + # 有匹配的项,处理这些项 + for key in result_data: + if any(item in key for item in include): + inner_dict = result_data[key] + updated_jsons[key] = combine_technical_and_business(inner_dict, target_values) +else: + # 没有匹配的项,对整个字典运行 + updated_jsons = combine_technical_and_business(result_data, target_values) +res=reorganize_data(updated_jsons,include) +print(json.dumps(res, ensure_ascii=False, indent=4)) \ No newline at end of file diff --git a/flask_app/货物标/评分标准提取main.py b/flask_app/货物标/评分标准提取main.py index 4f721c4..0514316 100644 --- a/flask_app/货物标/评分标准提取main.py +++ b/flask_app/货物标/评分标准提取main.py @@ -1,6 +1,7 @@ # -*- encoding:utf-8 -*- import json import re +import time from collections import defaultdict from flask_app.general.通义千问long import upload_file, qianwen_long @@ -143,6 +144,44 @@ def parse_json_with_duplicates(raw_string): print("json_utils: extract_content_from_json: No valid JSON content found.") return {} +def reorganize_data(input_dict, include=None): + """ + 重组输入字典,将“技术评分”和“商务评分”提升为最外层键, + 并将包含在 include 列表中的包名的数据嵌套在相应的评分类别下。 + + 如果 input_dict 的顶层键不包含任何 include 列表中的项,则返回原始字典。 + + :param input_dict: 原始输入字典 + :param include: 包名列表,例如 ['一包', '二包', '三包'] + :return: 重组后的字典 + """ + if include is None: + include = [] + + # 检查是否有任何顶层键包含在 include 列表中 + has_include = any(key in include for key in input_dict.keys()) + + if not has_include: + # 没有包含任何指定的包名,直接返回原始字典 + return input_dict + + # 初始化新的字典结构 + reorganized = { + "技术评分": {}, + "商务评分": {} + } + + # 遍历每一个包(如 "一包", "二包") + for package, categories in input_dict.items(): + # 处理技术评分 + if "技术评分" in categories: + reorganized["技术评分"][package] = categories["技术评分"] + + # 处理商务评分 + if "商务评分" in categories: + reorganized["商务评分"][package] = categories["商务评分"] + + return reorganized def combine_evaluation_standards(truncate_file): # 定义默认的评审结果字典 @@ -218,24 +257,24 @@ def combine_evaluation_standards(truncate_file): evaluation_res = qianwen_long(file_id, user_query) # 清理和处理响应 - cleaned_evaluation_res = parse_json_with_duplicates(evaluation_res) - result_data = process_data_based_on_key(cleaned_evaluation_res) + cleaned_evaluation_res = parse_json_with_duplicates(evaluation_res) #处理重复键名的情况 + result_data = process_data_based_on_key(cleaned_evaluation_res) #处理不知名外键的情况 include = ['一包', '二包', '三包', '四包', '五包'] target_values = ['技术', '设计', '实施'] updated_jsons = {} # 检查是否有外层键匹配 include 列表 - if any(key for key in result_data if any(included in key for included in include)): + if any(key for key in result_data if any(included in key for included in include)): #检查result_data中的任何键是否包含include列表中的任意一个项。 # 有匹配的项,处理这些项 for key in result_data: if any(item in key for item in include): inner_dict = result_data[key] - updated_jsons[key] = combine_technical_and_business(inner_dict, target_values) + updated_jsons[key] = combine_technical_and_business(inner_dict, target_values) #对于分包,单独对分包内的'技术评分''商务评分'作处理 else: # 没有匹配的项,对整个字典运行 updated_jsons = combine_technical_and_business(result_data, target_values) - - return updated_jsons + final_res=reorganize_data(updated_jsons,include) #重新组织字典,尤其是分包的情况 + return final_res else: # 如果 judge 是 False,直接返回默认的技术标和商务标的结构 result_data = {} @@ -250,10 +289,13 @@ def combine_evaluation_standards(truncate_file): if __name__ == "__main__": - # truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\2-招标文件_evaluation_method.pdf" + start_time=time.time() + truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\招标文件(107国道)_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\\fsdownload\\2d481945-1f82-45a5-8e56-7fafea4a7793\\ztbfile_evaluation_method.pdf" - truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\ztbfile_evaluation_method.pdf" + # truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\ztbfile_evaluation_method.pdf" res = combine_evaluation_standards(truncate_file) print(json.dumps(res, ensure_ascii=False, indent=4)) + end_time=time.time() + print("elapsed time:"+str(end_time-start_time))