2024-10-15 20:57:58 +08:00
|
|
|
|
# 竞磋 竞谈 磋商 询价 邀请 单一来源
|
2024-10-12 18:01:59 +08:00
|
|
|
|
import json
|
2024-10-31 15:03:32 +08:00
|
|
|
|
import time
|
2024-10-23 20:33:41 +08:00
|
|
|
|
from flask_app.general.format_change import docx2pdf, pdf2docx,doc2docx
|
2024-10-22 10:06:22 +08:00
|
|
|
|
from flask_app.general.json_utils import transform_json_values
|
2024-10-12 18:01:59 +08:00
|
|
|
|
from flask_app.货物标.基础信息解析main import combine_basic_info
|
|
|
|
|
from flask_app.货物标.投标人须知正文提取指定内容货物标版 import extract_from_notice
|
2024-10-23 20:33:41 +08:00
|
|
|
|
from flask_app.货物标.截取pdf货物标版 import truncate_pdf_multiple
|
2024-10-12 18:01:59 +08:00
|
|
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
|
|
|
import concurrent.futures
|
2024-11-11 17:12:38 +08:00
|
|
|
|
from flask_app.货物标.提取json货物标版 import convert_clause_to_json
|
2024-11-13 11:46:13 +08:00
|
|
|
|
from flask_app.general.无效标和废标公共代码 import combine_find_invalid
|
2024-10-12 18:01:59 +08:00
|
|
|
|
from flask_app.货物标.资格审查main import combine_qualification_review
|
|
|
|
|
from flask_app.货物标.评分标准提取main import combine_evaluation_standards
|
|
|
|
|
import logging
|
2024-10-15 20:57:58 +08:00
|
|
|
|
|
|
|
|
|
|
2024-10-12 18:01:59 +08:00
|
|
|
|
def get_global_logger(unique_id):
|
|
|
|
|
if unique_id is None:
|
|
|
|
|
return logging.getLogger() # 获取默认的日志器
|
|
|
|
|
logger = logging.getLogger(unique_id)
|
|
|
|
|
return logger
|
|
|
|
|
|
|
|
|
|
# 创建全局线程池
|
|
|
|
|
executor = ThreadPoolExecutor()
|
2024-10-15 20:57:58 +08:00
|
|
|
|
|
|
|
|
|
|
2024-11-15 09:23:26 +08:00
|
|
|
|
def preprocess_files(output_folder, file_path, file_type,logger):
|
2024-10-12 18:01:59 +08:00
|
|
|
|
logger.info("starting 文件预处理...")
|
2024-10-30 20:41:19 +08:00
|
|
|
|
start_time = time.time()
|
2024-10-12 18:01:59 +08:00
|
|
|
|
logger.info("output_folder..." + output_folder)
|
|
|
|
|
|
|
|
|
|
# 根据文件类型处理文件路径
|
|
|
|
|
if file_type == 1: # docx
|
|
|
|
|
docx_path = file_path
|
|
|
|
|
pdf_path = docx2pdf(docx_path) # 将docx转换为pdf以供后续处理
|
|
|
|
|
elif file_type == 2: # pdf
|
|
|
|
|
pdf_path = file_path
|
|
|
|
|
docx_path = pdf2docx(pdf_path) # 将pdf转换为docx以供上传到知识库
|
2024-10-15 20:57:58 +08:00
|
|
|
|
elif file_type == 3: # doc
|
|
|
|
|
pdf_path = docx2pdf(file_path)
|
2024-10-23 20:33:41 +08:00
|
|
|
|
docx_path = doc2docx(file_path)
|
2024-09-23 15:49:30 +08:00
|
|
|
|
else:
|
2024-10-12 18:01:59 +08:00
|
|
|
|
logger.error("Unsupported file type provided. Preprocessing halted.")
|
2024-09-23 15:49:30 +08:00
|
|
|
|
return None
|
2024-10-12 18:01:59 +08:00
|
|
|
|
|
2024-10-15 20:57:58 +08:00
|
|
|
|
# # 异步上传知识库
|
|
|
|
|
# future_knowledge = executor.submit(addfileToKnowledge, docx_path, "招标解析" + unique_id)
|
2024-10-12 18:01:59 +08:00
|
|
|
|
# 调用截取PDF多次
|
2024-10-15 20:57:58 +08:00
|
|
|
|
truncate_files = truncate_pdf_multiple(pdf_path,
|
|
|
|
|
output_folder) # index: 0->商务技术服务要求 1->评标办法 2->资格审查 3->投标人须知前附表 4->投标人须知正文
|
2024-10-12 18:01:59 +08:00
|
|
|
|
|
|
|
|
|
# 处理各个部分
|
2024-10-30 18:08:46 +08:00
|
|
|
|
invalid_path=pdf_path
|
2024-10-15 20:57:58 +08:00
|
|
|
|
invalid_docpath = docx_path # docx截取无效标部分
|
2024-10-31 15:03:32 +08:00
|
|
|
|
procurement_path = truncate_files[5] # 采购需求
|
2024-10-15 20:57:58 +08:00
|
|
|
|
evaluation_method_path = truncate_files[1] # 评标办法
|
|
|
|
|
qualification_path = truncate_files[2] # 资格审查
|
|
|
|
|
tobidders_notice_path = truncate_files[4] # 投标人须知正文
|
2024-10-28 17:40:02 +08:00
|
|
|
|
notice_path = truncate_files[0] #招标公告
|
2024-10-15 20:57:58 +08:00
|
|
|
|
merged_baseinfo_path = truncate_files[6] # 合并封面+招标公告+投标人须知前附表+须知正文
|
2024-10-12 18:01:59 +08:00
|
|
|
|
clause_path = convert_clause_to_json(tobidders_notice_path, output_folder) # 投标人须知正文条款pdf->json
|
|
|
|
|
|
2024-10-30 20:41:19 +08:00
|
|
|
|
end_time = time.time()
|
|
|
|
|
logger.info(f"文件预处理 done,耗时:{end_time - start_time:.2f} 秒")
|
2024-10-12 18:01:59 +08:00
|
|
|
|
|
|
|
|
|
# 提前返回,不等待 future_knowledge 完成,返回包含 Future 对象
|
|
|
|
|
return {
|
2024-10-30 18:08:46 +08:00
|
|
|
|
'invalid_path': invalid_path,
|
2024-10-12 18:01:59 +08:00
|
|
|
|
'output_folder': output_folder,
|
|
|
|
|
'procurement_path': procurement_path,
|
|
|
|
|
'evaluation_method_path': evaluation_method_path,
|
|
|
|
|
'qualification_path': qualification_path,
|
2024-10-15 20:57:58 +08:00
|
|
|
|
'notice_path': notice_path,
|
|
|
|
|
# 'knowledge_future': future_knowledge, # 返回 Future 对象
|
2024-10-12 18:01:59 +08:00
|
|
|
|
'clause_path': clause_path,
|
2024-10-15 20:57:58 +08:00
|
|
|
|
'invalid_docpath': invalid_docpath,
|
|
|
|
|
'merged_baseinfo_path': merged_baseinfo_path
|
2024-10-12 18:01:59 +08:00
|
|
|
|
}
|
2024-10-15 20:57:58 +08:00
|
|
|
|
|
2024-11-16 16:14:53 +08:00
|
|
|
|
def fetch_project_basic_info(invalid_path,invalid_docpath, merged_baseinfo_path, procurement_path, clause_path,logger):
|
2024-10-30 20:41:19 +08:00
|
|
|
|
logger.info("starting 基础信息...")
|
|
|
|
|
start_time = time.time()
|
2024-10-30 18:08:46 +08:00
|
|
|
|
if not merged_baseinfo_path:
|
2024-10-30 20:41:19 +08:00
|
|
|
|
merged_baseinfo_path = invalid_path
|
2024-11-07 17:32:45 +08:00
|
|
|
|
if not procurement_path:
|
|
|
|
|
procurement_path=invalid_path
|
2024-11-16 16:14:53 +08:00
|
|
|
|
basic_res = combine_basic_info(merged_baseinfo_path, procurement_path,clause_path,invalid_path)
|
2024-11-15 09:23:26 +08:00
|
|
|
|
base_info, good_list = post_process_baseinfo(basic_res,logger)
|
2024-10-30 20:41:19 +08:00
|
|
|
|
end_time = time.time()
|
|
|
|
|
logger.info(f"基础信息 done,耗时:{end_time - start_time:.2f} 秒")
|
|
|
|
|
return base_info, good_list
|
|
|
|
|
|
|
|
|
|
|
2024-11-15 09:23:26 +08:00
|
|
|
|
def fetch_qualification_review(invalid_path,qualification_path, notice_path,logger):
|
2024-10-30 20:41:19 +08:00
|
|
|
|
logger.info("starting 资格审查...")
|
|
|
|
|
start_time = time.time()
|
2024-11-07 16:22:52 +08:00
|
|
|
|
review_standards_res = combine_qualification_review(invalid_path,qualification_path, notice_path)
|
2024-10-30 20:41:19 +08:00
|
|
|
|
end_time = time.time()
|
|
|
|
|
logger.info(f"资格审查 done,耗时:{end_time - start_time:.2f} 秒")
|
2024-10-12 18:01:59 +08:00
|
|
|
|
return review_standards_res
|
|
|
|
|
|
2024-10-15 20:57:58 +08:00
|
|
|
|
|
2024-11-15 09:23:26 +08:00
|
|
|
|
def fetch_evaluation_standards(invalid_path, evaluation_method_path,logger):
|
2024-10-12 18:01:59 +08:00
|
|
|
|
logger.info("starting 商务评分和技术评分...")
|
2024-10-30 20:41:19 +08:00
|
|
|
|
start_time = time.time()
|
2024-10-30 18:08:46 +08:00
|
|
|
|
if not evaluation_method_path:
|
2024-10-30 20:41:19 +08:00
|
|
|
|
evaluation_method_path = invalid_path
|
2024-10-12 18:01:59 +08:00
|
|
|
|
evaluation_standards_res = combine_evaluation_standards(evaluation_method_path)
|
|
|
|
|
technical_standards = {"技术评分": evaluation_standards_res.get("技术评分", {})}
|
|
|
|
|
commercial_standards = {"商务评分": evaluation_standards_res.get("商务评分", {})}
|
2024-10-30 20:41:19 +08:00
|
|
|
|
end_time = time.time()
|
|
|
|
|
logger.info(f"商务评分和技术评分 done,耗时:{end_time - start_time:.2f} 秒")
|
2024-10-12 18:01:59 +08:00
|
|
|
|
return {
|
|
|
|
|
"technical_standards": technical_standards,
|
|
|
|
|
"commercial_standards": commercial_standards
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-15 09:23:26 +08:00
|
|
|
|
def fetch_invalid_requirements(invalid_docpath, output_folder,logger):
|
2024-10-30 20:41:19 +08:00
|
|
|
|
logger.info("starting 无效标与废标...")
|
|
|
|
|
start_time = time.time()
|
2024-10-12 18:01:59 +08:00
|
|
|
|
find_invalid_res = combine_find_invalid(invalid_docpath, output_folder)
|
2024-10-30 20:41:19 +08:00
|
|
|
|
end_time = time.time()
|
|
|
|
|
logger.info(f"无效标与废标 done,耗时:{end_time - start_time:.2f} 秒")
|
2024-10-12 18:01:59 +08:00
|
|
|
|
return find_invalid_res
|
|
|
|
|
|
2024-11-15 09:23:26 +08:00
|
|
|
|
def fetch_bidding_documents_requirements(invalid_path,merged_baseinfo_path,clause_path,logger):
|
2024-10-30 20:41:19 +08:00
|
|
|
|
logger.info("starting 投标文件要求...")
|
2024-11-04 17:13:06 +08:00
|
|
|
|
if not merged_baseinfo_path:
|
|
|
|
|
merged_baseinfo_path=invalid_path
|
2024-10-30 20:41:19 +08:00
|
|
|
|
start_time = time.time()
|
2024-10-31 15:03:32 +08:00
|
|
|
|
selection=1
|
2024-11-04 17:13:06 +08:00
|
|
|
|
fetch_bidding_documents_requirements_json = extract_from_notice(merged_baseinfo_path,clause_path, selection)
|
2024-10-30 20:41:19 +08:00
|
|
|
|
end_time = time.time()
|
|
|
|
|
logger.info(f"投标文件要求 done,耗时:{end_time - start_time:.2f} 秒")
|
2024-10-15 20:57:58 +08:00
|
|
|
|
return {"投标文件要求": fetch_bidding_documents_requirements_json}
|
|
|
|
|
|
2024-10-12 18:01:59 +08:00
|
|
|
|
|
|
|
|
|
# 开评定标流程
|
2024-11-15 09:23:26 +08:00
|
|
|
|
def fetch_bid_opening(invalid_path,merged_baseinfo_path,clause_path,logger):
|
2024-10-30 20:41:19 +08:00
|
|
|
|
logger.info("starting 开评定标流程...")
|
2024-11-04 17:13:06 +08:00
|
|
|
|
if not merged_baseinfo_path:
|
|
|
|
|
merged_baseinfo_path=invalid_path
|
2024-10-30 20:41:19 +08:00
|
|
|
|
start_time = time.time()
|
2024-10-31 15:03:32 +08:00
|
|
|
|
selection=2
|
2024-11-04 17:13:06 +08:00
|
|
|
|
fetch_bid_opening_json = extract_from_notice(merged_baseinfo_path,clause_path, selection)
|
2024-10-30 20:41:19 +08:00
|
|
|
|
end_time = time.time()
|
|
|
|
|
logger.info(f"开评定标流程 done,耗时:{end_time - start_time:.2f} 秒")
|
2024-10-15 20:57:58 +08:00
|
|
|
|
return {"开评定标流程": fetch_bid_opening_json}
|
|
|
|
|
|
2024-10-12 18:01:59 +08:00
|
|
|
|
|
2024-11-15 09:23:26 +08:00
|
|
|
|
def post_process_baseinfo(base_info,logger):
|
2024-10-25 17:50:20 +08:00
|
|
|
|
"""
|
|
|
|
|
在 'base_info' 任务完成后执行的函数。
|
|
|
|
|
确保在缺少某些键时,返回 good_list=[]。
|
|
|
|
|
|
|
|
|
|
参数:
|
|
|
|
|
- base_info (dict): 原始的 base_info 数据。
|
|
|
|
|
|
|
|
|
|
返回:
|
|
|
|
|
- tuple: (处理后的 base_info, good_list)
|
|
|
|
|
"""
|
|
|
|
|
try:
|
2024-10-27 12:08:54 +08:00
|
|
|
|
pure_base_info = base_info.get("基础信息", {})
|
2024-10-25 17:50:20 +08:00
|
|
|
|
# 尝试提取 '货物列表',若中间某个键不存在,返回 good_list=[]
|
2024-10-27 12:08:54 +08:00
|
|
|
|
procurement_reqs = pure_base_info.get('采购要求', {})
|
2024-11-08 19:55:02 +08:00
|
|
|
|
technical_requirements = procurement_reqs.get('采购需求', {})
|
2024-10-25 17:50:20 +08:00
|
|
|
|
good_list = technical_requirements.pop('货物列表', []) # 如果 '货物列表' 不存在,返回 []
|
2024-10-27 12:08:54 +08:00
|
|
|
|
|
|
|
|
|
# 删除 '货物列表' 后更新原始的 base_info
|
2024-11-08 19:55:02 +08:00
|
|
|
|
if '采购需求' in procurement_reqs and not technical_requirements:
|
2024-10-27 12:08:54 +08:00
|
|
|
|
# 若技术要求为空,删除该键
|
2024-11-08 19:55:02 +08:00
|
|
|
|
procurement_reqs.pop('采购需求')
|
2024-10-27 12:08:54 +08:00
|
|
|
|
|
|
|
|
|
if '采购要求' in pure_base_info and not procurement_reqs:
|
|
|
|
|
# 若采购要求为空,删除该键
|
|
|
|
|
pure_base_info.pop('采购要求')
|
|
|
|
|
|
|
|
|
|
# 更新基础信息
|
|
|
|
|
base_info['基础信息'] = pure_base_info
|
|
|
|
|
|
2024-10-25 17:50:20 +08:00
|
|
|
|
logger.info(f"Extracted good_list: {good_list}")
|
|
|
|
|
return base_info, good_list
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error(f"Error in post_process_baseinfo: {e}")
|
|
|
|
|
return base_info, [] # 返回空列表
|
|
|
|
|
|
2024-10-15 20:57:58 +08:00
|
|
|
|
def goods_bid_main(output_folder, file_path, file_type, unique_id):
|
2024-10-12 18:01:59 +08:00
|
|
|
|
logger = get_global_logger(unique_id)
|
|
|
|
|
# 预处理文件,获取处理后的数据
|
2024-11-15 09:23:26 +08:00
|
|
|
|
processed_data = preprocess_files(output_folder, file_path, file_type,logger)
|
2024-10-12 18:01:59 +08:00
|
|
|
|
if not processed_data:
|
|
|
|
|
yield json.dumps({}) # 如果处理数据失败,返回空的 JSON
|
|
|
|
|
|
|
|
|
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
|
|
|
# 立即启动不依赖 knowledge_name 和 index 的任务
|
|
|
|
|
futures = {
|
2024-10-30 20:41:19 +08:00
|
|
|
|
'evaluation_standards': executor.submit(fetch_evaluation_standards,processed_data['invalid_path'],
|
2024-11-15 09:23:26 +08:00
|
|
|
|
processed_data['evaluation_method_path'],logger),
|
2024-10-15 20:57:58 +08:00
|
|
|
|
'invalid_requirements': executor.submit(fetch_invalid_requirements, processed_data['invalid_docpath'],
|
2024-11-15 09:23:26 +08:00
|
|
|
|
output_folder,logger),
|
2024-11-04 17:13:06 +08:00
|
|
|
|
'bidding_documents_requirements': executor.submit(fetch_bidding_documents_requirements,processed_data['invalid_path'],processed_data['merged_baseinfo_path'],
|
2024-11-15 09:23:26 +08:00
|
|
|
|
processed_data['clause_path'],logger),
|
|
|
|
|
'opening_bid': executor.submit(fetch_bid_opening, processed_data['invalid_path'],processed_data['merged_baseinfo_path'],processed_data['clause_path'],logger),
|
2024-10-31 15:03:32 +08:00
|
|
|
|
'base_info': executor.submit(fetch_project_basic_info, processed_data['invalid_path'],processed_data['invalid_docpath'],processed_data['merged_baseinfo_path'],
|
2024-11-16 16:14:53 +08:00
|
|
|
|
processed_data['procurement_path'],processed_data['clause_path'],logger),
|
2024-11-07 16:36:42 +08:00
|
|
|
|
'qualification_review': executor.submit(fetch_qualification_review, processed_data['invalid_path'],
|
2024-10-15 20:57:58 +08:00
|
|
|
|
processed_data['qualification_path'],
|
2024-11-15 09:23:26 +08:00
|
|
|
|
processed_data['notice_path'],logger),
|
2024-10-12 18:01:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 提前处理这些不依赖的任务,按完成顺序返回
|
|
|
|
|
for future in concurrent.futures.as_completed(futures.values()):
|
|
|
|
|
key = next(k for k, v in futures.items() if v == future)
|
|
|
|
|
try:
|
|
|
|
|
result = future.result()
|
2024-10-25 17:50:20 +08:00
|
|
|
|
if key == 'base_info':
|
|
|
|
|
base_info, good_list = result
|
|
|
|
|
collected_good_list = good_list # Store good_list for later use
|
|
|
|
|
yield json.dumps({'base_info': transform_json_values(base_info)}, ensure_ascii=False)
|
2024-10-12 18:01:59 +08:00
|
|
|
|
# 如果是 evaluation_standards,拆分技术标和商务标
|
2024-10-28 17:40:02 +08:00
|
|
|
|
elif key == 'evaluation_standards':
|
2024-10-12 18:01:59 +08:00
|
|
|
|
technical_standards = result["technical_standards"]
|
|
|
|
|
commercial_standards = result["commercial_standards"]
|
|
|
|
|
# 分别返回技术标和商务标
|
2024-10-28 17:40:02 +08:00
|
|
|
|
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)
|
2024-10-12 18:01:59 +08:00
|
|
|
|
else:
|
|
|
|
|
# 处理其他任务的结果
|
|
|
|
|
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)
|
2024-10-25 17:50:20 +08:00
|
|
|
|
if collected_good_list is not None:
|
|
|
|
|
yield json.dumps({'good_list': transform_json_values(collected_good_list)}, ensure_ascii=False)
|
2024-10-12 18:01:59 +08:00
|
|
|
|
|
2024-10-17 19:07:57 +08:00
|
|
|
|
#广水市 2022 年义务教育学校多媒体补充采购项目 资格审查有问题
|
2024-11-06 14:07:21 +08:00
|
|
|
|
|
2024-11-15 09:23:26 +08:00
|
|
|
|
|
2024-11-08 19:30:19 +08:00
|
|
|
|
#TODO:把所有未知都删掉。
|
2024-11-16 16:14:53 +08:00
|
|
|
|
#TODO:考虑把解析失败的调用豆包,全文上传。
|
2024-11-08 19:30:19 +08:00
|
|
|
|
#商务标这里改为列表最里层
|
2024-10-25 17:50:20 +08:00
|
|
|
|
#good_list 金额 截取上下文
|
2024-10-14 10:52:31 +08:00
|
|
|
|
if __name__ == "__main__":
|
2024-10-21 17:31:48 +08:00
|
|
|
|
# 配置日志器
|
|
|
|
|
unique_id = "uuidzyzy11"
|
2024-11-15 09:23:26 +08:00
|
|
|
|
# logger = get_global_logger(unique_id)
|
2024-10-21 17:31:48 +08:00
|
|
|
|
|
|
|
|
|
output_folder = "flask_app/static/output/zytest1"
|
2024-10-15 20:57:58 +08:00
|
|
|
|
file_type = 1 # 1:docx 2:pdf 3:其他
|
2024-10-14 10:52:31 +08:00
|
|
|
|
input_file = "C:\\Users\\Administrator\\Desktop\\货物标\\zbfiles\\6.2定版视频会议磋商文件.pdf"
|
2024-10-21 17:31:48 +08:00
|
|
|
|
start_time = time.time()
|
|
|
|
|
# 创建生成器
|
|
|
|
|
generator = goods_bid_main(output_folder, input_file, file_type, unique_id)
|
|
|
|
|
|
|
|
|
|
# 迭代生成器,逐步获取和处理结果
|
|
|
|
|
for output in generator:
|
|
|
|
|
print(output)
|
|
|
|
|
|
2024-10-14 10:52:31 +08:00
|
|
|
|
end_time = time.time()
|
|
|
|
|
elapsed_time = end_time - start_time # 计算耗时
|
2024-10-21 17:31:48 +08:00
|
|
|
|
print(f"Function execution took {elapsed_time:.2f} seconds.")
|
|
|
|
|
|