diff --git a/flask_app/general/merge_pdfs.py b/flask_app/general/merge_pdfs.py index e720690..513b875 100644 --- a/flask_app/general/merge_pdfs.py +++ b/flask_app/general/merge_pdfs.py @@ -3,11 +3,15 @@ import os from PyPDF2 import PdfReader, PdfWriter #合并PDF def merge_pdfs(paths, output_path): + # 检查是否所有路径都是空字符串或仅包含空白字符 + if not any(path.strip() for path in paths): + return "" + pdf_writer = PdfWriter() last_page_text = None # 用于存储上一个PDF的最后一页的文本 for path in paths: - # 跳过空字符串或无效路径 + # 跳过空字符串或仅包含空白字符的路径 if not path.strip(): continue try: @@ -17,25 +21,34 @@ def merge_pdfs(paths, output_path): # 如果这不是第一个文件,并且有上一个文件的最后一页文本 if last_page_text is not None and len(pages) > 0: - current_first_page_text = pages[0].extract_text() if pages[0].extract_text() else "" + current_first_page_text = pages[0].extract_text() or "" # 比较当前文件的第一页和上一个文件的最后一页的文本 if current_first_page_text == last_page_text: start_index = 1 # 如果相同,跳过当前文件的第一页 # 添加当前PDF的页面到写入器 - for page in range(start_index, len(pages)): - pdf_writer.add_page(pages[page]) + for page_num in range(start_index, len(pages)): + pdf_writer.add_page(pages[page_num]) - # 更新last_page_text为当前PDF的最后一页的文本 + # 更新 last_page_text 为当前PDF的最后一页的文本 if len(pages) > 0: - last_page_text = pages[-1].extract_text() if pages[-1].extract_text() else "" + last_page_text = pages[-1].extract_text() or "" except Exception as e: print(f"文件 '{path}' 无法处理,错误: {e}") continue + # 如果没有添加任何页面,返回空字符串 + if len(pdf_writer.pages) == 0: + return "" + # 写入合并后的PDF到文件 - with open(output_path, 'wb') as out: - pdf_writer.write(out) + try: + with open(output_path, 'wb') as out: + pdf_writer.write(out) + return output_path + except Exception as e: + print(f"无法写入输出文件,错误: {e}") + return "" def judge_file_exist(original_path, new_suffix): # 提取目录路径和原始文件名 diff --git a/flask_app/general/商务技术评分提取.py b/flask_app/general/商务技术评分提取.py index f18f0db..8a27861 100644 --- a/flask_app/general/商务技术评分提取.py +++ b/flask_app/general/商务技术评分提取.py @@ -213,12 +213,18 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): return DEFAULT_EVALUATION_REVIEW.copy() def run_first_query(file_path): + print("判断有无评分") # 上传文件并获取文件ID file_id = upload_file(file_path) # 定义用户查询 query = ( - "根据该文档,你判断它是否有关于技术评分或商务评分或投标报价的具体的评分及要求,如果有,返回'是',否则返回'否'。" + """根据该文档,你判断它是否有关于技术评分或商务评分或投标报价的具体的评分及要求如果有,返回'是',否则返回'否'。 + 要求与指南: + 1. 竞争性磋商文件通常无评分要求 + 2. 评分要求主要以表格形式呈现,且会有评分因素及评分要求。 + 3. 仅返回'是'或'否',不需要其他解释或内容。 + """ ) # 应对竞争性谈判这种无评分要求的情况 # 执行查询 @@ -232,15 +238,20 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): 请以 JSON 格式返回结果,最外层键名为 '技术评分'、'商务评分' 和 '投标报价评分'。在每大项下,用键值对表示具体评分项,键为具体的评审因素,若评审因素存在嵌套(表格中存在层级),请使用嵌套键值对表示,外层键名为主评审因素,嵌套的子评审因素作为内层键名,最内键值为列表,列表中包含描述评分及要求的字典。每个字典的键包括: '评分':具体得分或定性指标(如 '合格制'),无评分时可删去'评分'键值对。 '要求':说明评分标准。 - 若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注' ,值为该要求;请勿擅自添加不属于'评审因素'的键名。 + 若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注'。 要求与指南: 1. 请首先定位评分细则的表格,不要回答有关资格审查的内容,也不要从评标办法正文中提取回答 2. 你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体 3. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制' 4. 如果该招标活动有多个包,则最外层键名为对应的包名,否则最外层键名为各大评分项 - 5. 若表格中商务和技术评分混合一起,请你手动将它们区别,商务评分通常包含'售后服务'、'质量保证'、'业绩'、'企业人员'、'企业信用'等商务因素。 - 6. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分'、'商务评分'、'投标报价评分',而不必严格按照表格中的名称,若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项" + 5. 若表格中商务和技术评分混合一起,请根据实际表格内容进行准确分类。 + 6. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分'、'商务评分'、'投标报价评分',而不必严格按照表格中的名称。 + 7. 若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项" + +禁止内容: +1. 确保所有输出内容均基于提供的实际招标文件内容(除了最外层的三个评分大项名称),不使用任何预设的示例作为回答。 +2. 不得擅自添加不属于评审因素的键名以及 `'备注'` 之外的其他键名。 以下为示例输出,仅供格式参考: { @@ -305,15 +316,20 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): 请以 JSON 格式返回结果,最外层键名为 '技术评分'、'商务评分' 和 '投标报价评分'。在每大项下,用键值对表示具体评分项,键为具体的评审因素,若评审因素存在嵌套(表格中存在层级),请使用嵌套键值对表示,外层键名为主评审因素,嵌套的子评审因素作为内层键名,最内键值为列表,列表中包含描述评分及要求的字典。每个字典的键包括: '评分':具体得分或定性指标(如 '合格制'),无评分时可删去'评分'键值对。 '要求':说明评分标准。 -若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注' ,值为该要求;请勿擅自添加不属于'评审因素'的键名。 +若这三大项评分中存在额外信息(不属于某个评审因素,即该大项评分的整体要求),在该评分项内部新增键名为'备注',值为该要求。 要求与指南: 1. 请首先定位评分细则的表格,不要回答有关资格审查的内容,也不要从评标办法正文中提取回答 2. 你无需将表格的单元格内的内容进行拆分,需要将它视为一个整体 3. '评分'的键值不能是一个范围数字,如'0-5分',应该是一个具体数字,如'5分',或者是一个定性的指标如'合格制' 4. 如果该招标活动有多个包,则最外层键名为对应的包名,否则最外层键名为各大评分项 -5. 若表格中商务和技术评分混合一起,请你手动将它们区别,商务评分通常包含'售后服务'、'质量保证'、'业绩'、'企业人员'、'企业信用'等商务因素。 -6. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分'、'商务评分'、'投标报价评分',而不必严格按照表格中的名称,若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项" +5. 若表格中商务和技术评分混合一起,请根据实际表格内容进行准确分类。 +6. 若表中的评分大项不是这三个,请你根据语义分别映射到'技术评分'、'商务评分'、'投标报价评分',而不必严格按照表格中的名称。 +7. 若大项的'xx评分'要求未在文中说明,则键名'xx评分'的键值设为'本项目无xx评分项',例如"技术评分":"本项目无技术评分项" + +禁止内容: +1. 确保所有输出内容均基于提供的实际招标文件内容(除了最外层的三个评分大项名称),不使用任何预设的示例作为回答。 +2. 不得擅自添加不属于评审因素的键名以及 `'备注'` 之外的其他键名。 以下为示例输出,仅供格式参考: { @@ -389,7 +405,7 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): # 执行 user_query 相关的逻辑 return run_second_qeury(file_id) else: - judge_res,file_id=run_first_query(invalid_path) + judge_res,file_id=run_first_query(invalid_path) #调用 if '是' in judge_res: # 执行 user_query 相关的逻辑 return run_second_qeury(file_id) @@ -407,8 +423,8 @@ def combine_evaluation_standards(evaluation_method_path,invalid_path,zb_type): 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\招标文件\output2\zbtest7_evaluation_method.pdf' - invalid_path=r'C:\Users\Administrator\Desktop\招标文件\output2\zbtest7_evaluation_method.pdf' + evaluation_method_path = r'C:\Users\Administrator\Desktop\fsdownload\91399aa4-1ee8-447d-a05b-03cd8d15ced5\ztbfile_evaluation_method.pdf' + invalid_path=r'C:\Users\Administrator\Desktop\fsdownload\91399aa4-1ee8-447d-a05b-03cd8d15ced5\ztbfile_invalid.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" diff --git a/flask_app/routes/工程标解析main.py b/flask_app/routes/工程标解析main.py index af5f1be..a580dd7 100644 --- a/flask_app/routes/工程标解析main.py +++ b/flask_app/routes/工程标解析main.py @@ -75,7 +75,7 @@ def preprocess_files(output_folder, downloaded_file_path, file_type,unique_id,lo merged_baseinfo_path=truncate_files[-1] more_path=[merged_baseinfo_path,tobidders_notice] merged_baseinfo_path_more=os.path.join(output_folder,"merged_baseinfo_path_more.pdf") - merge_pdfs(more_path,merged_baseinfo_path_more) + merged_baseinfo_path_more=merge_pdfs(more_path,merged_baseinfo_path_more) clause_path = convert_clause_to_json(tobidders_notice, output_folder) # 投标人须知正文条款pdf->json end_time=time.time() logger.info(f"文件预处理 done,耗时:{end_time - start_time:.2f} 秒") diff --git a/flask_app/routes/货物标解析main.py b/flask_app/routes/货物标解析main.py index 5c086c6..a02d400 100644 --- a/flask_app/routes/货物标解析main.py +++ b/flask_app/routes/货物标解析main.py @@ -274,6 +274,10 @@ def goods_bid_main(output_folder, file_path, file_type, unique_id): #TODO:小解析考虑提速:1:直接pdf转文本,再切分。后期考虑。 #TODO: ec7d5328-9c57-450f-baf4-2e5a6f90ed1d + +#TODO:1.先截取合同、投标文件格式之前的页码 即 invalid 如果页码小于50页,那么剩下的不切了直接仍。 +# 2.废标项这边,考虑大模型+正则并用 +# 3.限制评分项的因素。 #商务标这里改为列表最里层 #good_list 金额 截取上下文 if __name__ == "__main__": diff --git a/flask_app/工程标/形式响应评审.py b/flask_app/工程标/形式响应评审.py index f84cd86..d22d749 100644 --- a/flask_app/工程标/形式响应评审.py +++ b/flask_app/工程标/形式响应评审.py @@ -289,7 +289,7 @@ def fetch_specific_pdf(output_folder): if baseinfo_found and pdf_paths: # 如果找到了 merged_baseinfo.pdf 和其他文件,则进行合并 output_path = os.path.join(output_folder, "merged_reviews.pdf") - merge_pdfs(pdf_paths, output_path) + output_path=merge_pdfs(pdf_paths, output_path) return output_path else: # 如果未找到 merged_baseinfo.pdf 且没有 invalid.pdf 匹配 diff --git a/flask_app/工程标/截取pdf.py b/flask_app/工程标/截取pdf.py index 20e9946..f4ac13e 100644 --- a/flask_app/工程标/截取pdf.py +++ b/flask_app/工程标/截取pdf.py @@ -495,8 +495,18 @@ def truncate_pdf_main(input_path, output_folder, selection): def truncate_pdf_multiple(input_path, output_folder, unique_id="123"): - global logger logger = get_global_logger(unique_id) + # 新增逻辑:检查 PDF 总页数 + try: + reader = PdfReader(input_path) + num_pages = len(reader.pages) + logger.info(f"PDF '{input_path}' 的总页数为: {num_pages}") + if num_pages <= 50: + logger.info(f"PDF页数小于或等于50页,跳过切分逻辑。") + return ['', '', '', '', '', '', ''] + except Exception as e: + logger.error(f"无法读取 PDF 页数: {e}") + return ['', '', '', '', '', '', ''] base_file_name = os.path.splitext(os.path.basename(input_path))[0] # 纯文件名 truncate_files = [] selections = range(1, 6) # 选择 1 到 5 @@ -545,8 +555,17 @@ def truncate_pdf_multiple(input_path, output_folder, unique_id="123"): def truncate_pdf_specific_engineering(pdf_path, output_folder, selections, unique_id="123"): try: - global logger logger = get_global_logger(unique_id) + try: + reader = PdfReader(input_path) + num_pages = len(reader.pages) + logger.info(f"PDF '{input_path}' 的总页数为: {num_pages}") + if num_pages <= 50: + logger.info(f"PDF页数小于或等于50页,跳过切分逻辑。") + return ['', '', '', ''] + except Exception as e: + logger.error(f"无法读取 PDF 页数: {e}") + return ['', '', '', ''] base_file_name = os.path.splitext(os.path.basename(pdf_path))[0] truncate_files = [] @@ -598,14 +617,14 @@ def truncate_pdf_specific_engineering(pdf_path, output_folder, selections, uniqu if __name__ == "__main__": start_time = time.time() # input_path = r"C:\Users\Administrator\Desktop\new招标文件\工程标" - input_path=r"C:\Users\Administrator\Desktop\fsdownload\ec7d5328-9c57-450f-baf4-2e5a6f90ed1d\ztbfile.pdf" + input_path=r"C:\Users\Administrator\Desktop\fsdownload\91399aa4-1ee8-447d-a05b-03cd8d15ced5\西藏监管局办.pdf" # input_path = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\2-招标文件.pdf" # input_path=r"C:\Users\Administrator\Desktop\招标文件\招标test文件夹\zbtest8.pdf" - output_folder = r"C:\Users\Administrator\Desktop\fsdownload\ec7d5328-9c57-450f-baf4-2e5a6f90ed1d\tmp" + output_folder = r"C:\Users\Administrator\Desktop\fsdownload\91399aa4-1ee8-447d-a05b-03cd8d15ced5\tmp" files=truncate_pdf_multiple(input_path,output_folder) - print(files) # selections = [4, 1] # 仅处理 selection 4、1 # files=truncate_pdf_specific_engineering(input_path,output_folder,selections) + print(files) # selection = 2 # 例如:1 - 投标人须知前附表+正文, 2 - 评标办法, 3 -资格审查条件 4-招标公告 5-无效标 # generated_files = truncate_pdf_main(input_path, output_folder, selection) # print(generated_files) diff --git a/flask_app/工程标/资格审查模块.py b/flask_app/工程标/资格审查模块.py index 49fa05d..c0b782e 100644 --- a/flask_app/工程标/资格审查模块.py +++ b/flask_app/工程标/资格审查模块.py @@ -70,7 +70,7 @@ def combine_review_standards(evaluation_method, qualification_path, output_folde print("call 资格审查main(货物标)") paths=[qualification_path,evaluation_method] more_qualification_path=os.path.join(output_folder,"merged_qualification.pdf") - merge_pdfs(paths,more_qualification_path) + more_qualification_path=merge_pdfs(paths,more_qualification_path) final_result=combine_qualification_review(invalid_path,more_qualification_path,notice_path) else: tobidders_notice_table_docx = pdf2docx(tobidders_notice_table) # 投标人须知前附表转docx diff --git a/flask_app/货物标/截取pdf货物标版.py b/flask_app/货物标/截取pdf货物标版.py index e175928..2467a42 100644 --- a/flask_app/货物标/截取pdf货物标版.py +++ b/flask_app/货物标/截取pdf货物标版.py @@ -7,7 +7,7 @@ import os # 用于文件和文件夹操作 from flask_app.general.clean_pdf import clean_page_content, extract_common_header from flask_app.general.format_change import docx2pdf -from flask_app.general.merge_pdfs import merge_and_cleanup, merge_pdfs, merge_selected_pdfs_for_goods +from flask_app.general.merge_pdfs import merge_and_cleanup, merge_selected_pdfs_for_goods import concurrent.futures @@ -685,6 +685,18 @@ def process_input(input_path, output_folder, selection, output_suffix): def truncate_pdf_multiple(pdf_path, output_folder, logger): + # 新增逻辑:检查 PDF 总页数 + try: + reader = PdfReader(pdf_path) + num_pages = len(reader.pages) + logger.info(f"PDF '{pdf_path}' 的总页数为: {num_pages}") + if num_pages <= 50: + logger.info(f"PDF页数小于或等于50页,跳过切分逻辑。") + return ['', '', '', '', '', '', ''] + except Exception as e: + logger.error(f"无法读取 PDF 页数: {e}") + return ['', '', '', '', '', '', ''] + base_file_name = os.path.splitext(os.path.basename(pdf_path))[0] truncate_files = [] @@ -738,6 +750,17 @@ def truncate_pdf_specific_goods(pdf_path, output_folder, selections, unique_id=" list: 截取的文件路径列表,包括合并后的文件路径(如果有)。 """ logger = get_global_logger(unique_id) + # 新增逻辑:检查 PDF 总页数 + try: + reader = PdfReader(pdf_path) + num_pages = len(reader.pages) + logger.info(f"PDF '{pdf_path}' 的总页数为: {num_pages}") + if num_pages <= 50: + logger.info(f"PDF页数小于或等于50页,跳过切分逻辑。") + return ['', '', '', ''] + except Exception as e: + logger.error(f"无法读取 PDF 页数: {e}") + return ['', '', '', ''] base_file_name = os.path.splitext(os.path.basename(pdf_path))[0] truncate_files = [] @@ -788,9 +811,9 @@ if __name__ == "__main__": input_path=r"C:\Users\Administrator\Desktop\fsdownload\ff2acdba-3a55-48a7-aa7a-61d9f89b909a\ztbfile.pdf" output_folder = r"C:\Users\Administrator\Desktop\fsdownload\ff2acdba-3a55-48a7-aa7a-61d9f89b909a\tmp" # output_folder = r"C:\Users\Administrator\Desktop\new招标文件\output2" - files = truncate_pdf_multiple(input_path, output_folder,logger) - # selections = [3,5] - # files=truncate_pdf_specific_goods(input_path,output_folder,selections) + # files = truncate_pdf_multiple(input_path, output_folder,logger) + selections = [1,4] + files=truncate_pdf_specific_goods(input_path,output_folder,selections) print(files) # selection = 2 # 例如:1 - 公告, 2 - 评标办法, 3 - 资格审查后缀有qualification1或qualification2(与评标办法一致) 4.投标人须知前附表part1 投标人须知正文part2 5-采购需求 # generated_files = truncate_pdf_main(input_path, output_folder, selection) diff --git a/flask_app/货物标/技术参数要求提取.py b/flask_app/货物标/技术参数要求提取.py index 113bb2c..d7ce702 100644 --- a/flask_app/货物标/技术参数要求提取.py +++ b/flask_app/货物标/技术参数要求提取.py @@ -382,6 +382,7 @@ def get_technical_requirements(invalid_path,processed_filepath): processed_data=truncate_system_keys(cleaned_res['采购需求']) 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) + print(json.dumps(modified_data,ensure_ascii=False,indent=4)) user_query_template = """请根据货物标中采购要求部分的内容,告诉我\"{}\"的技术参数或采购要求是什么。请以 JSON 格式返回结果,键名为\"{}\",键值为一个列表,列表中包含若干描述\"{}\"的技术参数或采购要求或功能说明的字符串,请按原文内容回答,保留三角▲、五角★和序号,不可擅自增删内容,尤其是不可擅自添加序号。 要求与指南: @@ -526,8 +527,8 @@ def test_all_files_in_folder(input_folder, output_folder): if __name__ == "__main__": start_time=time.time() # truncate_file="C:\\Users\\Administrator\\Desktop\\fsdownload\\469d2aee-9024-4993-896e-2ac7322d41b7\\ztbfile_procurement.docx" - truncate_docfile=r"C:\Users\Administrator\Desktop\new招标文件\货物标\HBDL-2024-0481-001-招标文件.docx" - truncate_file=r'C:\Users\Administrator\Desktop\new招标文件\货物标\HBDL-2024-0481-001-招标文件.pdf' + truncate_docfile=r"C:\Users\Administrator\Desktop\fsdownload\6b7ea51f-eb6d-4a4f-a518-dc1f57d27ea1\ztbfile.docx" + truncate_file=r'C:\Users\Administrator\Desktop\fsdownload\6b7ea51f-eb6d-4a4f-a518-dc1f57d27ea1\省考试院院内电子屏采购.pdf' # invalid_path="D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile.pdf" # truncate_file="D:\\flask_project\\flask_app\\static\\output\\output1\\e7dda5cb-10ba-47a8-b989-d2993d34bb89\\ztbfile_procurement.docx" # output_folder="C:\\Users\\Administrator\\Desktop\\货物标\\output1\\tmp"