zbparse/flask_app/货物标/商务服务其他要求提取.py

272 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- encoding:utf-8 -*-
import json
import re
from PyPDF2 import PdfReader
from flask_app.general.doubao import read_txt_to_string
from flask_app.general.json_utils import combine_json_results,clean_json_string
from flask_app.general.通义千问long import upload_file,qianwen_long_stream
from flask_app.货物标.截取pdf货物标版 import extract_common_header, clean_page_content
from flask_app.general.doubao import doubao_model
#正则表达式判断原文中是否有商务、服务、其他要求
def find_exists(truncate_file, required_keys):
if not truncate_file:
return ["技术要求", "商务要求", "服务要求", "其他要求"]
common_header = extract_common_header(truncate_file) # 假设该函数已定义
pdf_document = PdfReader(truncate_file)
# 定义正则模式
begin_pattern = re.compile(
r'(?:^第[一二三四五六七八九十百千]+(?:章|部分)\s*' # 匹配“第X章”或“第X部分”
r'[\u4e00-\u9fff、()]*?' # 匹配允许的字符
r'(?:(?:服务|项目|商务|技术)[\u4e00-\u9fff、()]*?要求|' # 匹配“服务”、“项目”、“商务”或“技术”后跟“要求”
r'(?:采购|需求)[\u4e00-\u9fff、()]*?)' # 匹配“采购”或“需求”
r'\s*$|' # 匹配行尾
r'^第[一二三四五六七八九十百千]+(?:章|部分)(?!.*说明).*?' # 匹配“第X章”后带“采购内容”等排除“说明”
r'(?:采购内容|采购要求|需求).*|' # 匹配“采购内容”或“采购要求”关键词
r'^[一二三四五六七八九十百千]+、\s*采购清单)' # 匹配“一、采购清单”
r'\s*$', # 匹配行尾
re.MULTILINE
)
end_pattern = re.compile(
r'第[一二三四五六七八九1-9]+(?:章|部分)\s*[\u4e00-\u9fff、()]+\s*$', re.MULTILINE)
# 遍历所有页面,拼接全文
text = ""
for page in pdf_document.pages:
page_text = page.extract_text() or ""
cleaned_text = clean_page_content(page_text, common_header)
text += cleaned_text + "\n"
# 匹配起始位置
start_match = re.search(begin_pattern, text)
if not start_match:
print("未找到开始模式")
return []
start_index = start_match.end()
# 匹配结束位置
end_match = re.search(end_pattern, text[start_index:])
if end_match:
end_index = start_index + end_match.start()
relevant_text = text[start_index:end_index]
else:
relevant_text = text[start_index:]
# 保留换行,避免结构丢失
relevant_text = re.sub(r'\s+', ' ', relevant_text)
# print(f"提取的内容范围:\n{relevant_text}")
# 匹配所需的要求
matched_requirements = []
punctuation = r"[,。?!、;:,.?!]*"
for req in required_keys:
# required_keys 中的元素本身已包含 \s*,直接作为正则模式
if re.search(req, relevant_text):
if req == "\s*务\s*要\s*求":
# 提取所有包含"服务要求"的行
lines = [line for line in relevant_text.split('\n') if re.search(req, line)]
# 检查是否存在'技术'紧跟在'服务要求'前面(中间只有标点,标点是可选的)
pattern = "\s*术" + punctuation + req
if any(re.search(pattern, line) for line in lines):
# 如果存在'技术'紧跟'服务要求',添加"技术、服务要求"
if "\s*术\s*、\s*服\s*务\s*要\s*求" not in matched_requirements:
matched_requirements.append("\s*术\s*、\s*服\s*务\s*要\s*求")
else:
# 如果不存在'技术'紧跟'服务要求',正常添加"服务要求"
matched_requirements.append(req)
else:
matched_requirements.append(req)
# 去除 \s*,仅返回原始关键词
clean_requirements = [re.sub(r'\\s\*', '', req) for req in matched_requirements]
# 判断互斥关系:如果有"技术、服务要求",删除"技术要求"和"服务要求"
if "技术、服务要求" in clean_requirements:
clean_requirements = [req for req in clean_requirements if req not in ["技术要求", "服务要求"]]
return clean_requirements
def generate_queries(truncate_file, required_keys):
key_list = find_exists(truncate_file, required_keys)
queries = []
user_query_template = "这是一份货物标中采购要求部分的内容,请告诉我\"{}\"是什么请以json格式返回结果外层键名是\"{}\",内层键值对中的键名是原文中的标题或者是你对相关子要求的总结,而键值需要完全与原文保持一致,不可擅自总结删减,注意你无需回答采购清单中具体设备的技术参数要求,仅需从正文部分开始提取,"
for key in key_list:
query_base = user_query_template.format(key, key)
other_keys = [k for k in key_list if k != key]
if other_keys:
query_base += "也不需要回答\"{}\"中的内容,".format("\"\"".join(other_keys))
query_base += "若相关要求不存在,在键值中填'未知'"
queries.append(query_base)
# print(query_base)
return queries
def generate_user_query_template(required_keys,processed_filepath):
import textwrap
import json
# 定义所有可能的键
all_possible_keys = ["技术要求", "服务要求", "商务要求", "其他要求", "技术、服务要求","总体要求","进度要求","培训要求"]
# 定义每个键对应的示例内容
example_content1 = {
"技术要求": ["相关技术要求1", "相关技术要求2"],
"服务要求": ["服务要求1", "服务要求2"],
"商务要求": {
"子因素名1": ["商务要求1"],
"子因素名2": ["商务要求2"]
},
"其他要求": {
"子因素名1": ["关于项目采购的其他要求1...", "关于项目采购的其他要求2..."],
"子因素名2": ["关于项目采购的其他要求3...", "关于项目采购的其他要求4..."]
},
"技术、服务要求": ["相关技术、服务要求内容1", "相关技术、服务要求内容2"]
}
example_content2 = {
"技术要求": {
"子因素名1": ["相关技术要求1", "相关技术要求2"],
"子因素名2": ["相关技术要求3"]
},
"服务要求": {
"子因素名1": ["相关服务要求1", "相关服务要求2"],
"子因素名2": ["相关服务要求3", "相关服务要求4"]
},
"商务要求": ["商务要求1", "商务要求2"],
"其他要求": ["关于项目采购的其他要求1..."],
"技术、服务要求": {
"子因素名1": ["相关技术、服务要求内容1"],
"子因素名2": ["相关技术、服务要求内容2", "相关技术、服务要求内容3"]
}
}
# 将 required_keys 转换为集合以便于操作
keys = set(required_keys)
# 处理互斥关系:如果 "技术要求" 和 "服务要求" 同时存在,则移除 "技术、服务要求"
if "技术要求" in keys and "服务要求" in keys:
keys.discard("技术、服务要求")
# 如果 "技术、服务要求" 存在,则移除 "技术要求" 和 "服务要求"
elif "技术、服务要求" in keys:
keys.discard("技术要求")
keys.discard("服务要求")
# 确保 keys 中只包含允许的键
keys = keys.intersection(all_possible_keys)
# 按照预定义的顺序排序键,以保持一致性
sorted_keys = [key for key in all_possible_keys if key in keys]
# 如果没有任何键被选中,返回一个默认的模板或抛出异常
if not sorted_keys:
raise ValueError("required_keys 中没有有效的键。")
# 生成提示部分,根据 sorted_keys 动态构建
keys_str = ''.join(sorted_keys)
outer_keys_str = ', '.join([f"'{key}'" for key in sorted_keys])
# 使用三引号定义多行字符串,便于编辑和维护
prompt_instruction = textwrap.dedent(f"""请你根据该货物类招标文件中的采购要求部分内容(技术、服务及商务要求部分内容),请告诉我该项目采购的{keys_str}分别是什么请以json格式返回结果默认情况下外层键名是{outer_keys_str},键值为字符串列表,每个字符串表示具体的一条要求,请按原文内容回答,保留三角▲、五角星★和序号(若有),不要擅自增添内容。
要求与指南:
1. 默认情况无需嵌套,键值为字符串列表;若存在嵌套结构,嵌套键名是原文中该要求下相应子标题,最多一层嵌套。
2. JSON 的结构要求:
- 外层键名为 {outer_keys_str} 中的各项。
- 每个外层键对应的值可以是:
a. 一个对象(字典),其键为子因素名,值为字符串列表。
b. 一个字符串列表,表示具体的一条条要求。若只有一条要求,也用字符串列表表示。
- 最多只允许一层嵌套。
3. 请优先定位正文部分的大标题'xx要求',在其之后提取'xx要求'相关内容,
4. 若章节开头位置或者采购清单中除了需要采购的货物、数量、单位之外,还有带三角▲或五角星★的描述内容(如工期要求、质保要求等商务要求),请将该部分内容提取出来,添加在键名为'商务要求'的字典的键值部分,注意请不要返回Markdown语法必要时使用冒号':'将相关信息拼接在一起。
5. 在提取技术要求或技术、服务要求时(若有),你无需从采购清单或表格中提取货物名以及参数要求,你仅需定位到原文中大标题'技术要求''技术、服务要求'部分提取正文内容,若内容全在表格中,键值为空列表[]。
6. 若无相关要求,键值为[]
""" )
# 过滤 example_content1 和 example_content2 以仅包含 sorted_keys
def filter_content(example_content, keys):
return {k: v for k, v in example_content.items() if k in keys}
filtered_example_content1 = filter_content(example_content1, sorted_keys)
filtered_example_content2 = filter_content(example_content2, sorted_keys)
# 将过滤后的示例转换为格式化的 JSON 字符串
json_example1_str = json.dumps(filtered_example_content1, indent=4, ensure_ascii=False)
json_example2_str = json.dumps(filtered_example_content2, indent=4, ensure_ascii=False)
# 从文件中读取内容
# full_text = read_txt_to_string(processed_filepath)
# 完整的用户查询模板,包含两份示例输出
user_query_template = f"""
{prompt_instruction}
以下为示例输出,仅供格式参考:
示例 1
{json_example1_str}
示例 2
{json_example2_str}
"""
# 文本内容:{full_text}
return user_query_template
def merge_requirements(input_dict):
# 初始化一个临时字典,用于存储标准化后的键
temp_dict = {}
# 初始化最终字典,只包含指定的四个键
final_keys = ['技术要求', '商务要求', '服务要求', '其他要求']
final_dict = {key: "" for key in final_keys}
# 如果输入字典中有'其他要求',保留其内容
if '其他要求' in temp_dict and temp_dict['其他要求'].strip():
final_dict['其他要求'] = temp_dict['其他要求'].strip()
# 处理'技术要求', '商务要求', '服务要求'
for key in ['技术要求', '商务要求', '服务要求']:
if key in temp_dict:
final_dict[key] = temp_dict[key].strip()
# 收集需要合并到'其他要求'的内容
merge_keys = ['总体要求', '进度要求', '培训要求']
merged_contents = []
for key in merge_keys:
if key in temp_dict and temp_dict[key].strip():
merged_contents.append(temp_dict[key].strip())
# 如果有需要合并的内容
if merged_contents:
merged_text = " ".join(merged_contents)
if final_dict['其他要求']:
final_dict['其他要求'] += " " + merged_text
else:
final_dict['其他要求'] = merged_text
# 移除多余的空格
for key in final_dict:
final_dict[key] = final_dict[key].strip()
return final_dict
def get_business_requirements(procurement_path,processed_filepath):
file_id=upload_file(procurement_path)
required_keys = ["\s*术\s*要\s*求","\s*务\s*要\s*求", "\s*务\s*要\s*求", "\s*他\s*要\s*求","\s*体\s*要\s*求","\s*度\s*要\s*求","\s*训\s*要\s*求"]
contained_keys=find_exists(procurement_path,required_keys)
print(contained_keys)
# queries = generate_queries(truncate_file, contained_keys)
user_query=generate_user_query_template(contained_keys,processed_filepath)
# print(user_query)
model_res=qianwen_long_stream(file_id,user_query)
# model_res=doubao_model(user_query)
# Combine and fill missing keys with default values
final_res = clean_json_string(model_res)
# final_res.update({key: final_res.get(key, "") for key in required_keys})
return final_res
#TODO:改为先判断,再摘取
if __name__ == "__main__":
# truncate_file = "C:\\Users\\Administrator\\Desktop\\fsdownload\\e4be098d-b378-4126-9c32-a742b237b3b1\\ztbfile_procurement.docx"
truncate_file=r"C:\Users\Administrator\Desktop\fsdownload\5901b181-b55f-4107-9f30-c85d607b1fa0\ztbfile_procurement.pdf"
processed_filepath=""
# file_id = upload_file(truncate_file)
res=get_business_requirements(truncate_file,"")
print(json.dumps(res, ensure_ascii=False, indent=4))