From 43598850993cb87c1ff756f971c50c49086ade5c Mon Sep 17 00:00:00 2001 From: zy123 <646228430@qq.com> Date: Mon, 23 Sep 2024 17:44:34 +0800 Subject: [PATCH] =?UTF-8?q?9.23=E6=B5=8B=E8=AF=95=E5=88=86=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- flask_app/main/start_up.py | 141 ++++++++++-------- flask_app/main/ttt.py | 107 +------------ flask_app/main/招标文件解析.py | 248 ++++++++++++++++--------------- flask_app/货物标/test.py | 84 ++++++++--- flask_app/货物标/资格审查main.py | 102 +++++++++++-- 5 files changed, 373 insertions(+), 309 deletions(-) diff --git a/flask_app/main/start_up.py b/flask_app/main/start_up.py index 89a3a34..a7a41df 100644 --- a/flask_app/main/start_up.py +++ b/flask_app/main/start_up.py @@ -56,76 +56,99 @@ def create_logger(): g.logger = logger -@app.route('/upload', methods=['POST']) -def zbparse(): - logger=g.logger - file_url = validate_request() - if isinstance(file_url, tuple): # Check if the returned value is an error response - return file_url - try: - logger.info("starting parsing url:" + file_url) - final_json_path, output_folder= download_and_process_file(file_url) - if not final_json_path: - return jsonify({'error': 'File processing failed'}), 500 - response = generate_response(final_json_path) # 先获取响应内容 - # remove_directory(output_folder) # 然后删除文件夹 - return response # 最后返回获取的响应 - except Exception as e: - logger.error('Exception occurred: ' + str(e)) # 使用全局 logger 记录 - return jsonify({'error': str(e)}), 500 - - -# 流式 # @app.route('/upload', methods=['POST']) # def zbparse(): -# logger = g.logger +# logger=g.logger # file_url = validate_request() # if isinstance(file_url, tuple): # Check if the returned value is an error response # return file_url # try: # logger.info("starting parsing url:" + file_url) -# return Response(stream_with_context(process_and_stream(file_url)), content_type='text/event-stream') +# final_json_path, output_folder= download_and_process_file(file_url) +# if not final_json_path: +# return jsonify({'error': 'File processing failed'}), 500 +# response = generate_response(final_json_path) # 先获取响应内容 +# # remove_directory(output_folder) # 然后删除文件夹 +# return response # 最后返回获取的响应 # except Exception as e: -# logger.error('Exception occurred: ' + str(e)) +# logger.error('Exception occurred: ' + str(e)) # 使用全局 logger 记录 # return jsonify({'error': str(e)}), 500 -#分段返回 -# def process_and_stream(file_url): -# logger = g.logger -# unique_id = g.unique_id -# output_folder = f"flask_app/static/output/{unique_id}" # 直接使用全局 unique_id 构建路径 -# filename = "ztbfile" -# downloaded_filename = os.path.join(output_folder, filename) -# -# downloaded_filepath, file_type = download_file(file_url, downloaded_filename) -# -# if downloaded_filepath is None or file_type == 3: -# logger.error("Unsupported file type or failed to download file") -# error_response = { -# 'message': 'File processing failed', -# 'filename': None, -# 'data': json.dumps({'error': 'File processing failed'}) -# } -# yield f"data: {json.dumps(error_response)}\n\n" -# return -# -# logger.info("Local file path: " + downloaded_filepath) -# -# for data in main_processing(output_folder, downloaded_filepath, file_type, unique_id): -# response = { -# 'message': 'Processing', -# 'filename': os.path.basename(downloaded_filepath), -# 'data': data -# } -# yield f"data: {json.dumps(response)}\n\n" -# -# final_response = { -# 'message': 'File uploaded and processed successfully', -# 'filename': os.path.basename(downloaded_filepath), -# 'data': 'END' -# } -# yield f"data: {json.dumps(final_response)}\n\n" +# 流式 +@app.route('/upload', methods=['POST']) +def zbparse(): + logger = g.logger + file_url = validate_request() + if isinstance(file_url, tuple): # Check if the returned value is an error response + return file_url + try: + logger.info("starting parsing url:" + file_url) + return Response(stream_with_context(process_and_stream(file_url)), content_type='text/event-stream') + except Exception as e: + logger.error('Exception occurred: ' + str(e)) + return jsonify({'error': str(e)}), 500 + + +# 分段返回 +def process_and_stream(file_url): + logger = g.logger + unique_id = g.unique_id + output_folder = f"flask_app/static/output/{unique_id}" + filename = "ztbfile" + downloaded_filename = os.path.join(output_folder, filename) + + downloaded_filepath, file_type = download_file(file_url, downloaded_filename) + + if downloaded_filepath is None or file_type == 3: + logger.error("Unsupported file type or failed to download file") + error_response = { + 'message': 'File processing failed', + 'filename': None, + 'data': json.dumps({'error': 'File processing failed'}) + } + yield f"data: {json.dumps(error_response)}\n\n" + return + + logger.info("Local file path: " + downloaded_filepath) + + combined_data = {} + for data in main_processing(output_folder, downloaded_filepath, file_type, unique_id): + response = { + 'message': 'Processing', + 'filename': os.path.basename(downloaded_filepath), + 'data': data + } + yield f"data: {json.dumps(response)}\n\n" + + # 解析数据并添加到 combined_data + parsed_data = json.loads(data.split('data: ')[1]) + for outer_key, inner_dict in parsed_data.items(): + if isinstance(inner_dict, dict): + # 获取内层字典的第一个(也是唯一的)键值对 + inner_key, inner_value = next(iter(inner_dict.items())) + if outer_key not in combined_data: + combined_data[outer_key] = {} + combined_data[outer_key][inner_key] = inner_value + else: + # 处理 evaluation_standards 的特殊情况 + combined_data[outer_key] = inner_dict + + # 发送整合后的完整数据 + complete_response = { + 'message': 'Combined data', + 'filename': os.path.basename(downloaded_filepath), + 'data': combined_data + } + yield f"data: {json.dumps(complete_response, ensure_ascii=False)}\n\n" + + # 发送最终响应 + final_response = { + 'message': 'File uploaded and processed successfully', + 'filename': os.path.basename(downloaded_filepath), + 'data': 'END' + } + yield f"data: {json.dumps(final_response)}\n\n" def validate_request(): diff --git a/flask_app/main/ttt.py b/flask_app/main/ttt.py index 9746aba..ce5a683 100644 --- a/flask_app/main/ttt.py +++ b/flask_app/main/ttt.py @@ -1,104 +1,7 @@ -import json -import re +dict1 = {"a": {}} +dict2 = {"b": {}} -def transform_json(data): - result = {} - temp = {0: result} # 初始化根字典 +# 使用 update() 方法 +dict1.update(dict2) - # 首先,创建一个临时字典用于检查是否存在三级标题 - has_subkey = {} - for key in data.keys(): - parts = key.split('.') - if len(parts) > 2 and parts[1]: - parent_key = parts[0] + '.' + parts[1] - has_subkey[parent_key] = True - - for key, value in data.items(): - match = re.match(r'(\d+)(?:\.(\d+))?(?:\.(\d+))?', key) - if match: - levels = [int(l) for l in match.groups() if l is not None] - if (len(levels) - 1) in temp: - parent = temp[len(levels) - 1] - else: - print(f"No parent found at level {len(levels) - 1} for key '{key}'. Check the data structure.") - continue - - if len(levels) == len(match.groups()): - if isinstance(parent, list): - parent.append(value) - else: - # 对于根级别,使用完整的值作为键 - if len(levels) == 1: - parent[value] = {} - temp[len(levels)] = parent[value] - else: - parent[value.split()[0]] = value - else: - new_key = value - if '\n' in value and len(levels) == 2 and f"{levels[0]}.{levels[1]}" not in has_subkey: - new_key, new_value = value.split('\n', 1) - new_key = new_key.strip() - new_value = new_value.strip() - if isinstance(parent, list): - if len(parent) == 0 or not isinstance(parent[-1], dict): - parent.append({}) - parent[-1][new_key] = new_value - else: - parent[new_key] = new_value - else: - if isinstance(parent, list): - if len(parent) == 0 or not isinstance(parent[-1], dict): - parent.append({}) - parent = parent[-1] - if new_key not in parent: - parent[new_key] = [] - temp[len(levels)] = parent[new_key] - - def remove_single_item_lists(node): - if isinstance(node, dict): - for key in list(node.keys()): - node[key] = remove_single_item_lists(node[key]) - if isinstance(node[key], list) and not node[key]: - node[key] = "" - elif isinstance(node, list) and len(node) == 1: - return remove_single_item_lists(node[0]) - return node - - return remove_single_item_lists(result) - -# 示例 JSON 数据 -data = { - "3.1": "投标文件的组成", - "3.1.1": "投标文件应包括下列内容:(1)投标函及投标函附录;(2)法定代表人身份证明;(3)联合体协议书(如有);(4)投标保证金;(5)监理服务费投标报价表;(6)监理大纲;(7)监理机构;(8)资格审查资料;(9)投标人须知前附表规定的其他材料。", - "3.1.2": "招标公告规定不接受联合体投标的,或投标人没有组成联合体的,投标文件不包括本章第3.1.1项第(3)目所指的联合体协议书。", - "3.1.3": "投标人须知前附表规定不允许分包的,投标文件不包括本章第3.1.1项第(8)目所指的拟分包项目情况表。", - "3.6": "投标文件的编制", - "3.6.1": "投标文件应按“投标文件格式”进行编写,如有必要,可以增加附页,作为投标文件的组成部分。其中,投标函附录在满足招标文件实质性要求的基础上,可以提出比招标文件要求更有利于招标人的承诺。", - "4.": "投标", - "4.1": "投标文件的密封与标记", - "4.1.1": "投标文件的加密加密的电子投标文件应按照本章第3.6.3项要求制作并加密,未按要求加密的电子投标文件,招标人(“市电子交易平台”)将拒收并提示。", - "4.1.2": "不加密的电子投标文件的密封和标识(1)不加密的电子投标文件(U盘备份)应单独密封包装,并在封套的封口处加贴封条。(2)不加密的电子投标文件(U盘备份)封套上应写明的其他内容见投标人须知前附表。3未按本章第4.1.2项要求密封和加写标记的投标文件,招标人将拒收。", - "4.2": "投标文件的递交", - "4.2.1": "在招标公告规定的投标截止时间前,投标人可以修改或撤回已递交的投标文件。", - "4.2.2": "投标人对加密的电子投标文件进行撤回的,在“市电子交易平台”直接进行撤回操作;投标人对不加密的电子投标文件(U盘备份)进行撤回的,应以书面形式通知招标人,撤回的书面通知应加盖投标人的单位章或由法定代表人或其委托代理人签字(指亲笔签名),招标人收到书面通知后,向投标人出具签收凭证。", - "4.3": "投标文件的修改与撤回", - "4.3.1": "在送交投标文件截止期以前,投标人可以更改或撤回投标文件,并按本章第项的规定操作。", - "4.3.2": "送交投标文件截止期以后,投标文件不得更改。需对投标文件做出澄清时,必须按照本须知第23条的规定办理。", - "4.3.4": "如果在送交投标文件截止期以后且投标文件有效期内撤回投标文件,则按本须知第3.4.4款的规定不予退还其投标担保。", -} -def sort_data_keys(data): - # 将键转换成由整数构成的元组,作为排序依据 - def key_func(key): - return tuple(int(part) for part in re.split(r'\D+', key) if part) - # 对字典键进行排序 - sorted_keys = sorted(data.keys(), key=key_func) - # 创建一个新的字典,按照排序后的键添加键值对 - sorted_data = {key: data[key] for key in sorted_keys} - return sorted_data - -sorted_data=sort_data_keys(data) -# 调用 transform_json 函数 -transformed_data = transform_json(sorted_data) - -# 打印结果 -print(json.dumps(transformed_data, indent=4, ensure_ascii=False)) \ No newline at end of file +print(dict1) diff --git a/flask_app/main/招标文件解析.py b/flask_app/main/招标文件解析.py index 4154067..a906d1e 100644 --- a/flask_app/main/招标文件解析.py +++ b/flask_app/main/招标文件解析.py @@ -109,24 +109,25 @@ def fetch_qualification_review(truncate1, truncate3, knowledge_name, truncate0_j return review_standards_res -# 评分细则 -# def fetch_evaluation_standards(truncate1): # 评标办法前附表 -# logger.info("starting 商务标和技术标...") -# -# # 获取评标办法前附表的字典结果 -# evaluation_standards_res = combine_evaluation_standards(truncate1) -# # 从结果中提取"商务标"和"技术标" -# technical_standards = evaluation_standards_res.get("技术标", {}) -# commercial_standards = evaluation_standards_res.get("商务标", {}) -# logger.info("商务标和技术标 done") -# # 返回技术标和商务标 -# return technical_standards, commercial_standards - +# 评分细则 流式 def fetch_evaluation_standards(truncate1): # 评标办法前附表 - logger.info("starting商务标技术标...") + logger.info("starting 商务标和技术标...") + + # 获取评标办法前附表的字典结果 evaluation_standards_res = combine_evaluation_standards(truncate1) - logger.info("商务标技术标done") - return evaluation_standards_res + + # 从结果中提取"商务标"和"技术标" + technical_standards = evaluation_standards_res.get("技术标", {}) + commercial_standards = evaluation_standards_res.get("商务标", {}) + logger.info("商务标和技术标 done") + # 返回技术标和商务标 + return {"technical_standards": technical_standards, "commercial_standards": commercial_standards} + +# def fetch_evaluation_standards(truncate1): # 评标办法前附表 +# logger.info("starting商务标技术标...") +# evaluation_standards_res = combine_evaluation_standards(truncate1) +# logger.info("商务标技术标done") +# return evaluation_standards_res # 无效、废标项解析 @@ -156,115 +157,124 @@ def fetch_bid_opening(clause_path): 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 logger - 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) - if not processed_data: - return "" - - 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']), - 'qualification_review': executor.submit(fetch_qualification_review, 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', 'qualification_review', '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: - logger.error(f"Error processing {key}: {exc}") - # 合并 JSON 结果 - combined_final_result = combine_json_results(comprehensive_responses) - includes = ["基础信息", "资格审查", "商务标", "技术标", "无效标与废标项", "投标文件要求", "开评定标流程"] - result = post_processing(combined_final_result, includes) - modified_json = transform_json_values(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) - logger.info("final_result.json has been saved") - deleteKnowledge(processed_data['knowledge_index']) - return final_result_path +# def main_processing(output_folder, downloaded_file_path, file_type, unique_id): # file_type=1->docx file_type=2->pdf +# global logger +# 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) +# if not processed_data: +# return "" +# +# 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']), +# 'qualification_review': executor.submit(fetch_qualification_review, 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', 'qualification_review', '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: +# logger.error(f"Error processing {key}: {exc}") +# # 合并 JSON 结果 +# combined_final_result = combine_json_results(comprehensive_responses) +# includes = ["基础信息", "资格审查", "商务标", "技术标", "无效标与废标项", "投标文件要求", "开评定标流程"] +# result = post_processing(combined_final_result, includes) +# modified_json = transform_json_values(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) +# 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) -# if not processed_data: -# yield "data: {}\n\n" # 如果处理数据失败,返回空 -# -# 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']), -# 'qualification_review': executor.submit(fetch_qualification_review, processed_data['truncate_files'][1], -# processed_data['truncate_files'][4], -# 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['truncate_files'][1]), -# 'technical_standards': executor.submit(lambda: fetch_evaluation_standards(processed_data['truncate_files'][1])[0]), # 技术标 -# 'commercial_standards': executor.submit(lambda: fetch_evaluation_standards(processed_data['truncate_files'][1])[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, future in futures.items(): -# futures[task_name] = executor.submit(future) -# time.sleep(1) # 任务提交后暂停1秒 -# -# # 按照任务完成的顺序返回结果 -# 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() -# # 对结果进行JSON转换和修改 -# modified_result = transform_json_values({key: result}) -# # 以流式形式返回结果 -# yield f"data: {json.dumps(modified_result, ensure_ascii=False)}\n\n" -# except Exception as exc: -# global_logger.error(f"Error processing {key}: {exc}") -# # 返回错误信息 -# yield f"data: {json.dumps({'error': f'Error processing {key}: {str(exc)}'})}\n\n" -# -# # 删除知识索引 -# deleteKnowledge(processed_data['knowledge_index']) +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) + if not processed_data: + yield "data: {}\n\n" # 如果处理数据失败,返回空 + + 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']), + 'qualification_review': executor.submit(fetch_qualification_review, processed_data['truncate_files'][1], + processed_data['truncate_files'][4], + 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['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, future in futures.items(): + futures[task_name] = executor.submit(future) + time.sleep(1) # 任务提交后暂停1秒 + + # 按照任务完成的顺序返回结果 + 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() + + # 处理 evaluation_standards 返回的技术标和商务标分别作为两个结果 + if key == 'evaluation_standards': + technical_standards = result["technical_standards"] + commercial_standards = result["commercial_standards"] + + # 分别发送技术标和商务标 + modified_technical_result = transform_json_values({"technical_standards": technical_standards}) + modified_commercial_result = transform_json_values({"commercial_standards": commercial_standards}) + + yield f"data: {json.dumps(modified_technical_result, ensure_ascii=False)}\n\n" + yield f"data: {json.dumps(modified_commercial_result, ensure_ascii=False)}\n\n" + else: + # 对非 evaluation_standards 的结果进行 JSON 转换和修改 + modified_result = transform_json_values({key: result}) + yield f"data: {json.dumps(modified_result, ensure_ascii=False)}\n\n" + except Exception as exc: + global_logger.error(f"Error processing {key}: {exc}") + # 返回错误信息 + yield f"data: {json.dumps({'error': f'Error processing {key}: {str(exc)}'})}\n\n" + + # 删除知识索引 + deleteKnowledge(processed_data['knowledge_index']) #TODO:流式输出要改商务标技术标整合 if __name__ == "__main__": diff --git a/flask_app/货物标/test.py b/flask_app/货物标/test.py index 4e7c952..2d66582 100644 --- a/flask_app/货物标/test.py +++ b/flask_app/货物标/test.py @@ -83,26 +83,72 @@ def process_dict(data): # 测试代码 input_data = { - "符合性审查": { - "说明": "评标委员会应当对符合资格的投标人的投标文件进行符合性审查,以确定其是否满足招标文件的实质性要求。", - "审查标准": [ - { - "序号": 9, - "内容": "未按要求提供加盖公章及签字(签章)的;" + "资格性审查": { + "资格证明文件审查": [ + "应具备《政府采购法》第二十二条第一款规定的条件,提供下列材料:", + { + "法人或者其他组织的营业执照等证明文件,自然人的身份证明(投标人根据自身情况提供对应的证明材料)": { + "法人": { + "企业法人(包括合伙企业)": "提供工商部门注册的有效“企业法人营业执照” 或“营业执照”;", + "事业单位法人": "提供有效的“事业单位法人证书”。" + }, + "其他组织": { + "非企业专业服务机构": "提供执业许可证等证明文件;", + "个体工商户": "提供有效的“个体工商户营业执照”。" + }, + "自然人": "提供有效的自然人身份证明。(仅限中国公民)" + } + }, + { + "财务状况报告,依法缴纳税收和社会保障资金的相关材料(投标人根据自身情况提供对应的证明材料)": { + "法人": { + "财务状况报告": { + "(1)": "提供经第三方审计的财务报告(完整的财务报告,包括“四表一注”, 即资产负债表、利润表、现金流量表、所有者权益变动表及其附注),或其基 本开户银行出具的资信证明;", + "(2)": "部分其他组织和自然人:没有经第三方审计的财务报告的,可以提供银行出具 的资信证明;", + "(3)": "投标人没有经第三方审计的财务报告和资信证明时,也可以提供财政部门认可 的政府采购专业担保机构出具的投标担保函。" }, - { - "序号": 1, - "内容": "a39" - }, - { - "序号": 2, - "内容": "依据财库[2019]9号文的规定,招标文件采购清单中为“节能产品”的货物,未提供国家确定的认证机构出具的节能产品认证证书的;" - }, - - ] - } + "依法缴纳税收和社会保障资金的证明材料": { + "(1)": { + "税务登记证(国税、地税或多证合一);": "", + "参加政府采购活动前一段时间内缴纳增值税、营业税和企业所得税的凭据;": "", + "社会保险登记证(或多证合一);": "", + "参加政府采购活动前一段时间内缴纳社会保险的凭据(专用收据或社会保险 缴纳清单)。": "" + }, + "(2)": { + "参加政府采购活动前一段时间内缴纳税收的凭据;": "", + "参加政府采购活动前一段时间内缴纳社会保险的凭据(专用收据或社会保险 缴纳清单)。": "" + }, + "(3)": "依法免税或不需要缴纳社会保险资金的投标人 提供其依法免税或不需要缴纳社会保险资金的相关证明文件。" + } + } + } + }, + { + "具备履行合同所必需的设备和专业技术能力的证明材料": { + "提供具备足够数量的设施设备的证明材料;": "", + "提供具备足够数量的技术人员的证明材料。": "" + } + }, + { + "参加政府采购活动前 3年内在经营活动中没有重大违法记录的书面声明": { + "应严格按照附件格式提交“参加政府采购活动前三年内在经营活动中没有重大 违法记录的书面声明函”;": "", + "政府采购法第二十二条第一款第五项所称重大违法记录,是指供应商因违法经 营受到刑事处罚或者责令停产停业、吊销许可证或者执照、较大数额罚款等行政处罚;": "", + "按照财政部《关于规范政府采购行政处罚有关问题的通知》的规定,各级人民 政府财政部门依法对参加政府采购活动的供应商作出的禁止参加政府采购活动等行政 处罚决定在全国范围内生效。": "" + } + }, + "具备法律、行政法规规定的其他条件的证明材料:", + { + "1": "国家对生产和销售相关产品或提供相关服务有专门法律、行政法规规定的,则 必须提供取得国家有关主管部门行政许可的证明材料。", + "2": "未被列入“信用中国”网站(www.creditchina.gov.cn)信用服务栏失信被执行人、重 大税收违法案件当事人名单、政府采购严重违法失信行为记录名单和“中国政 府采购”网站(www.ccgp.gov.cn)政府采购严重违法失信行为记录名单,并提供 网页截图以证明;", + "3": "招标文件第一章“投标人资格要求”中有特殊要求的,投标人应提供其符合特 殊要求的证明材料或者情况说明;", + "4": "不符合联合体投标相关规定和要求的;", + "5": "投标人认为需提供的其它相关资格证明材料;", + "6": "资格证明文件正本应为清晰彩色影印件且加盖单位公章。" + } + ] + } } pred=preprocess_dict(input_data) print(json.dumps(pred, ensure_ascii=False, indent=4)) -# processed_data = process_dict(pred) -# print(json.dumps(processed_data, ensure_ascii=False, indent=4)) \ No newline at end of file +processed_data = process_dict(pred) +print(json.dumps(processed_data, ensure_ascii=False, indent=4)) \ No newline at end of file diff --git a/flask_app/货物标/资格审查main.py b/flask_app/货物标/资格审查main.py index d0456cd..5366e59 100644 --- a/flask_app/货物标/资格审查main.py +++ b/flask_app/货物标/资格审查main.py @@ -1,24 +1,106 @@ +# -*- encoding:utf-8 -*- import json +import re from flask_app.main.基础信息整合 import combine_basic_info from flask_app.main.通义千问long import qianwen_long,upload_file from flask_app.main.多线程提问 import multi_threading -from flask_app.main.json_utils import combine_json_results +from flask_app.main.json_utils import combine_json_results,clean_json_string +#这个字典可能有嵌套,你需要遍历里面的键名,对键名作判断,而不是键值,具体是这样的:如果处于同一层级的键的数量>1并且键名全由数字或点号组成。那么就将这些序号键名全部删除,重新组织成一个字典格式的数据,你可以考虑用字符串列表来保持部分平级的数据 +#对于同级的键,如果数量>1且键名都统一,那么将键名去掉,用列表保持它们的键值 +#对于同一个字典中,可能存在若干键值对,若它们的键值都是""或者"/" 你就将它们的键值删去,它们的键名用字符串列表保存 + +def is_numeric_key(key): + # 这个正则表达式匹配由数字、点、括号中的数字或单个字母(小写或大写)组成的字符串, + # 字母后跟数字,或数字后跟字母,单个字母后跟点,但不能是字母-数字-字母的组合 + pattern = r'^[\d.]+$|^\(\d+\)$|^(\d+)$|^[a-zA-Z]$|^[a-zA-Z]\d+$|^\d+[a-zA-Z]$|^[a-zA-Z]\.$' + return re.match(pattern, key) is not None +#TODO:如果键值中存在数字就不行 +#zbtest20也有问题 +def contains_number_or_index(key, value): + # 判断值是否是数字或数字字符串 + is_number = isinstance(value, (int, float)) or (isinstance(value, str) and value.isdigit()) + # 判断键是否包含 "序号" + contains_index = '序号' in key + # 判断值中是否包含数字 + contains_digit = isinstance(value, str) and re.search(r'\d+', value) + # 判断值中是否包含中文字符 + contains_chinese = isinstance(value, str) and re.search(r'[\u4e00-\u9fff]', value) + # 如果值中包含数字但也有中文字符,则保留(返回 False) + if contains_digit and contains_chinese: + return False + # 如果值是数字或包含数字,且不包含中文字符,或者键包含 "序号",返回 True + return is_number or contains_index or contains_digit + +def preprocess_dict(data): + if isinstance(data, dict): + if len(data) > 1: + # 检查是否所有值都是 "" 或 "/" + if all(v == "" or v == "/" for v in data.values()): + return list(data.keys()) + else: + processed = {} + for k, v in data.items(): + if not contains_number_or_index(k, v): + processed_v = preprocess_dict(v) + if processed_v != "": # 只添加非空值 + processed[k] = processed_v + return processed + else: + return {k: preprocess_dict(v) for k, v in data.items()} + elif isinstance(data, list): + return [preprocess_dict(item) for item in data] + else: + return data +def process_dict(data): + if not isinstance(data, dict): + return data + + result = {} + numeric_keys = [] + non_numeric_keys = {} + + for key, value in data.items(): + if is_numeric_key(key): + numeric_keys.append((key, value)) + else: + non_numeric_keys[key] = value + + # 处理数字键,不再要求数量>1 + if numeric_keys: + result['items'] = [process_dict(item[1]) for item in sorted(numeric_keys)] + + # 处理非数字键 + for key, value in non_numeric_keys.items(): + if isinstance(value, list) and len(value) > 1 and all(isinstance(item, dict) and len(item) == 1 for item in value): + common_key = next(iter(value[0].keys())) + if all(common_key in item and len(item) == 1 for item in value): + result[key] = [process_dict(item[common_key]) for item in value] + else: + result[key] = [process_dict(item) for item in value] + else: + result[key] = process_dict(value) + + # 如果结果只包含'items'键,直接返回其值 + if len(result) == 1 and 'items' in result: + return result['items'] + + return result def qualification_review(truncate_file): file_id=upload_file(truncate_file) - user_query1="该招标文件中规定的资格性审查标准是怎样的?请以json格式给出,外层为'资格性审查',对于原文中的序号,你仅需要捕获它们之间的层级关系并根据序号后的内容生成嵌套键值对,若多个内容位于同一层级,你应用字符串列表作为键值保存这些内容,你的回答需删去这些序号,但其余内容要与原文一致,不可擅自总结删减,也不要回答符合性审查的内容。" - user_query2="该招标文件中规定的符合性审查标准是怎样的?请以json格式给出,外层为'符合性审查',你的回答要与原文一致,不可擅自总结删减,也不要回答资格性审查的内容。" - user_query=[] - user_query.append(user_query1) - user_query.append(user_query2) + user_query=["该招标文件中规定的资格性审查标准是怎样的?请以json格式给出,外层为'资格性审查',对于原文中的序号,你仅需要捕获它们之间的层级关系并根据序号后的内容生成嵌套键值对,若多个内容位于同一层级,你应用字符串列表作为键值保存这些内容,你的回答需删去这些序号,但其余内容要与原文一致,不可擅自总结删减,也不要回答符合性审查的内容。","该招标文件中规定的符合性审查标准是怎样的?请以json格式给出,外层为'符合性审查',你的回答要与原文一致,不可擅自总结删减,也不要回答资格性审查的内容。"] results=multi_threading(user_query,"",file_id,2) - result_list=[] + combined_res = {} for question, response in results: - result_list.append(response) - combined_res = combine_json_results(result_list) # 整合基础信息核心代码 + print(response) + cleaned_data = clean_json_string(response) + processed1 = preprocess_dict(cleaned_data) + processed2 = process_dict(processed1) + combined_res.update(processed2) + # 整合基础信息核心代码 return combined_res if __name__ == "__main__": - truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output3\\2-招标文件(2020年广水市中小学教师办公电脑系统及多媒体“班班通”设备采购安装项目)_qualification1.pdf" + truncate_file="C:\\Users\\Administrator\\Desktop\\货物标\\output3\\2-招标文件(广水市教育局封闭管理)_qualification1.pdf" res=qualification_review(truncate_file) print(json.dumps(res,ensure_ascii=False, indent=4)) \ No newline at end of file