kgrag/kg_build/extract_text.py
2026-06-30 13:35:52 +08:00

339 lines
14 KiB
Python
Raw Permalink 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.

import json
import fitz # PyMuPDF
import os
from collections import Counter
from kg_build.Prompt import Prompt_guzhang
from openai import OpenAI
def get_compose_chunk_bbox_and_removed_data(data):
"""
返回:
- 符合条件的故障表分组(每组 [前前, 前, 当前]
- 从原始 data 中移除了所有相关记录后的新 data 列表
"""
title = "组成"
# 支持多个 label 模式(可扩展)
labels = [
'<table><tr><td rowspan=1 colspan=1>序号</td><td rowspan=1 colspan=1>组成部分</td><td rowspan=1 colspan=1>功能</td><td rowspan=1 colspan=1>数量</td></tr><tr><td rowspan=1 colspan=1>1</td>',
# 可在此添加更多变体,例如:
# '<table><tr><th>序号</th><th>故障现象</th><th>故障原因</th><th>维修项目</th></tr>',
# '<table><tr><td>序号</td><td>故障现象</td><td>故障原因</td><td>维修项目</td></tr>',
]
# 构建 id -> record 映射
record_map = {item["id"]: item for item in data}
results = []
ids_to_remove = set()
for ins in data:
if ins.get("type") == "table":
# 检查 caption 是否包含 "故障表"
caption_has_title = any(title in caption for caption in ins.get("table_caption", []))
# 检查 body 是否匹配任意一个 label
body = ins.get("table_body", "")
body_matches_any_label = any(label in body for label in labels)
if caption_has_title and body_matches_any_label:
current_id = ins["id"]
prev_ids = [current_id - 2, current_id - 1]
# 收集要移除的 ID前两条 + 当前)
group_ids = [pid for pid in prev_ids if pid >= 1] + [current_id]
ids_to_remove.update(group_ids)
# 构建分组
group = [record_map.get(pid) for pid in prev_ids if pid >= 1] + [ins]
results.append(group)
# 过滤原始 data
filtered_data = [item for item in data if item["id"] not in ids_to_remove]
return results, filtered_data
def get_tujiecompose_chunk_bbox_and_removed_data(data):
"""
返回:
- 符合条件的故障表分组(每组 [前前, 前, 当前]
- 从原始 data 中移除了所有相关记录后的新 data 列表
"""
title = "组成"
# 支持多个 label 模式(可扩展)
labels = [
'<table><tr><td colspan=\"1\" rowspan=\"1\">组成编码</td><td colspan=\"1\" rowspan=\"1\">名称</td><td colspan=\"1\" rowspan=\"1\">是否为关重件</td><td colspan=\"1\" rowspan=\"1\">是否为寿命件</td>',
'<table><tr><td rowspan=1 colspan=1>序号</td><td rowspan=1 colspan=1>组成部分</td><td rowspan=1 colspan=1>功能</td><td rowspan=1 colspan=1>数量</td></tr><tr><td rowspan=1 colspan=1>1</td>'
# 可在此添加更多变体,例如:
# '<table><tr><th>序号</th><th>故障现象</th><th>故障原因</th><th>维修项目</th></tr>',
# '<table><tr><td>序号</td><td>故障现象</td><td>故障原因</td><td>维修项目</td></tr>',
]
# 构建 id -> record 映射
record_map = {item["id"]: item for item in data}
results = []
ids_to_remove = set()
for ins in data:
if ins.get("type") == "table":
# 检查 caption 是否包含 "故障表"
caption_has_title = any(title in caption for caption in ins.get("table_caption", []))
# 检查 body 是否匹配任意一个 label
body = ins.get("table_body", "")
body_matches_any_label = any(label in body for label in labels)
if caption_has_title and body_matches_any_label:
current_id = ins["id"]
prev_ids = [current_id - 2, current_id - 1]
# 收集要移除的 ID前两条 + 当前)
group_ids = [pid for pid in prev_ids if pid >= 1] + [current_id]
ids_to_remove.update(group_ids)
# 构建分组
group = [record_map.get(pid) for pid in prev_ids if pid >= 1] + [ins]
results.append(group)
# 过滤原始 data
filtered_data = [item for item in data if item["id"] not in ids_to_remove]
return results, filtered_data
def get_guzhang_chunk_bbox_and_removed_data(data):
title = "故障表"
label = "<table><tr><td colspan=\"1\" rowspan=\"1\">序号</td><td colspan=\"1\" rowspan=\"1\">故障现象</td><td colspan=\"1\" rowspan=\"1\">故障原因</td><td colspan=\"1\" rowspan=\"1\">维修项目</td></tr>"
record_map = {item["id"]: item for item in data}
results = []
ids_to_remove = set()
for ins in data:
if ins.get("type") == "table":
caption_has_title = any(title in caption for caption in ins.get("table_caption", []))
body_has_label = label in ins.get("table_body", "")
if caption_has_title and body_has_label:
current_id = ins["id"]
prev_ids = [current_id - 2, current_id - 1]
group_ids = [pid for pid in prev_ids if pid >= 1] + [current_id]
ids_to_remove.update(group_ids)
group = [record_map.get(pid) for pid in prev_ids if pid >= 1] + [ins]
results.append(group)
filtered_data = [item for item in data if item["id"] not in ids_to_remove]
return results, filtered_data
START_MARKERS = [
# 标准格式(带 rowspan/colspan
"<table><tr><td rowspan=1 colspan=1>维修项目编号</td><td rowspan=1 colspan=1>组成编码</td><td rowspan=1 colspan=1>名称</td><td rowspan=1 colspan=1>维修项目</td><td rowspan=1 colspan=1>维修间隔期</td>",
# 可在此添加其他格式,例如:
# "<table><tr><td>维修项目编号</td><td>组成编码</td><td>名称</td><td>维修项目</td><td>维修间隔期</td>",
]
def extract_maintenance_groups_and_remaining(records, markers=START_MARKERS, max_following_for_last=None):
"""
普通起始标记:组 = [start, ..., next_start - 1]
最后一个起始标记:组 = [start, ..., start + max_following_for_last] (若指定)
"""
groups = []
current_group = None
used_ids = set()
for record in records:
body = record.get("table_body", "")
is_start = (record.get("type") == "table" and
any(marker in body for marker in markers))
if is_start:
if current_group is not None:
groups.append(current_group)
used_ids.update(r["id"] for r in current_group)
current_group = [record]
else:
if current_group is not None:
# 如果指定了 max_following_for_last且这是最后一个组暂时不知道是不是最后一个
# 我们无法在遍历时知道“这是不是最后一个起始标记”
# 所以更好的办法是:先按原逻辑分组,再处理最后一个组
current_group.append(record)
if current_group is not None:
groups.append(current_group)
used_ids.update(r["id"] for r in current_group)
# —————— 关键:后处理最后一个组 ——————
if groups and max_following_for_last is not None:
last_group = groups[-1]
if len(last_group) > max_following_for_last + 1: # +1 包含起始记录
truncated = last_group[:max_following_for_last + 1]
# 更新 used_ids移除被截掉的 ID
removed_ids = {r["id"] for r in last_group[max_following_for_last + 1:]}
used_ids -= removed_ids
groups[-1] = truncated
remaining_records = [r for r in records if r["id"] not in used_ids]
return groups, remaining_records
START_MARKERS_ope = [
'<table><tr><td>项目编号</td><td>操作项目</td></tr><tr>',
'<table><tr><td rowspan=1 colspan=1>项目编号</td><td rowspan=1 colspan=1>操作项目</td></tr><tr>',
'<table><tr><td colspan="1" rowspan="1">项目编号</td><td colspan="1" rowspan="1">操作项目</td></tr><tr>'
]
def extract_operation_groups_and_remaining(records, markers=START_MARKERS_ope, max_following_for_last=None):
"""
普通起始标记:组 = [start, ..., next_start - 1]
最后一个起始标记:组 = [start, ..., start + max_following_for_last] (若指定)
"""
groups = []
current_group = None
used_ids = set()
for record in records:
body = record.get("table_body", "")
is_start = (record.get("type") == "table" and
any(marker in body for marker in markers))
if is_start:
if current_group is not None:
groups.append(current_group)
used_ids.update(r["id"] for r in current_group)
current_group = [record]
else:
if current_group is not None:
# 如果指定了 max_following_for_last且这是最后一个组暂时不知道是不是最后一个
# 我们无法在遍历时知道“这是不是最后一个起始标记”
# 所以更好的办法是:先按原逻辑分组,再处理最后一个组
current_group.append(record)
if current_group is not None:
groups.append(current_group)
used_ids.update(r["id"] for r in current_group)
# —————— 关键:后处理最后一个组 ——————
if groups and max_following_for_last is not None:
last_group = groups[-1]
if len(last_group) > max_following_for_last + 1: # +1 包含起始记录
truncated = last_group[:max_following_for_last + 1]
# 更新 used_ids移除被截掉的 ID
removed_ids = {r["id"] for r in last_group[max_following_for_last + 1:]}
used_ids -= removed_ids
groups[-1] = truncated
remaining_records = [r for r in records if r["id"] not in used_ids]
return groups, remaining_records
# def get_chunk_filter(data,max_length=7000):
# for idx, item in enumerate(data, start=1):
# item["id"] = idx
# groups, remaining_records_repair = extract_maintenance_groups_and_remaining(data)
# groups, remaining_records_operation = extract_operation_groups_and_remaining(remaining_records_repair)
# groups, remaining_records_compose = get_compose_chunk_bbox_and_removed_data(remaining_records_operation)
# groups, remaining_records_composetujie =get_tujiecompose_chunk_bbox_and_removed_data(remaining_records_compose)
# results, filtered_data=get_guzhang_chunk_bbox_and_removed_data(remaining_records_composetujie)
# res = filter_content_bbox(filtered_data,max_length=max_length)
# return res
def get_chunk_filter(data,max_length=10000):
"""
将 data 中的 text/table/image 内容按字符长度分块(每块不超过 max_length
返回 [{"content": str, "highlight_list": list}, ...]
"""
chunks = []
current_content = ""
current_highlight_list = []
def _flush_chunk():
"""内部函数:将当前内容打包进 chunks并重置"""
if current_content or current_highlight_list:
chunks.append({
"content": current_content,
"highlight_list": current_highlight_list.copy()
})
for ins in data:
if "type" not in ins:
continue
added_content = ""
bbox = ins.get("bbox", [])
page_idx = ins.get("page_idx", -1)
if ins["type"] == "text":
text_content = ins.get("text", "")
added_content = text_content + "\n"
elif ins["type"] == "image":
image_caption = ins.get("image_caption", "")
img_path = ins.get("img_path", "")
if isinstance(image_caption, list):
image_caption = "\n".join(str(item) for item in image_caption)
elif not isinstance(image_caption, str):
image_caption = str(image_caption)
if isinstance(img_path, list):
img_path = "\n".join(str(p) for p in img_path)
elif not isinstance(img_path, str):
img_path = str(img_path)
if image_caption or img_path:
added_content = image_caption + "\n" + img_path + "\n"
else:
continue # 跳过空图像项
else:
continue
# 检查加上 added_content 后是否超限
if len(current_content) + len(added_content) > max_length:
# 如果当前块非空,先 flush
if current_content:
_flush_chunk()
current_content = ""
current_highlight_list = []
# 如果单个 added_content 超过 max_length仍需保留避免丢弃
if len(added_content) > max_length:
# 强制截断或直接放入(这里选择直接放入,避免信息丢失)
current_content = added_content[:max_length]
current_highlight_list.append({"page_idx": page_idx, "bbox": bbox})
_flush_chunk()
# 剩余部分?此处简化处理:忽略超长部分(或可递归拆分,但复杂)
continue
else:
current_content = added_content
current_highlight_list.append({"page_idx": page_idx, "bbox": bbox})
else:
current_content += added_content
current_highlight_list.append({"page_idx": page_idx, "bbox": bbox})
# 处理最后一块
if current_content or current_highlight_list:
chunks.append({
"content": current_content,
"highlight_list": current_highlight_list
})
return chunks
# if __name__ == "__main__":
# file_path = '/storage01/home/hdf/project/wxkgrag/kgextract/output/122-06A0014-B01001_发动机-维修手册_content_list.json'
# input_pdf_path = "/storage01/home/hdf/project/wxkgrag/kgextract/know/122-06A0014-B01001_发动机-维修手册_highlighted_fdb9e9db-98d2-4ef6-bc72-52f875453f85.pdf"
# try:
# with open(file_path, 'r', encoding='utf-8') as file:
# data = json.load(file)
# except Exception as e:
# print(f"读取 JSON 文件时出错:{e}")
# data = []
# res = get_chunk_filter(data=data)
# for ins in res:
# print(ins)
# print(11111111111111111)