# -*- encoding:utf-8 -*-
import json
import logging
import os
import time
from flask_app.main.截取pdf import truncate_pdf_multiple
from flask_app.main.table_content_extraction import extract_tables_main
from flask_app.main.知识库操作 import addfileToKnowledge, deleteKnowledge
from flask_app.main.投标人须知正文条款提取成json文件 import convert_clause_to_json
from flask_app.main.json_utils import nest_json_under_key, transform_json_values, combine_json_results
from flask_app.main.无效标和废标和禁止投标整合 import combine_find_invalid
from flask_app.main.投标人须知正文提取指定内容 import extract_from_notice
import concurrent.futures
from flask_app.main.基础信息整合 import project_basic_info
from flask_app.main.资格审查模块 import combine_review_standards
from flask_app.main.商务标技术标整合 import combine_evaluation_standards
from flask_app.main.format_change import pdf2docx, docx2pdf
from flask_app.main.docx截取docx import copy_docx
global_logger = None
def get_global_logger(unique_id):
if unique_id is None:
return logging.getLogger() # 获取默认的日志器
logger = logging.getLogger(unique_id)
return logger
# 可能有问题:pdf转docx导致打勾符号消失
def preprocess_files(output_folder, downloaded_file_path, file_type, unique_id):
# 根据文件类型处理文件路径
global docx_path, pdf_path
if file_type == 1: # docx
docx_path = downloaded_file_path
pdf_path = docx2pdf(docx_path) # 将docx转换为pdf以供后续处理
elif file_type == 2: # pdf
pdf_path = downloaded_file_path
docx_path = pdf2docx(pdf_path) # 将pdf转换为docx以供上传到知识库
# 上传知识库
knowledge_name = "招标解析" + unique_id
index = addfileToKnowledge(docx_path, knowledge_name)
# 调用截取PDF多次
truncate_files = truncate_pdf_multiple(pdf_path, output_folder) # [前附表, 评标办法, 须知正文, 资格审查条件]
print(truncate_files)
# 处理各个部分
truncate0_docpath = pdf2docx(truncate_files[0]) # 投标人须知前附表转docx
invalid_docpath = copy_docx(docx_path) #docx截取无效标部分
truncate_jsonpath=extract_tables_main(truncate0_docpath, output_folder) # 投标人须知前附表docx->json,从表格提取数据
truncate0 = truncate_files[0] #投标人须知前附表
truncate1 = truncate_files[1] #评标办法前附表
truncate3 = truncate_files[3] #资格审查表
clause_path = convert_clause_to_json(truncate_files[2], output_folder) # 投标人须知正文条款pdf->json
return {
'input_file_path':downloaded_file_path,
'output_folder':output_folder,
'truncate0': truncate0,
'truncate1': truncate1,
'truncate3': truncate3,
'knowledge_index': index,
'knowledge_name': knowledge_name,
'truncate0_jsonpath': truncate_jsonpath,
'clause_path': clause_path,
'invalid_docpath': invalid_docpath
}
# 基本信息
def fetch_project_basic_info(knowledge_name, truncate0, output_folder, clause_path): # 投标人须知前附表
global_logger.info("starting基础信息...")
basic_res = project_basic_info(knowledge_name, truncate0, output_folder, clause_path)
global_logger.info("基础信息done")
return basic_res
# 形式、响应、资格评审
def fetch_review_standards(truncate1, truncate3, knowledge_name, truncate0_jsonpath, clause_path,input_file,output_folder):
global_logger.info("starting资格审查...")
review_standards_res = combine_review_standards(truncate1, truncate3, knowledge_name, truncate0_jsonpath,
clause_path,input_file,output_folder)
global_logger.info("资格审查done")
return review_standards_res
# 评分细则
def fetch_evaluation_standards(truncate1): # 评标办法前附表
global_logger.info("starting商务标技术标...")
evaluation_standards_res = combine_evaluation_standards(truncate1)
global_logger.info("商务标技术标done")
return evaluation_standards_res
# 无效、废标项解析
def fetch_invalid_requirements(invalid_docpath, output_folder, truncate0_jsonpath, clause_path, truncate3):
# 废标项要求:千问
global_logger.info("starting无效标与废标...")
find_invalid_res = combine_find_invalid(invalid_docpath, output_folder, truncate0_jsonpath, clause_path, truncate3)
global_logger.info("无效标与废标done...")
return find_invalid_res
# 投标文件要求
def fetch_bidding_documents_requirements(clause_path):
global_logger.info("starting投标文件要求...")
fetch_bidding_documents_requirements_json = extract_from_notice(clause_path, 1)
qualify_nested_res = nest_json_under_key(fetch_bidding_documents_requirements_json, "投标文件要求")
global_logger.info("投标文件要求done...")
return qualify_nested_res
# 开评定标流程
def fetch_bid_opening(clause_path):
global_logger.info("starting开评定标流程...")
fetch_bid_opening_json = extract_from_notice(clause_path, 2)
qualify_nested_res = nest_json_under_key(fetch_bid_opening_json, "开评定标流程")
global_logger.info("开评定标流程done...")
return qualify_nested_res
def main_processing(output_folder, downloaded_file_path, file_type, unique_id): # file_type=1->docx file_type=2->pdf
global global_logger
global_logger = get_global_logger(unique_id)
# Preprocess files and get necessary data paths and knowledge index
processed_data = preprocess_files(output_folder, downloaded_file_path, file_type, unique_id)
with concurrent.futures.ThreadPoolExecutor() as executor:
# Submit all tasks to the executor
futures = {
'base_info': executor.submit(fetch_project_basic_info, processed_data['knowledge_name'],
processed_data['truncate0'], output_folder,
processed_data['clause_path']),
'review_standards': executor.submit(fetch_review_standards, processed_data['truncate1'],
processed_data['truncate3'],
processed_data['knowledge_name'], processed_data['truncate0_jsonpath'],
processed_data['clause_path'],processed_data['input_file_path'],processed_data['output_folder']),
'evaluation_standards': executor.submit(fetch_evaluation_standards, processed_data['truncate1']),
'invalid_requirements': executor.submit(fetch_invalid_requirements, processed_data['invalid_docpath'],
output_folder, processed_data['truncate0_jsonpath'],
processed_data['clause_path'], processed_data['truncate3']),
'bidding_documents_requirements': executor.submit(fetch_bidding_documents_requirements,
processed_data['clause_path']),
'opening_bid': executor.submit(fetch_bid_opening, processed_data['clause_path'])
}
comprehensive_responses = []
# Collect results in the defined order
for key in ['base_info', 'review_standards', 'evaluation_standards', 'invalid_requirements',
'bidding_documents_requirements', 'opening_bid']:
try:
# Wait for the future to complete and get the result
result = futures[key].result()
comprehensive_responses.append(result)
except Exception as exc:
global_logger.info(f"Error processing {key}: {exc}")
# 合并 JSON 结果
combined_final_result = combine_json_results(comprehensive_responses)
modified_json = transform_json_values(combined_final_result)
final_result_path = os.path.join(output_folder, "final_result.json")
with open(final_result_path, 'w', encoding='utf-8') as file:
json.dump(modified_json, file, ensure_ascii=False, indent=2)
global_logger.info("final_result.json has been saved")
deleteKnowledge(processed_data['knowledge_index'])
return final_result_path
# 目前返回结果:
# "opening_bid": "{
\"开评定标流程\": {
\"开标\": {
\"开标时间和地点\": [
\"招标人在本章第4.2.1项规定的投标截止时间(开标时间)在“电子交易平台”上公开进行开标,所有投标人均应当准时在线参加开标。\",
\"招标人通过互联网在投标人须知前附表规定的地点组织开标,并在投标截止时间30分钟前,使用CA数字证书登录“电子交易平台”,进入“开标室”选择相应标段作在线开标的准备工作。\",
\"投标人应当在能够保证设施设备可靠、互联网畅通的任意地点,通过互联网在线参加开标。在投标截止时间前,使用加密其投标文件的CA数字证书登录“电子交易平台”,进入“开标室”选择所投标段进行签到,并实时在线关注招标人的操作情况。5.2开标程序\",
\"主持人按下列程序在“电子交易平台”的“开标室”进行在线开标:(1)宣布开标纪律;(2)公布主持人、招标人代表、监标人等有关人员姓名;(3)公布在投标截止时间前投标文件的递交情况;(4)公布投标保证金递交情况;(5)按照投标人须知前附表规定抽取评标基准价下浮值(如有);规定最高投标限价计算方法的,计算并公布最高投标限价(如适用),当众公布后记录在案;(6)读取已解密的投标文件的内容;(7)公布投标人名称、标段名称、投标保证金的递交情况、投标报价、项目经理姓名及其他内容,并生成开标记录;(8)开标结束。\",
\"在本章第5.2.1(6)目规定的时间内,非因“电子交易平台”原因造成投标文件未解密的,视为投标人撤回投标文件。已解密的投标文件少于三个的,招标失败;已解密的投标文件不少于三个,开标继续进行。\"
],
\"开标异议\": [
\"投标人对开标有异议的,应当在开标过程中提出;招标人当场对异议作出答复,并记入开标记录。异议与答复应通过“开标室”在“异议与答复”菜单以书面形式进行。本处所称异议是指投标人在开标过程中对投标文件提交、投标截止时间、开标程序、开标记录以及投标人和招标人或者投标人相互之间存在利益冲突的情形等提出的质疑。\",
\"投标人异议成立的,招标人将及时采取纠正措施,或者提交评标委员会评审确认;投标人异议不成立的,招标人将当场给予解释说明。\"
],
\"特殊情况的处置\": [
\"因“电子交易平台”系统故障导致无法投标的,交易中心及时通知招标人,招标人视情况决定是否顺延投标截止时间。因投标人自身原因导致无法完成投标的,由投标人自行承担后果。\",
\"因“电子交易平台”系统故障导致无法正常开标的,招标人将暂停开标,待系统恢复正常后继续开标。\",
\"“电子交易平台”系统故障是指下列情形:(1)系统服务器发生故障,无法访问或无法使用系统;(2)系统的软件或数据库出现错误,不能进行正常操作;(3)系统发现有安全漏洞,有潜在的泄密危险;(4)出现断电、断网事故;(5)其他无法保证招投标过程正常进行的情形。\"
]
},
\"评标\": {
\"评标委员会\": [
\"评标由招标人依法组建的评标委员会负责。评标委员会由招标人代表以及有关技术、经济等方面的专家组成。评标委员会成员人数以及技术、经济等方面专家的确定方式见投标人须知前附表。\",
\"评标委员会成员有下列情形之一的,应当回避:(1)投标人或投标人主要负责人的近亲属;(2)项目主管部门或者行政监督部门的人员;(3)与投标人有经济利益关系,可能影响对投标公正评审的;(4)曾因在招标、评标以及其他与招标投标有关活动中从事违法行为而受过行政处罚或刑事处罚的。\",
\"定标会招标人原则上应当在定标候选人公示结束后5个工作日内召开定标会,如有特殊情况,最迟应当在定标候选人公示结束后10个工作日内召开定标会,定标会进入公共资源交易中心进行。\",
\"定标流程(1)签到,宣读定标委员会成员名单;(2)监督小组监督员宣读定标纪律;(3)招标人代表或招标代理机构人员向定标委员会介绍定标项目相关情况;(4)定标委员成员会有疑问的,可以向招标人代表进行提问;(5)阅相关资料;(6)投票;(7)招标人代表或招标代理机构人员进行统计;(8)定标委员会组长宣读得分结果和定标结果;定标委员会成员签署定标报告,会议结束。\",
\"定标原则(1)组建定标委员会:由招标人组建定标委员会负责定标工作,按照定标委员会定标法进行定标。定标委员会成员数量为5人,招标人的法定代表人或其授权代表(为领导班子成员之一)应当参加定标会,并推荐担任定标会组长主持定标会,定标委员会其他成员从招标人组建的定标成员库中随机抽取确定。定标委员会成员与定标候选人有利害关系的,应当回避。所有参加定标会的定标委员会成员的意见应有书面记录,并由所有定标委员会成员签字确认。(2)组建定标监督小组:由招标人组建定标监督小组,对定标委员会的定标活动全过程进行监督,定标监督小组由2人组成,一般为招标人本单位或上级单位纪检监察人员,也可由招标人的法定代表人或主要负责人指定骨干成员参加。定标监督小组有权就定标委员会违反定标规则的行为进行质询。评估是否符合内控机制及价值取向,确保定标过程公正、公平。定标前,招标人或者招标代理机构在定标前可以介绍项目情况、招标情况、对投标人或者项目负责人的考察、质询情况;招标人可以邀请评标专家代表介绍评标情况、专家评审意见及评标结论、提醒注意事项。定标委员会成员有疑问的,可以向招标人或者招标代理机构、评标专家提问。\",
\"定标办法(1)定标会成员根据评标委员会提出书面评标报告,结合定标候选人的投标报价、商务标、技术标、市场信誉等,招标人应当按照充分竞争、合理低价的原则,集体讨论后,采用简单多数原则进行票决,在进入投票范围的定标候选人中,以每人投票支持一个定标候选人的方式,得票最多且过半数的定标候选人为中标人。当没有定标候选人得票超过半数,但有2个定标候选人得票较多时,选择得票较多的2个定标候选人(按上一轮得票多少的顺序选择,在选择第2个定标候选人时出现同票的投标人时,所有同票定标候选人一并纳入下一轮的投票范围)作为二次投票的范围,直至出现得票过半数的定标候选人为止。如果没有2个定标候选人得票较多时,重新投票。(2)定标会由招标人或代理机构的工作人员发放选票、定标会成员填写选票(须说明推荐理由并署名),定标过程公开、公平、公正。定标会成员按有关规定及招标文件约定的定标方法确定一名中标人。投票定标选票招标项目名称:支持的投标人支持理由定标委员签名:时间:本项目采用“评定分离”方法实施招投标活动。本项目定标办法详见第三章附件定标办法。\",
\"定标标准(1)择优要素。招标人在定标前应对评标委员会评审结果与实际情况进行实质性审查核实,重点对投标人的企业实力、企业信誉、履约能力的真实性、准确性、一致性进行核实,招标人应如实记录审查核实情况并作为定标参考。在考虑价格因素时,招标人应坚持投标人投标报价和其履约能力、服务质量等与招标项目相匹配的原则。企业实力包括资质等级、近几年营业额、过往业绩(含业绩影响力、难易程度)等方面。企业信誉包括获得各种荣誉、过往业绩履约情况,同时应重点关注近几年的不良信息,包括建设行政主管部门作出的各种不良处罚以及其他失信记录。对拟派团队履约能力与履约水平考核方式,可以考察团队主要负责人类似业绩情况,也可以对拟派项目负责人进行答辩。为确保可追溯性,答辩工作在有录音、录像场所进行。各项考核动作要针对所有投标人统一进行,不宜针对部分投标人进行考核,以体现公平原则。在同等条件下,择优的相对标准有以下几个方面:1)投标报价:各定标候选人的报价结合其履约能力,服务质量等与招标项目相匹配,经综合比较,价格最合理得优;2)工程业绩:综合比较投标人投标人近五年,完成的单项合同额在2000万元以上的装饰装修项目,主要比较项目难易程度和项目造价,工程业绩总造价高且项目难度大的优于工程业绩总造价低且设计难度小的;若总体难度差异不大且造价类似的情况下,业绩数量多的优于业绩数量少的;3)技术方案:对项目理解程度高、与本项目针对性强、技术方案完善且合理性相应程度高的企业优于项目理解程度一般、技术方案基本完善且进度控制一般的企业;4)企业实力:企业财务指标良好(整体营业收入、资产负债率等)的企业优于财务指标一般得企业;以水平相同的情况下,营业收入的优劣为准;5)企业获奖:近五年(指从投标截止日往前推算五年)类似项目获得国家级奖项优于获得省级奖项;6)企业信誉:无不良行为记录企业优于有不良行为记录企业,不良行为记录较轻企业优于不良行为记录较重企业。定标会在评议时优先进行“比优”,无法比优情况下可进行“比劣”,“比劣”可参考以下等要素进行:1)有无串通投标,围标,以行贿等不正当手段谋取中标行为;2)有无挂靠,以他人名义投标,出让或者出租资格、资质证书供他人投标行为;投标人在招标人的项目中有无严重违约或重大工程质量安全问题;投标人在近一年内经查实有以上行为的不确定为中标人。\"
],
\"评标原则\": \"评标活动遵循公平、公正、科学和择优的原则。\",
\"评标\": \"评标委员会按照第三章“评标办法”规定的方法、评审因素、标准和程序对投标文件进行评审。第三章“评标办法”没有规定的方法、评审因素和标准,不作为评标依据。\",
\"评标结果公示\": \"招标人将自收到评标报告之日起3日内,在投标人须知前附表规定的媒介公示中标候选人。公示期不少于3日。投标人或者其他利害关系人对评标结果有异议的,应当在评标结果公示期间提出。招标人自收到异议之日起3日内作出答复;作出答复前,暂停招标投标活动。异议与答复应当通过“电子交易平台”在“异议与答复”菜单以书面形式进行。\",
\"履约能力的审查(如有)\": \"如果中标候选人的经营、财务状况发生较大变化或者存在违法行为,招标人认为可能影响其履约能力的,将在发出中标通知书前报行政监督部门后,召集原评标委员会按照招标文件规定的标准和方法审查确认。\"
},
\"定标\": {
\"评标结果\": \"(1)评标委员会完成评标后,应当向招标人提出书面评标报告,阐明评标委员会对各投标文件的评审和比较意见,并按照招标文件中规定的评标方法,在投标报价合格的基础上,按照最终得分(保留2位小数)由高到低推荐定标候选人。定标候选人不少于3家,不超过5家。投标人的数量少于或者等于10家时,评标委员会推荐的定标候选人数量不超过3家,经评标委员会评审,符合招标文件要求的定标候选人不足3家时,由评标委员会作出是否具备竞争性,如具备竞争性,可继续推荐定标候选人,招标人可继续定标,否则,招标人应重新招标。定标侯选人进入定标程序。(2)经评标委员会评审,符合招标文件要求的定标候选人不足3家时,由评标委员会作出是否具备竞争性,如具备竞争性,可继续推荐定标候选人,招标人可继续定标,否则,招标人应重新招标。(3)招标人应当自收到评标报告之日起3日内公示定标候选人,公示期不少于3日。对评标结果的异议的提出和处理,适用《招标投标法实施条例》第五十四条的规定。评标结果(定标候选人)公示期间,因异议或投诉导致定标候选人少于招标文件规定的数量时,招标人继续定标还是在原评标委员会评审的基础上递补定标候选人由招标人在招标文件中明确。评标结果(定标候选人)公示期间,有定标候选人因异议或投诉并查实被取消中标资格时,若有效定标候选人不少于3家的,不再递补,招标人继续定标;除评标委员会作出具备竞争性情形外,若有效定标候选人少于3家的,按投标人得分高低补足至3家。对于递补的定标候选人需在黄石市公共资源交易信息网公示不少于3日。\"
}
}
}"
# }
# def main_processing(output_folder, downloaded_file_path, file_type, unique_id):
# global global_logger
# global_logger = get_global_logger(unique_id)
# processed_data = preprocess_files(output_folder, downloaded_file_path, file_type, unique_id)
#
# with concurrent.futures.ThreadPoolExecutor() as executor:
# # 提交任务到线程池
# futures = {
# 'base_info': executor.submit(fetch_project_basic_info, processed_data['knowledge_name'],
# processed_data['truncate_files'][0], output_folder,
# processed_data['clause_path']),
# 'review_standards': executor.submit(fetch_review_standards, processed_data['truncate_files'][1],
# processed_data['truncate_files'][4],
# processed_data['knowledge_name'], processed_data['truncate0_jsonpath'],
# processed_data['clause_path']),
# 'evaluation_standards': executor.submit(fetch_evaluation_standards, processed_data['truncate_files'][1]),
# 'invalid_requirements': executor.submit(fetch_invalid_requirements, processed_data['invalid_docpath'],
# output_folder, processed_data['truncate0_jsonpath'],
# processed_data['clause_path'], processed_data['truncate_files'][4]),
# 'bidding_documents_requirements': executor.submit(fetch_bidding_documents_requirements,
# processed_data['clause_path']),
# 'opening_bid': executor.submit(fetch_bid_opening, processed_data['clause_path'])
# }
# # 逐个提交任务,每次提交间隔1秒
# for task_name, func, args in futures:
# futures[task_name] = executor.submit(func, *args)
# time.sleep(1) # 每提交一个任务后暂停1秒
#
# # 根据任务完成的顺序处理和返回结果
# for future in concurrent.futures.as_completed(futures.values()):
# key = next(key for key, val in futures.items() if val == future) # 获取完成任务的key
# try:
# result = future.result()
# modified_result = transform_json_values({key: result})
# yield f"data: {json.dumps(modified_result)}\n\n"
# except Exception as exc:
# global_logger.info(f"Error processing {key}: {exc}")
# yield f"data: {json.dumps({'error': f'Error processing {key}: {str(exc)}'})}\n\n"
#
# deleteKnowledge(processed_data['knowledge_index'])
if __name__ == "__main__":
output_folder = "C:\\Users\\Administrator\\Desktop\\招标文件\\test3"
# truncate0 = os.path.join(output_folder, "ztb_tobidders_notice_table.pdf")
# truncate1=os.path.join(output_folder,"ztb_evaluation_method.pdf")
# clause_path = convert_clause_to_json(truncate1, output_folder)
# truncate0_jsonpath = os.path.join(output_folder, "truncate_output3.json")
start_time = time.time()
file_type = 1 #1:docx 2:pdf 3:其他
input_file = "C:\\Users\\Administrator\\Desktop\\招标文件\\test3\\zbtest20.docx"
# file_path = main_processing(output_folder, input_file, file_type, "uuidzyzy11")
preprocess_files(output_folder, input_file, file_type, "unique_id")
end_time = time.time()
elapsed_time = end_time - start_time # 计算耗时
print(f"Function execution took {elapsed_time} seconds.")