diff --git a/flask_app/main/商务标技术标整合.py b/flask_app/main/商务标技术标整合.py index c44723f..aa8bdc3 100644 --- a/flask_app/main/商务标技术标整合.py +++ b/flask_app/main/商务标技术标整合.py @@ -3,65 +3,109 @@ import json from flask_app.main.json_utils import clean_json_string from flask_app.main.通义千问long import upload_file, qianwen_long -def combine_technical_and_business(data, target_values1, target_values2): - extracted_data = {} # 根级别存储所有数据 - technical_found = False - business_found = False +# def combine_technical_and_business(data, target_values1, target_values2): +# 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_values1): +# if not is_technical: +# # 直接存储在根级别 +# extracted_data[key] = value +# technical_found = True +# # 标记为技术标内容并停止进一步处理这个分支 +# continue +# +# # 检查是否为商务标的内容 +# elif any(target in key for target in target_values2): +# 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 - 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 +def combine_technical_and_business(data, target_values): + extracted_data = {} # 根级别存储所有数据 + technical_found = False + business_found = False - # 检查是否为技术标的内容 - if any(target in key for target in target_values1): - if not is_technical: - # 直接存储在根级别 - extracted_data[key] = value - technical_found = True - # 标记为技术标内容并停止进一步处理这个分支 - continue + 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 - # 检查是否为商务标的内容 - elif any(target in key for target in target_values2): - if not is_business: - # 存储在'商务标'分类下 - if '商务标' not in extracted_data: - extracted_data['商务标'] = {} - extracted_data['商务标'][key] = value - business_found = True - # 标记为商务标内容并停止进一步处理这个分支 - continue + # 检查是否为技术标的内容 + if any(target in key for target in target_values): + if not is_technical: + extracted_data[key] = value + technical_found = True + continue - # 如果当前值是字典或列表,且不在技术或商务分类下,继续递归搜索 - if isinstance(value, dict) or isinstance(value, list): - extract_nested(value, current_key, is_technical, is_business) + # 默认其他所有内容都归为商务标 + else: + if not is_business: + if '商务标' not in extracted_data: + extracted_data['商务标'] = {} + extracted_data['商务标'][key] = value + business_found = True + continue - elif isinstance(data, list): - for index, item in enumerate(data): - extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) + if isinstance(value, dict) or isinstance(value, list): + extract_nested(value, current_key, is_technical, is_business) - # 开始从顶级递归搜索 - extract_nested(data) + elif isinstance(data, list): + for index, item in enumerate(data): + extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) - # 处理未找到匹配的情况 - if not technical_found: - extracted_data['技术标'] = '' - if not business_found: - extracted_data['商务标'] = '' + extract_nested(data) - return extracted_data + if not technical_found: + extracted_data['技术标'] = '' + if not business_found: + extracted_data['商务标'] = '' + return extracted_data def combine_evaluation_standards(truncate2): # 商务标、技术标评分项:千问 file_id = upload_file(truncate2) user_query_2 = ( "根据该文档中的评标办法前附表,请你列出该文件的技术标,商务标,投标报价评审标准以及它们对应的具体评分要求,若对应内容中存在其他信息,在键名如'技术标'中新增子键名'备注'存放该信息。如果评分内容不是这3个,则返回文档中给定的评分内容以及它的评分要求,都以json的格式返回结果。请不要回答有关形式、资格、响应性评审标准的内容") evaluation_res = qianwen_long(file_id, user_query_2) - target_values1 = ['技术标','技术部分','设计', '实施', '方案'] - target_values2=['投标报价','商务标','商务部分','报价部分','业绩','信誉','分值','计算公式','信用','人员','资格','奖项','认证','荣誉'] - update_json=combine_technical_and_business(clean_json_string(evaluation_res),target_values1,target_values2) + print(evaluation_res) + target_values1 = ['技术标','技术部分','设计', '实施'] + # 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 diff --git a/flask_app/main/按页读取pdf.py b/flask_app/main/按页读取pdf.py deleted file mode 100644 index 6fe5ce0..0000000 --- a/flask_app/main/按页读取pdf.py +++ /dev/null @@ -1,31 +0,0 @@ -import PyPDF2 -import re # 导入正则表达式库 - -def clean_page_numbers(text): - # 使用正则表达式删除页码 - # 假设页码在文本的最开始,最结尾,或中间(如 /129 或 129) - cleaned_text = re.sub(r'^\s*\d+\s+', '', text) # 删除开头的页码 - cleaned_text = re.sub(r'\s+\d+\s*$', '', cleaned_text) # 删除结尾的页码 - cleaned_text = re.sub(r'\s*\/\s*\d+\s*', '', cleaned_text) # 删除形如 /129 的页码 - return cleaned_text - -def extract_text_by_page(file_path): - result = "" - with open(file_path, 'rb') as file: - reader = PyPDF2.PdfReader(file) - num_pages = len(reader.pages) - # print(f"Total pages: {num_pages}") - for page_num in range(num_pages): - page = reader.pages[page_num] - text = page.extract_text() - if text: - cleaned_text = clean_page_numbers(text) - # print(cleaned_text) - result += cleaned_text - # print(f"Page {page_num + 1} Content:\n{cleaned_text}") - else: - print(f"Page {page_num + 1} is empty or text could not be extracted.") - return result -if __name__ == '__main__': - file_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\招标文件正文(1)(1).pdf" - extract_text_by_page(file_path) diff --git a/flask_app/main/读取文件/__init__.py b/flask_app/main/读取文件/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flask_app/main/读取文件/按页读取pdf.py b/flask_app/main/读取文件/按页读取pdf.py new file mode 100644 index 0000000..5a2e16b --- /dev/null +++ b/flask_app/main/读取文件/按页读取pdf.py @@ -0,0 +1,77 @@ +import PyPDF2 +import re # 导入正则表达式库 + +from PyPDF2 import PdfReader + + +def clean_page_content(text, common_header): + # 首先删除抬头公共部分 + if common_header: # 确保有公共抬头才进行替换 + for header_line in common_header.split('\n'): + if header_line.strip(): # 只处理非空行 + # 替换首次出现的完整行 + text = re.sub(r'^' + re.escape(header_line.strip()) + r'\n?', '', text, count=1) + + # 删除页码 eg:89/129 这个代码分三步走可以把89/129完全删除 + text = re.sub(r'^\s*\d+\s*(?=\D)', '', text) # 删除开头的页码,仅当紧跟非数字字符时 + text = re.sub(r'\s+\d+\s*$', '', text) # 删除结尾的页码 + text = re.sub(r'\s*\/\s*\d+\s*', '', text) # 删除形如 /129 的页码 + + return text +def extract_common_header(pdf_path): + pdf_document = PdfReader(pdf_path) + headers = [] + total_pages = len(pdf_document.pages) + + # 确定要读取的页数和起始页 + if total_pages == 2: + pages_to_read = 2 + start_page = 0 + else: + pages_to_read = 3 + middle_page = total_pages // 2 + start_page = max(0, middle_page - 1) + + for i in range(start_page, min(start_page + pages_to_read, total_pages)): + page = pdf_document.pages[i] + text = page.extract_text() or "" + if text: + # 只取每页的前三行 + first_lines = text.strip().split('\n')[:3] + headers.append(first_lines) + + if len(headers) < 2: + return "" # 如果没有足够的页来比较,返回空字符串 + + # 寻找每一行中的公共部分 + common_headers = [] + for lines in zip(*headers): + # 在每一行中寻找公共单词 + common_line = set(lines[0].split()).intersection(*[set(line.split()) for line in lines[1:]]) + if common_line: + common_headers.append(' '.join(common_line)) + + return '\n'.join(common_headers) + +def extract_text_by_page(file_path): + common_header = extract_common_header(file_path) + result = "" + with open(file_path, 'rb') as file: + reader = PyPDF2.PdfReader(file) + num_pages = len(reader.pages) + # print(f"Total pages: {num_pages}") + for page_num in range(num_pages): + page = reader.pages[page_num] + text = page.extract_text() + if text: + cleaned_text = clean_page_content(text,common_header) + print(cleaned_text) + result += cleaned_text + # print(f"Page {page_num + 1} Content:\n{cleaned_text}") + else: + print(f"Page {page_num + 1} is empty or text could not be extracted.") + return result +if __name__ == '__main__': + file_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\ztbfile.pdf" + res=extract_text_by_page(file_path) + # print(res) \ No newline at end of file diff --git a/flask_app/main/读取docx.py b/flask_app/main/读取文件/读取docx.py similarity index 100% rename from flask_app/main/读取docx.py rename to flask_app/main/读取文件/读取docx.py diff --git a/flask_app/main/资格评审前判断.py b/flask_app/main/资格评审前判断.py index 48fc979..391c4c2 100644 --- a/flask_app/main/资格评审前判断.py +++ b/flask_app/main/资格评审前判断.py @@ -1,4 +1,4 @@ -from flask_app.main.按页读取pdf import extract_text_by_page +from flask_app.main.读取文件.按页读取pdf import extract_text_by_page def check_strings_in_pdf(file_path): judge_list=['施工机械设备', '企业信息登记'] diff --git a/flask_app/货物标/test.py b/flask_app/货物标/test.py index a6f7a15..7982ce4 100644 --- a/flask_app/货物标/test.py +++ b/flask_app/货物标/test.py @@ -1,229 +1,31 @@ -# -*- encoding:utf-8 -*- -import json +import re +def get_patterns_for_qualification(): + # begin_pattern 匹配以'资格审查'或'资格性检查'开始的标题,同时允许前面有章节编号和标题 + begin_pattern = re.compile( + r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(资格审查|资格性检查).*', re.MULTILINE) -def combine_technical_and_business(data, target_values1): - extracted_data = {} # 根级别存储所有数据 - technical_found = False - business_found = False + # end_pattern 匹配下一个章节的开始或以'附件'加数字开头的页 + end_pattern = re.compile( + r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+|附件\s*\d+', re.MULTILINE) - 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 + return begin_pattern, end_pattern - # 检查是否为技术标的内容 - if any(target in key for target in target_values1): - if not is_technical: - extracted_data[key] = value - technical_found = True - continue +# 获取编译后的正则表达式 +begin_pattern, end_pattern = get_patterns_for_qualification() - # 默认其他所有内容都归为商务标 - else: - if not is_business: - if '商务标' not in extracted_data: - extracted_data['商务标'] = {} - extracted_data['商务标'][key] = value - business_found = True - continue +# 示例字符串,用于测试正则表达式是否工作 +test_string = """ +第一章 资格审查 +资格性检查 +第二章 实施规范 +附件 4 使用说明 +""" - if isinstance(value, dict) or isinstance(value, list): - extract_nested(value, current_key, is_technical, is_business) +# 测试 begin_pattern +begin_matches = begin_pattern.findall(test_string) +print("Begin Matches:", begin_matches) - 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 - - -# 示例数据 -data = { - "一包评分标准": { - "报价部分": { - "价格评议": { - "分值": 30, - "评分标准": "对确定资格性和符合性审查合格的投标文件进行价格评议,报价分采用低价优先法计算,即满足招标文件要求且投标价格最低的投标报价为评标基准价,其报价分30分。其他投标人的价格分按照下列公式计算:投标报价得分=(评标基准价/投标报价)×30%×100(得分保留小数点后2位)" - } - }, - "商务部分": { - "企业实力": [ - { - "分值": 2, - "评分标准": "投标人具有有效期内的CCRC信息安全集成服务资质贰级以上资质证书,得2分,不具备得0分。(原件备查)" - }, - { - "分值": 2, - "评分标准": "投标人有健全的信息技术运维服务能力,并通过ITSS信息技术服务运维标准符合性认证证书,贰级以上得2分,不具备得0分。(原件备查)" - }, - { - "分值": 2, - "评分标准": "投标人具有ISO 14001环境管理体系认证以及OHSAS 18001职业健康安全管理体系认证,具有ISO 20000 IT服务管理体系认证以及ISO 27001信息安全管理体 系认证,每满足一项得2分,否则不得分。(原件备查)" - }, - { - "分值": 3, - "评分标准": "投标人同时具有省级及以上高新技术企业证书、AAA企业信用等级证书、上 年度纳税信用评价为A级,完全满足得3分,每缺一项扣1分,直至得0分;(原件备查)" - }, - { - "分值": 2, - "评分标准": "所投主要产品制造商应具备较强的合同执行诚信度和执行能力,要求获得政府权威机构颁发的国家级或省级守合同重信用公示企业,国家级得2分,省级得1分,不提供不得分。(原件备查)" - }, - { - "分值": 2, - "评分标准": "所投主要产品制造商通过CMMI软件能力成熟度模型集成认证,认证等级不低于5级得2分,4级得1分,其他不得分。(原件备查)" - }, - { - "分值": 3, - "评分标准": "投标人近三年以来具有类似项目经验(提供中标通知书及合同原件)。每提供一个得1分,最多得3分。" - }, - { - "分值": 2, - "评分标准": "投标人具备GB/T27922五星级服务认证证书的得2分,不满足得0分。(原件备查)" - } - ], - "产品制造商实力": [ - { - "分值": 2, - "评分标准": "为保证系统的稳定性,所投产品制造商应具有完善的工业信息安全应急体系、良好的安全应急能力,能被工业信息安全产业发展联盟认定为“工业信息安全应急服务支撑单位”,满足得2分,否则不得分。(原件备查)" - }, - { - "分值": 2, - "评分标准": "所投主要产品制造商通过CMMI软件能力成熟度模型集成认证,认证等级不低于5级得2分,4级得1分,其他不得分。(原件备查)" - } - ] - }, - "技术部分": { - "技术要求": { - "分值": 30, - "评分标准": "投标人所投产品完全满足招标文件要求的30分,'★'指标不符合招标文件技术参数的视为无效投标,针对'★'指标,投标人须对应招标文件第三章商务技术要求产品参数要求逐条提供包括但不限于第三方产品检测报告、产品技术白皮书、产品官网或公开的技术资料等佐证资料,并加盖产品制造商公章;'▲'指标不满足招标文件第三章商务技术要求产品参数要求的每项扣2分,其他非指标性参数不满足招标文件第三章商务技术要求产品参数要求的每项扣1分,扣完为止,未提供佐证资料的将作产品不响应处理,'▲'指标产品参数要求必须提供第三方权威机构检测报告佐证并加盖产品制造商公章,非指标参数提供产品彩页佐证并加盖产品制造商公章即可。", - "备注": "针对'★'指标,投标人须对应招标文件第三章商务技术要求产品参数要求逐条提供包括但不限于第三方产品检测报告、产品技术白皮书、产品官网或公开的技术资料等佐证资料,并加盖产品制造商公章;'▲'指标产品参数要求必须提供第三方权威机构检测报告佐证并加盖产品制造商公章,非指标参数提供产品彩页佐证并加盖产品制造商公章即可。" - }, - "质量保证": { - "分值": 2, - "评分标准": "投标人所投的电子警察系统、信号控制系统,如果不是自己生产的,需提供制造商出具的授权及满足招标质保要求的售后服务承诺函,提供得2分;(开标时提供授权书及售后服务承诺函原件予以证明,否则不得分。)" - }, - "实施方案": { - "分值": 5, - "评分标准": "投标人对于本项目的实施保障方案中包含:①施工组织方案、②施工协调经验、③进度计划、④质量保障、⑤工期保障五个方案的可行性、合理性、实用性等方面进行综合性评分,每个实施方案清晰、完整、合理、可行的得1分。" - }, - "实施团队": { - "分值": 9, - "评分标准": "1、投标人拟派项目经理具有机电工程专业壹级注册建造师资格及安全生产考核合格证书(B证),并同时具备相关专业高级工程师职称的,得2分;其他不得分;2、项目技术负责人具备相关专业高级工程师职称及高级信息系统项目管理师证书的,得2分;其他不得分;3、项目管理团队中的施工员、质量员、安全员(C证)、材料员、资料员须具有中级或以上职称;完全满足得5分,每缺一个扣1分,直至得0分;(以上人员不可兼任,提供证书复印件、身份证、劳动合同及社保证明文件加盖公章)" - }, - "运维服务": { - "分值": 2, - "评分标准": "投标人应对维护期给出合理、完善的运维及售后服务方案。包括售后服务团队管理、运维服务内容(日常运作、服务咨询、巡检保养、主动监测、故障修复、特殊保障和升级优化)、备件方案、运维服务流程、运维管理,服务方案清晰、完整、合理、可行2分;服务方案较完整1分;服务方案不完整,不明确得0分。" - }, - "培训方案": { - "分值": 2, - "评分标准": "投标人应针对本项目制定全面、可操作性强的技术培训方案有具体的培训时间、人员安排的2分,其它得0分。" - } - } - }, - "二包评分标准": { - "报价部分": { - "价格评议": { - "分值": 30, - "评分标准": "对确定资格性和符合性审查合格的投标文件进行价格评议,报价分采用低价优先法计算,即满足招标文件要求且投标价格最低的投标报价为评标基准价,其报价分30分。其他投标人的价格分按照下列公式计算:投标报价得分=(评标基准价/投标报价)×30%×100(得分保留小数点后2位)" - } - }, - "商务部分": { - "企业实力": [ - { - "分值": 3, - "评分标准": "投标人具有三级交通运输企业安全生产标准化建设等级证明的得1分;具有二级交通运输企业安全生产标准化建设等级证明的得2分;具有一级交通运输企业安全生产标准化建设等级证明的得3分。(提供相关证明材料加盖公章)" - }, - { - "分值": 3, - "评分标准": "投标人具有ISO 14001环境管理体系认证以及OHSAS 18001职业健康安全管理体系认证,具有ISO 09001质量管理体系认证,全部满足得3分,每满足一项得1分,否则不得分。(原件备查)" - }, - { - "分值": 4, - "评分标准": "投标人具有AAA企业信用等级证书且在有效期内的、上年度纳税信用评价为A级,完全满足得4分,每缺一项扣2分,直至得0分;(提供相关证明材料)" - }, - { - "分值": 8, - "评分标准": "投标人近三年以来具有类似项目经验(提供中标通知书及合同原件)。每提供一个得2分,最多得8分。" - }, - { - "分值": 9, - "评分标准": "1、投标人拟派项目经理具有高级工程师职称的,得2分;其他不得分;2、项目技术负责人具备相关专业高级工程师职称,得2分;其他不得分;3、项目管理团队中的施工员、质量员、安全员(C证)、材料员、资料员须具有中级或以上职称;完全满足得5分,每缺一个扣1分,直至得0分;(以上人员不可兼任,提供证书复印件、身份证、劳动合同及社保证明文件加盖公章)" - } - ] - }, - "技术部分": { - "技术要求": { - "分值": 20, - "评分标准": "投标人所投产品完全满足招标文件要求的20分,'▲'指标低于招标文件第三章商务技术要求产品参数要求的每项扣3分,扣完为止。" - }, - "质量保证措施": { - "分值": 8, - "评分标准": "投标人提供的用于本项目的质量保障措施,符合采购要求,并包含以下几点:(1)质量管理目标;(2)质量管理规章制度;(3)质量管理方案;(4)质量承诺。每一项2分,完全满足得8分,缺一项扣2分,直至得0分。" - }, - "售后服务承诺": { - "分值": 12, - "评分标准": "1、投标人承诺对招标人提供优先供货服务,以满足招标人的紧急需求;(2)投标人承诺接到故障报修,24小时内提供解决方案并开始组织实施;(3)投标人承诺现场施工降低污染,文明施工;每一项5分。完全满足得12分,缺一项扣4分,直至得0分。" - }, - "培训方案": { - "分值": 3, - "评分标准": "投标人应针对本项目制定全面、可操作性强的技术培训方案有具体的培训时间、人员安排的3分,其它得0分。" - } - } - }, - "三包评分标准": { - "报价部分": { - "分值": 40, - "评分标准": "对确定资格性和符合性审查合格的投标文件进行价格评议,报价分采用低价优先法计算,即满足招标文件要求且投标价格最低的投标报价为评标基准价,其报价分40分。其他投标人的价格分按照下列公式计算:投标报价得分=(评标基准价/投标报价)×40%×100(得分保留小数点后2位)" - }, - "技术指标": { - "分值": 40, - "评分标准": "1)供应商的技术投标书的响应性、符合性、完整性及编制质量,在技术参数*号项、普通项全部满足的得满分40分;2)技术参数中*号项一项不满足的扣2分,扣完为止;3)技术参数中普通项一项不满足扣1分,扣完为止;4)复制招标文件技术要求作为实际响应数据或投标响应数据无对应支持文件的,其技术响应将按负偏离处理。" - }, - "服务保障": { - "分值": 10, - "评分标准": "投标人应对维护期给出合理、完善的运维及售后服务方案。包括售后服务团队管理、运维服务内容(日常运作、服务咨询、巡检保养、主动监测、故障修复、特殊保障和升级优化)、备件方案、运维服务流程、运维管理,服务方案清晰、完整、合理、可行2分;服务方案较完整1分;服务方案不完整,不明确得0分。" - }, - "业绩证明": { - "分值": 5, - "评分标准": "投标人近三年以来具有类似项目经验(提供中标通知书及合同原件)。每提供一个得2.5分,最多得5分。" - }, - "投标文件的规范性": { - "分值": 2, - "评分标准": "投标文件的制作质量、文档编排、规范性、方便查阅性等进行综合比较:优得2分,良得1分。" - }, - "售后服务": { - "分值": 3, - "评分标准": "产品故障响应:省内24小时、省外48小时内到达现场处理并修复,且有完善售后服务网络和巡检方案的得3分,不完善售后服务酌情扣分。" - } - } -} - -# 转换字典 -def get_evaluation_standards(truncate_file): - - include = ['一包', '二包', '三包', '四包', '五包'] - target_values1 = ['技术', '设计', '实施', '方案'] - - updated_jsons = {} - - for key in data.keys(): - if any(item in key for item in include): - inner_dict = data[key] - # 将处理后的结果存储到updated_jsons中,每个包名为键 - updated_jsons[key] = combine_technical_and_business(inner_dict, target_values1) - - # 将updated_jsons转换为JSON格式 - evaluation_combined_res = json.dumps(updated_jsons, ensure_ascii=False, indent=4) - return evaluation_combined_res - -res=get_evaluation_standards("1") -print(res) +# 测试 end_pattern +end_matches = end_pattern.findall(test_string) +print("End Matches:", end_matches) diff --git a/flask_app/货物标/评分标准提取.py b/flask_app/货物标/评分标准提取.py index 275f03f..71a46dc 100644 --- a/flask_app/货物标/评分标准提取.py +++ b/flask_app/货物标/评分标准提取.py @@ -1,131 +1,89 @@ +# -*- encoding:utf-8 -*- import json from flask_app.main.通义千问long import upload_file, qianwen_long from flask_app.main.json_utils import clean_json_string +def combine_technical_and_business(data, target_values): + extracted_data = {} # 根级别存储所有数据 + technical_found = False + business_found = False -def combine_technical_and_business(data, target_values1, target_values2): - 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 - 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 - # 检查是否为技术标的内容 - if any(target in key for target in target_values1): #模糊匹配 - 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 - # 检查是否为商务标的内容 - elif any(target in key for target in target_values2): - 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) - # 如果当前值是字典或列表,且不在技术或商务分类下,继续递归搜索 - 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) - elif isinstance(data, list): - for index, item in enumerate(data): - extract_nested(item, f"{parent_key}[{index}]", is_technical, is_business) + extract_nested(data) - # 开始从顶级递归搜索 - extract_nested(data) + if not technical_found: + extracted_data['技术标'] = '' + if not business_found: + extracted_data['商务标'] = '' - # 处理未找到匹配的情况 - if not technical_found: - extracted_data['技术标'] = '' - if not business_found: - extracted_data['商务标'] = '' - - return extracted_data + return extracted_data +#如果外键直接是'评分因素',应该这个函数可以提取其中内容。 +def process_data_based_on_key(data, word): + # 获取字典的键列表 + keys = list(data.keys()) + # 检查键的数量是否为1并且该键是否包含指定的词 + if len(keys) == 1 and word in keys[0]: + # 返回内层的字典 + return data[keys[0]] + # 如果条件不满足,则返回原始字典 + return data def get_evaluation_standards(truncate_file): file_id = upload_file(truncate_file) - user_query = "根据该文档中的评标办法前附表,请你列出该文件的技术标,商务标,投标报价评审标准以及它们对应的具体评分要求,若对应内容中存在其他信息,在键名如'技术标'中新增子键名'备注'存放该信息。如果评分内容不是这3个,则返回文档中给定的评分内容以及它的评分要求,都以json的格式返回结果。请不要回答有关形式、资格、响应性评审标准的内容" + user_query = "根据该文档中的评标办法前附表或者评分标准表,请你列出该文件的技术标,商务标,投标报价评审标准以及它们对应的具体评分要求,外层键名分别为'技术标','商务标','投标报价'。如果评分内容不是这3个,则返回文档中给定的评分内容以及它的评分要求,都以json的格式返回结果,如果该采购活动有多个包,则最外层键名为对应的包名。请不要回答有关资格审查的内容" evaluation_res = qianwen_long(file_id, user_query) cleaned_evaluation_res = clean_json_string(evaluation_res) - + result_data = process_data_based_on_key(cleaned_evaluation_res, '评分') include = ['一包', '二包', '三包', '四包', '五包'] - target_values1 = ['技术标', '技术部分', '设计', '实施', '方案'] - target_values2 = ['投标报价', '商务标', '商务部分', '报价部分', '业绩', '信誉', '分值', '计算公式', '信用', '人员', - '资格', '奖项', '认证', '荣誉'] + target_values = ['技术', '设计', '实施'] updated_jsons = {} - for key in cleaned_evaluation_res.keys(): - if any(item in key for item in include): - inner_dict = cleaned_evaluation_res[key] - # 将处理后的结果存储到updated_jsons中,每个包名为键 - updated_jsons[key] = combine_technical_and_business(inner_dict, target_values1, target_values2) + # 检查是否有外层键匹配include列表 + 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) # 将updated_jsons转换为JSON格式 evaluation_combined_res = json.dumps(updated_jsons, ensure_ascii=False, indent=4) return evaluation_combined_res -def combine_technical_and_business(data, target_values1, target_values2): - 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_values1): - if not is_technical: - # 直接存储在根级别 - extracted_data[key] = value - technical_found = True - # 标记为技术标内容并停止进一步处理这个分支 - continue - - # 检查是否为商务标的内容 - elif any(target in key for target in target_values2): - 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 if __name__ == "__main__": - truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\招标文件(107国道)_evaluation_method.pdf" + truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output2\\竞争性谈判文件(3)_evaluation_method.pdf" res=get_evaluation_standards(truncate_file) - cleaned_res=clean_json_string(res).get("") \ No newline at end of file + print(res) diff --git a/flask_app/货物标/货物标截取pdf.py b/flask_app/货物标/货物标截取pdf.py index 5646444..c6ab823 100644 --- a/flask_app/货物标/货物标截取pdf.py +++ b/flask_app/货物标/货物标截取pdf.py @@ -18,6 +18,8 @@ def clean_page_content(text, common_header): text = re.sub(r'\s*\/\s*\d+\s*', '', text) # 删除形如 /129 的页码 return text + + def extract_common_header(pdf_path): pdf_document = PdfReader(pdf_path) headers = [] @@ -53,16 +55,19 @@ def extract_common_header(pdf_path): return '\n'.join(common_headers) + def is_pdf_or_doc(filename): # 判断文件是否为PDF或Word文档 return filename.lower().endswith(('.pdf', '.doc', '.docx')) + def convert_to_pdf(file_path): # 假设 docx2pdf 函数已经被定义,这里仅根据文件扩展名来决定是否需要转换 if file_path.lower().endswith(('.doc', '.docx')): return docx2pdf(file_path) return file_path + 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) @@ -79,13 +84,15 @@ def process_input(input_path, output_folder, begin_pattern, begin_page, end_patt generated_files.append(output_pdf_path) elif os.path.isfile(input_path) and input_path.endswith(".pdf"): # 处理单个PDF文件 - output_pdf_path = extract_pages(input_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix) + output_pdf_path = extract_pages(input_path, output_folder, begin_pattern, begin_page, end_pattern, + output_suffix) if output_pdf_path: generated_files.append(output_pdf_path) else: print("提供的路径既不是文件夹也不是PDF文件。") return generated_files + def extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, common_header, exclusion_pattern=None): start_page = None end_page = None @@ -101,19 +108,22 @@ def extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, break return start_page, end_page + def extract_pages(pdf_path, output_folder, begin_pattern, begin_page, end_pattern, output_suffix): try: common_header = extract_common_header(pdf_path) pdf_document = PdfReader(pdf_path) - start_page, end_page = extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, common_header) + start_page, end_page = extract_pages_generic(pdf_document, begin_pattern, end_pattern, begin_page, + common_header) if start_page is None or end_page is None: - print(f"未找到起始或结束页在文件 {pdf_path} 中!尝试备用提取策略。") + print(f"first: {output_suffix} 未找到起始或结束页在文件 {pdf_path} 中!尝试备用提取策略。") return 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) except Exception as e: print(f"Error processing {pdf_path}: {e}") return None + def get_patterns_for_procurement(): begin_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:服务|项目|商务).*?要求|' @@ -123,6 +133,7 @@ def get_patterns_for_procurement(): r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE) return begin_pattern, end_pattern + def get_patterns_for_evaluation_method(): begin_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(磋商|谈判|评标|评定|评审)(方法|办法).*', re.MULTILINE) @@ -130,17 +141,49 @@ def get_patterns_for_evaluation_method(): r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE) return begin_pattern, end_pattern + +def get_patterns_for_qualification(): + # 原始匹配逻辑 + begin_pattern_original = re.compile( + r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(资格审查).*', re.MULTILINE) + end_pattern_original = re.compile( + r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+', re.MULTILINE) + + # 新匹配逻辑 + begin_pattern_new = re.compile( + r'^资格性检查', re.MULTILINE) + end_pattern_new = re.compile( + r'^附件\s*\d+', re.MULTILINE) + + return (begin_pattern_original, end_pattern_original), (begin_pattern_new, end_pattern_new) + + def extract_pages_twice(pdf_path, output_folder, output_suffix, common_header): - exclusion_pattern = re.compile(r'文件的构成|文件的组成') + exclusion_pattern = re.compile(r'文件的构成|文件的组成|须对应|需对应|须按照|需按照|须根据|需根据') pdf_document = PdfReader(pdf_path) + patterns = None + if output_suffix == "procurement": - begin_pattern, end_pattern = get_patterns_for_procurement() - elif output_suffix == "evaluation_method": - begin_pattern, end_pattern = get_patterns_for_evaluation_method() - start_page, end_page = extract_pages_generic(pdf_document, begin_pattern, end_pattern, 5, common_header, exclusion_pattern) + patterns = [get_patterns_for_procurement()] + elif output_suffix == "evaluation_method" or output_suffix=="qualification2": + patterns = [get_patterns_for_evaluation_method()] + elif output_suffix == "qualification": + patterns = get_patterns_for_qualification() # This now returns a tuple of pattern pairs + + # 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], 5, common_header, + exclusion_pattern) + if start_page is not None and end_page is not None: + break if start_page is None or end_page is None: - print(f"twice: 未找到起始或结束页在文件 {pdf_path} 中!") - return "" + if output_suffix == "qualification": + print(f"second: {output_suffix} 未找到起始或结束页在文件 {pdf_path} 中!") + print("third:尝试提取评分办法章节...") + return truncate_pdf_main(pdf_path,output_folder,2,"qualification2") + else: + print(f"second: {output_suffix} 未找到起始或结束页在文件 {pdf_path} 中!") + return "" return save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_folder, output_suffix) @@ -152,51 +195,65 @@ def save_extracted_pages(pdf_document, start_page, end_page, pdf_path, output_fo output_doc.add_page(pdf_document.pages[page_num]) with open(output_pdf_path, 'wb') as f: output_doc.write(f) - print(f"已截取并保存页面从 {start_page} 到 {end_page} 为 {output_pdf_path}") + print(f"{output_suffix} 已截取并保存页面从 {start_page} 到 {end_page} 为 {output_pdf_path}") return output_pdf_path -def truncate_pdf_main(input_path, output_folder, selection): +def truncate_pdf_main(input_path, output_folder, selection, output_suffix="default"): if selection == 1: # 更新的正则表达式以匹配"第x章"和"第x部分",考虑到可能的空格和其他文字 begin_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(?:服务|项目|商务).*?要求|' - r'^第[一二三四五六七八九十百千]+(?:章|部分).*?采购.*', - # r'^[一二三四五六七八九十百千]+、\s*采购清单', + r'^第[一二三四五六七八九十百千]+(?:章|部分).*?采购.*' ) begin_page = 5 end_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+' ) - output_suffix = "procurement" + local_output_suffix = "procurement" elif selection == 2: - begin_pattern=re.compile( + begin_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(磋商|谈判|评标|评定|评审)(方法|办法).*' ) begin_page = 5 end_pattern = re.compile( r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+' ) - output_suffix = "evaluation_method" + local_output_suffix = "evaluation_method" + elif selection == 3: + begin_pattern = re.compile( + r'^第[一二三四五六七八九十百千]+(?:章|部分).*?(资格审查).*' + ) + begin_page = 5 + end_pattern = re.compile( + r'^第[一二三四五六七八九十百千]+(?:章|部分)\s*[\u4e00-\u9fff]+' + ) + local_output_suffix = "qualification" else: print("无效的选择") return None + # 如果传入的 output_suffix 是 'default',则使用本地生成的 output_suffix + if output_suffix == "default": + output_suffix = local_output_suffix + # 调用相应的处理函数 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, 3): + for selection in range(1, 4): files = truncate_pdf_main(input_path, output_folder, selection) truncate_files.extend(files) return truncate_files -#TODO:交通智能系统和招标(1)(1)文件有问题 + +# TODO:交通智能系统和招标(1)(1)文件有问题 if __name__ == "__main__": input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\交通智能运行维护招标文件.pdf" - output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output2" + output_folder = "C:\\Users\\Administrator\\Desktop\\货物标\\output3" # truncate_pdf_multiple(input_path,output_folder) - selection = 2 # 例如:1 - 投标人须知前附表, 2 - 评标办法, 3 - 投标人须知正文 4-招标公告-合同条款前 + selection = 3 # 例如:1 - 商务技术服务要求, 2 - 评标办法, 3 - 资格审查 generated_files = truncate_pdf_main(input_path, output_folder, selection) -