Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 83e7963391 | |||
| e7dbe6fc25 |
@@ -1,268 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Clean and chunk parsed PDF data from unstructured-api.
|
|
||||||
Filters noise, preserves structure, chunks to 1500-1800 chars.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
|
|
||||||
def load_parsed_data(path):
|
|
||||||
with open(path, 'r', encoding='utf-8') as f:
|
|
||||||
return json.load(f)
|
|
||||||
|
|
||||||
def clean_text(text):
|
|
||||||
if not text:
|
|
||||||
return ""
|
|
||||||
text = re.sub(r'\s+', ' ', text.strip())
|
|
||||||
return text.strip()
|
|
||||||
|
|
||||||
def is_noise(text):
|
|
||||||
"""Check if text is noise."""
|
|
||||||
text = clean_text(text)
|
|
||||||
if not text:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Company name header/footer
|
|
||||||
if text == "АО «ХК «Сибцем»":
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Page numbers: "Стр. 2 из 10"
|
|
||||||
if re.match(r'^Стр\.\s+\d+\s+из\s+\d+$', text):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Standalone numbers: "4."
|
|
||||||
if re.match(r'^\d+\.$', text):
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Table of contents
|
|
||||||
lines = text.split('\n')
|
|
||||||
if len(lines) > 3:
|
|
||||||
page_pattern = re.compile(r'\d+\s*$')
|
|
||||||
page_lines = sum(1 for l in lines if page_pattern.search(l.strip()))
|
|
||||||
if page_lines > len(lines) * 0.5:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Specific noise
|
|
||||||
noise_patterns = [
|
|
||||||
r'^Оглавление$',
|
|
||||||
r'^Содержание$',
|
|
||||||
r'^Редакция \d+$',
|
|
||||||
r'^Тип документа:',
|
|
||||||
r'^Наименование процесса:',
|
|
||||||
r'^Ведущее подразделение:',
|
|
||||||
r'^Дата утверждения:',
|
|
||||||
]
|
|
||||||
for pattern in noise_patterns:
|
|
||||||
if re.search(pattern, text):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def is_section_title(text):
|
|
||||||
"""Check if text is a section title like '4. Термины, определения и сокращения'."""
|
|
||||||
text = clean_text(text)
|
|
||||||
return bool(re.match(r'^\d+\.\s+\w', text))
|
|
||||||
|
|
||||||
def is_section_subtitle(text):
|
|
||||||
"""Check if text is a section subtitle like '6.7. Электронные...'."""
|
|
||||||
text = clean_text(text)
|
|
||||||
return bool(re.match(r'^\d+\.\d+\.\s+\w', text))
|
|
||||||
|
|
||||||
def is_section_header(text):
|
|
||||||
"""Check if text starts with a section/subsection number."""
|
|
||||||
text = clean_text(text)
|
|
||||||
return bool(re.match(r'^\d+(?:\.\d+)*[\.\s]', text))
|
|
||||||
|
|
||||||
def merge_elements(elements):
|
|
||||||
"""
|
|
||||||
Merge short fragments with context.
|
|
||||||
Strategy: merge consecutive non-title elements into text blocks.
|
|
||||||
Keep titles and tables as separate blocks.
|
|
||||||
"""
|
|
||||||
if not elements:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Filter noise first
|
|
||||||
filtered = [el for el in elements if not is_noise(el.get('text', ''))]
|
|
||||||
|
|
||||||
# Separate into blocks: text blocks, title blocks, table blocks
|
|
||||||
blocks = []
|
|
||||||
current_text_block = []
|
|
||||||
|
|
||||||
for el in filtered:
|
|
||||||
text = el.get('text', '')
|
|
||||||
el_type = el.get('type', '')
|
|
||||||
|
|
||||||
# Skip Image (OCR noise from cover page)
|
|
||||||
if el_type == 'Image':
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If this is a title/section header, flush text block and add title
|
|
||||||
if el_type in ('Title',) and is_section_header(text):
|
|
||||||
if current_text_block:
|
|
||||||
blocks.append({
|
|
||||||
'text': ' '.join(clean_text(e['text']) for e in current_text_block),
|
|
||||||
'type': 'NarrativeText',
|
|
||||||
'metadata': current_text_block[-1].get('metadata', {})
|
|
||||||
})
|
|
||||||
current_text_block = []
|
|
||||||
blocks.append({
|
|
||||||
'text': text,
|
|
||||||
'type': 'Title',
|
|
||||||
'metadata': el.get('metadata', {})
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If this is a table, flush text block and add table
|
|
||||||
if el_type == 'Table':
|
|
||||||
if current_text_block:
|
|
||||||
blocks.append({
|
|
||||||
'text': ' '.join(clean_text(e['text']) for e in current_text_block),
|
|
||||||
'type': 'NarrativeText',
|
|
||||||
'metadata': current_text_block[-1].get('metadata', {})
|
|
||||||
})
|
|
||||||
current_text_block = []
|
|
||||||
blocks.append({
|
|
||||||
'text': text,
|
|
||||||
'type': 'Table',
|
|
||||||
'metadata': el.get('metadata', {})
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Otherwise, accumulate into text block
|
|
||||||
current_text_block.append(el)
|
|
||||||
|
|
||||||
# Flush remaining text block
|
|
||||||
if current_text_block:
|
|
||||||
blocks.append({
|
|
||||||
'text': ' '.join(clean_text(e['text']) for e in current_text_block),
|
|
||||||
'type': 'NarrativeText',
|
|
||||||
'metadata': current_text_block[-1].get('metadata', {})
|
|
||||||
})
|
|
||||||
|
|
||||||
# Now merge short text blocks with adjacent content
|
|
||||||
# Specifically: merge short blocks that are continuations of previous sections
|
|
||||||
result = []
|
|
||||||
for i, block in enumerate(blocks):
|
|
||||||
text = block['text']
|
|
||||||
el_type = block['type']
|
|
||||||
clean = clean_text(text)
|
|
||||||
|
|
||||||
# If this is a short text block, try to merge with previous
|
|
||||||
if len(clean) < 60 and el_type == 'NarrativeText' and result:
|
|
||||||
# Check if it looks like a continuation (section subtitle or fragment)
|
|
||||||
if is_section_subtitle(clean) or not clean[0].isdigit():
|
|
||||||
# Merge with previous block
|
|
||||||
result[-1]['text'] = f"{result[-1]['text']} {clean}"
|
|
||||||
continue
|
|
||||||
|
|
||||||
result.append(block)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def chunk_text(text, max_chunk_size=1800):
|
|
||||||
"""Split text into chunks of ~1500-1800 characters."""
|
|
||||||
if len(text) <= max_chunk_size:
|
|
||||||
return [text]
|
|
||||||
|
|
||||||
# Try to split by paragraphs first
|
|
||||||
paragraphs = re.split(r'\n+', text)
|
|
||||||
if len(paragraphs) > 1:
|
|
||||||
chunks = []
|
|
||||||
current = ""
|
|
||||||
for para in paragraphs:
|
|
||||||
para = para.strip()
|
|
||||||
if not para:
|
|
||||||
continue
|
|
||||||
if len(current) + len(para) + 1 <= max_chunk_size:
|
|
||||||
current = f"{current}\n{para}"
|
|
||||||
else:
|
|
||||||
if current:
|
|
||||||
chunks.append(current)
|
|
||||||
current = para
|
|
||||||
if current:
|
|
||||||
chunks.append(current)
|
|
||||||
return chunks
|
|
||||||
|
|
||||||
# Split by sentences
|
|
||||||
sentences = re.split(r'(?<=[.!?])\s+', text)
|
|
||||||
chunks = []
|
|
||||||
current = ""
|
|
||||||
for sentence in sentences:
|
|
||||||
if len(current) + len(sentence) + 1 <= max_chunk_size:
|
|
||||||
current = f"{current} {sentence}"
|
|
||||||
else:
|
|
||||||
if current:
|
|
||||||
chunks.append(current)
|
|
||||||
current = sentence
|
|
||||||
if current:
|
|
||||||
chunks.append(current)
|
|
||||||
return chunks
|
|
||||||
|
|
||||||
def process_elements(elements, max_chunk_size=1800):
|
|
||||||
"""Process elements into chunks."""
|
|
||||||
# Merge blocks
|
|
||||||
merged = merge_elements(elements)
|
|
||||||
|
|
||||||
# Create chunks
|
|
||||||
chunks = []
|
|
||||||
chunk_index = 0
|
|
||||||
|
|
||||||
for el in merged:
|
|
||||||
text = el.get('text', '')
|
|
||||||
el_type = el.get('type', 'Unknown')
|
|
||||||
page = el.get('metadata', {}).get('page_number', '?')
|
|
||||||
|
|
||||||
# Skip very short chunks
|
|
||||||
if len(text.strip()) < 10:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Split long texts
|
|
||||||
parts = chunk_text(text, max_chunk_size)
|
|
||||||
|
|
||||||
for part in parts:
|
|
||||||
chunk_index += 1
|
|
||||||
chunks.append({
|
|
||||||
'index': chunk_index,
|
|
||||||
'type': el_type,
|
|
||||||
'page': page,
|
|
||||||
'text': part,
|
|
||||||
'size': len(part)
|
|
||||||
})
|
|
||||||
|
|
||||||
return chunks, merged
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parsed_path = '/tmp/pol177_parsed.json'
|
|
||||||
output_path = '/tmp/pol177_clean_chunk.json'
|
|
||||||
|
|
||||||
print("Loading parsed data...")
|
|
||||||
elements = load_parsed_data(parsed_path)
|
|
||||||
print(f"Loaded {len(elements)} elements")
|
|
||||||
|
|
||||||
print("Processing...")
|
|
||||||
chunks, merged = process_elements(elements)
|
|
||||||
|
|
||||||
print(f"Generated {len(chunks)} chunks")
|
|
||||||
|
|
||||||
# Stats
|
|
||||||
if chunks:
|
|
||||||
sizes = [c['size'] for c in chunks]
|
|
||||||
print(f"Chunk sizes: min={min(sizes)}, max={max(sizes)}, avg={sum(sizes)/len(sizes):.0f}")
|
|
||||||
|
|
||||||
# Type distribution
|
|
||||||
types = {}
|
|
||||||
for c in chunks:
|
|
||||||
types[c['type']] = types.get(c['type'], 0) + 1
|
|
||||||
print(f"Chunk types: {types}")
|
|
||||||
|
|
||||||
# Show all chunks
|
|
||||||
print("\n=== All chunks ===")
|
|
||||||
for c in chunks:
|
|
||||||
print(f"[{c['index']}] {c['type']} (page {c['page']}, {c['size']} chars)")
|
|
||||||
print(f" {c['text'][:120]}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
-100
@@ -1,100 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Парсинг регламента через Unstructured API.
|
|
||||||
Делает один проход (strategy=hi_res) и разделяет элементы на текст и таблицы.
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
import re
|
|
||||||
import requests
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# === НАСТРОЙКИ ===
|
|
||||||
UNSTRUCTURED_API = "http://192.168.1.103:8005/general/v0/general"
|
|
||||||
PDF_PATH = "/opt/documents_xk/АОХКСибцем/07 Служба Вице-президента по экономике и финансам/ДИТ/02 Регламенты деятельности/08 Положение Управление ИТ-инцидентами от 20.02.2024 № ПОЛ-123-2.pdf"
|
|
||||||
OUTPUT_PATH = "/opt/okf-regulations/concepts/pol123_parsed.json"
|
|
||||||
|
|
||||||
|
|
||||||
def parse_document(pdf_path: str) -> dict:
|
|
||||||
"""Отправляет PDF в API (hi_res) и возвращает рассортированные данные."""
|
|
||||||
print(f" Отправка файла {Path(pdf_path).name} в Unstructured API (hi_res)...")
|
|
||||||
|
|
||||||
with open(pdf_path, "rb") as f:
|
|
||||||
files = {"files": (Path(pdf_path).name, f, "application/pdf")}
|
|
||||||
data = {
|
|
||||||
"strategy": "hi_res",
|
|
||||||
"coordinates": "true"
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
response = requests.post(UNSTRUCTURED_API, files=files, data=data, timeout=300)
|
|
||||||
response.raise_for_status()
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f" ❌ Ошибка Unstructured API: {e}")
|
|
||||||
return {"text": [], "tables": []}
|
|
||||||
|
|
||||||
elements = response.json()
|
|
||||||
if isinstance(elements, dict):
|
|
||||||
elements = elements.get("elements", [])
|
|
||||||
|
|
||||||
text_elements = []
|
|
||||||
real_tables = []
|
|
||||||
|
|
||||||
print(" Сортировка и фильтрация элементов...")
|
|
||||||
for el in elements:
|
|
||||||
el_type = el.get("type")
|
|
||||||
|
|
||||||
if el_type == "Table":
|
|
||||||
meta = el.get("metadata", {})
|
|
||||||
html = meta.get("text_as_html", "")
|
|
||||||
|
|
||||||
# Строгий фильтр на наличие HTML-таблицы
|
|
||||||
if html and "<table" in html.lower():
|
|
||||||
row_count = len(re.findall(r'<tr[^>]*>', html, re.IGNORECASE)) if html else None
|
|
||||||
col_count = len(re.findall(r'<td[^>]*>|<th[^>]*>', html[:500], re.IGNORECASE)) if html else None
|
|
||||||
|
|
||||||
real_tables.append({
|
|
||||||
"page": meta.get("page_number"),
|
|
||||||
"text": el.get("text", ""),
|
|
||||||
"html": html,
|
|
||||||
"row_count": row_count,
|
|
||||||
"col_count": col_count,
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
# Если таблица оказалась фейковой, отправляем её текст в общий котел
|
|
||||||
text_elements.append(el)
|
|
||||||
else:
|
|
||||||
# Всё остальное (Title, NarrativeText, ListItem и т.д.)
|
|
||||||
text_elements.append(el)
|
|
||||||
|
|
||||||
return {"text": text_elements, "tables": real_tables}
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("=" * 60)
|
|
||||||
print(f"📄 Парсинг документа: {Path(PDF_PATH).name}")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
parsed_data = parse_document(PDF_PATH)
|
|
||||||
|
|
||||||
print(f"\n[Итоги]")
|
|
||||||
print(f" ✅ Получено текстовых элементов: {len(parsed_data['text'])}")
|
|
||||||
print(f" ✅ Найдено валидных таблиц: {len(parsed_data['tables'])}")
|
|
||||||
|
|
||||||
result = {
|
|
||||||
"document": "ПОЛ-123",
|
|
||||||
"title": "Положение Управление ИТ-инцидентами от 20.02.2024 № ПОЛ-123-2",
|
|
||||||
"text": parsed_data["text"],
|
|
||||||
"tables": parsed_data["tables"],
|
|
||||||
"metadata": {
|
|
||||||
"unstructured_api": UNSTRUCTURED_API,
|
|
||||||
"strategy": "hi_res",
|
|
||||||
"timestamp": "2026-07-06",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Path(OUTPUT_PATH).write_text(json.dumps(result, ensure_ascii=False, indent=2))
|
|
||||||
print(f"\n✅ Сохранено в {OUTPUT_PATH}")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Универсальный скрипт для улучшения MD-файлов документов АО «ХК «Сибцем».
|
|
||||||
|
|
||||||
Применяет паттерны:
|
|
||||||
1. Удаляет футеры страниц
|
|
||||||
2. Форматирует блок "УТВЕРЖДАЮ"
|
|
||||||
3. Выравнивает иерархию заголовков
|
|
||||||
4. Объединяет таблицы терминов
|
|
||||||
5. Добавляет разделители между разделами
|
|
||||||
6. Приводит таблицы к единому виду
|
|
||||||
|
|
||||||
Использование:
|
|
||||||
python3 optimize_document.py <путь_к_оригиналу_на_сервере>
|
|
||||||
|
|
||||||
Пример:
|
|
||||||
python3 optimize_document.py "/opt/documents_xk/АОХКСибцем/07 Служба.../08 Положение Управление ИТ-инцидентами от 20.02.2024 № ПОЛ-123-2.md"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import requests
|
|
||||||
import urllib.parse
|
|
||||||
|
|
||||||
# Конфигурация
|
|
||||||
SSH_KEY = "/home/hermes/.ssh/192.168.1.106"
|
|
||||||
SSH_USER = "administrator@192.168.1.106"
|
|
||||||
SERVER_BASE = "/opt/documents_xk/АОХКСибцем"
|
|
||||||
FILEBROWSER_URL = "https://smb.dddennnisss.ru"
|
|
||||||
FILEBROWSER_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJGaWxlQnJvd3NlciBRdWFudHVtIiwiZXhwIjoyMzAyNzM1NzA3LCJpYXQiOjE3ODQzMzU3MDcsImJlbG9uZ3NUbyI6MSwiUGVybWlzc2lvbnMiOnsiYXBpIjp0cnVlLCJhZG1pbiI6dHJ1ZSwibW9kaWZ5Ijp0cnVlLCJzaGFyZSI6dHJ1ZSwicmVhbHRpbWUiOmZhbHNlLCJkZWxldGUiOnRydWUsImNyZWF0ZSI6dHJ1ZSwiZG93bmxvYWQiOnRydWV9fQ.QBKlh3NQJeh4aeMiKLkhg0tF8hxk-Oh2KLsXLSbuvm8"
|
|
||||||
SOURCE_NAME = "SSD-Storage"
|
|
||||||
|
|
||||||
|
|
||||||
def run_ssh(command):
|
|
||||||
"""Выполнить команду на сервере через SSH."""
|
|
||||||
result = subprocess.run(
|
|
||||||
["ssh", "-i", SSH_KEY, "-o", "StrictHostKeyChecking=no",
|
|
||||||
SSH_USER, command],
|
|
||||||
capture_output=True, text=True, timeout=30
|
|
||||||
)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def scp_download(remote_path, local_path):
|
|
||||||
"""Скачать файл с сервера."""
|
|
||||||
result = subprocess.run(
|
|
||||||
["scp", "-i", SSH_KEY, "-o", "StrictHostKeyChecking=no",
|
|
||||||
f"{SSH_USER}:{remote_path}", local_path],
|
|
||||||
capture_output=True, text=True, timeout=60
|
|
||||||
)
|
|
||||||
return result.returncode == 0
|
|
||||||
|
|
||||||
|
|
||||||
def fix_headings(content):
|
|
||||||
"""Выровнять иерархию заголовков: все на уровень ##."""
|
|
||||||
content = re.sub(r'^(#+)\s+(.+)$', r'## \2', content, flags=re.MULTILINE)
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def add_dividers(content):
|
|
||||||
"""Добавить разделители между основными разделами."""
|
|
||||||
# Разделитель перед Оглавлением
|
|
||||||
content = re.sub(
|
|
||||||
r'(\n)(## )(Оглавление)',
|
|
||||||
r'\1\n---\n\1\2\3',
|
|
||||||
content
|
|
||||||
)
|
|
||||||
# Разделители перед разделами 1-9
|
|
||||||
content = re.sub(
|
|
||||||
r'(\n)(## )(\s*(?:1\.|2\.|3\.|4\.|5\.|6\.|7\.|8\.|9\.))',
|
|
||||||
r'\1\n---\n\1\2\3',
|
|
||||||
content
|
|
||||||
)
|
|
||||||
# Разделители перед Приложениями
|
|
||||||
content = re.sub(
|
|
||||||
r'(\n)(## )(Приложение)',
|
|
||||||
r'\1\n---\n\1\2\3',
|
|
||||||
content
|
|
||||||
)
|
|
||||||
# Разделитель перед Листом согласований
|
|
||||||
content = re.sub(
|
|
||||||
r'(\n)(## )(Лист согласований)',
|
|
||||||
r'\1\n---\n\1\2\3',
|
|
||||||
content
|
|
||||||
)
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def fix_tables(content):
|
|
||||||
"""Привести все таблицы к единому виду с thead/tbody."""
|
|
||||||
def fix_table(table):
|
|
||||||
if '<thead>' in table and '<tbody>' in table:
|
|
||||||
return table
|
|
||||||
rows = re.findall(r'<tr>(.*?)</tr>', table, re.DOTALL)
|
|
||||||
if not rows:
|
|
||||||
return table
|
|
||||||
|
|
||||||
header = '<thead><tr>' + rows[0] + '</tr></thead><tbody>'
|
|
||||||
body = ''
|
|
||||||
for row in rows[1:]:
|
|
||||||
body += '<tr>' + row + '</tr>'
|
|
||||||
body += '</tbody>'
|
|
||||||
|
|
||||||
return '<table>' + header + body + '</table>'
|
|
||||||
|
|
||||||
return re.sub(r'<table>.*?</table>', lambda m: fix_table(m.group(0)), content, flags=re.DOTALL)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_footers(content):
|
|
||||||
"""Удалить футеры страниц."""
|
|
||||||
# Удалить "АО «ХК «Сибцем»\nТип документа: ...\nНаименование документа: ..."
|
|
||||||
content = re.sub(
|
|
||||||
r'АО «ХК «Сибцем»\nТип документа:.*?\nНаименование документа:.*?\n\n',
|
|
||||||
'',
|
|
||||||
content,
|
|
||||||
flags=re.DOTALL
|
|
||||||
)
|
|
||||||
# Удалить "Стр. X из Y"
|
|
||||||
content = re.sub(r'Стр\. \d+ из \d+\n\n', '', content)
|
|
||||||
# Удалить "Дата утверждения: DD.MM.YYYY"
|
|
||||||
content = re.sub(r'Дата утверждения: \d{2}\.\d{2}\.\d{4}\n\n', '', content)
|
|
||||||
# Удалить "Ведущее подразделение: ..."
|
|
||||||
content = re.sub(r'Ведущее подразделение:.*?\n\n', '', content)
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def fix_approval_block(content):
|
|
||||||
"""Форматировать блок УТВЕРЖДАЮ."""
|
|
||||||
# Паттерн 1: "УТВЕРЖДАЮ\nПрезидент\n\nО.В. Шарыкин\n\n«XX» месяц год г."
|
|
||||||
approval_patterns = [
|
|
||||||
r'(УТВЕРЖДАУ\nПрезидент\n\nО\.В\. Шарыкин\n\n«\d+» \w+ \d{4} г\.)',
|
|
||||||
r'(УТВЕРЖДАУ\nПрезидент\n\nО\.В\. Шарыкин\n\n«\d+» \w+ \d{4} г\.)',
|
|
||||||
]
|
|
||||||
|
|
||||||
for pattern in approval_patterns:
|
|
||||||
match = re.search(pattern, content)
|
|
||||||
if match:
|
|
||||||
old_text = match.group(0)
|
|
||||||
new_text = f'''<div align="right">
|
|
||||||
<strong>УТВЕРЖДАУ</strong><br>
|
|
||||||
Президент<br>
|
|
||||||
О.В. Шарыкин<br>
|
|
||||||
{old_text.split("«")[1].split("г.")[0]} г.
|
|
||||||
</div>'''
|
|
||||||
content = content.replace(old_text, new_text)
|
|
||||||
break
|
|
||||||
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def merge_terms_tables(content):
|
|
||||||
"""Объединить разбитые таблицы терминов."""
|
|
||||||
# Поиск таблиц терминов
|
|
||||||
terms_pattern = r'(<table>.*?Термины.*?</table>)'
|
|
||||||
matches = re.findall(terms_pattern, content, re.DOTALL)
|
|
||||||
|
|
||||||
if len(matches) > 1:
|
|
||||||
# Объединяем все таблицы терминов в одну
|
|
||||||
merged = '<table><thead><tr><th>Термин</th><th>Определение</th></tr></thead><tbody>'
|
|
||||||
for table in matches:
|
|
||||||
rows = re.findall(r'<tr>(.*?)</tr>', table, re.DOTALL)
|
|
||||||
for row in rows[1:]: # Пропускаем заголовок
|
|
||||||
merged += row
|
|
||||||
merged += '</tbody></table>'
|
|
||||||
content = content.replace(matches[0], merged)
|
|
||||||
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def optimize_document(md_path):
|
|
||||||
"""Основная функция оптимизации документа."""
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
print(f"📄 Обработка документа: {md_path}")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
# Шаг 1: Скачать MD с сервера
|
|
||||||
print("\n📥 Шаг 1: Скачивание MD...")
|
|
||||||
result = run_ssh(f"cat '{md_path}'")
|
|
||||||
if result.returncode != 0:
|
|
||||||
print(f"✗ Ошибка при скачивании: {result.stderr}")
|
|
||||||
return
|
|
||||||
|
|
||||||
md_content = result.stdout
|
|
||||||
print(f"✓ MD скачан: {len(md_content)} символов")
|
|
||||||
|
|
||||||
# Шаг 2: Применить паттерны
|
|
||||||
print("\n🔧 Шаг 2: Применение паттернов...")
|
|
||||||
|
|
||||||
# 2.1 Удалить футеры
|
|
||||||
content = remove_footers(md_content)
|
|
||||||
print(" ✓ Футеры удалены")
|
|
||||||
|
|
||||||
# 2.2 Выровнять иерархию заголовков
|
|
||||||
content = fix_headings(content)
|
|
||||||
print(" ✓ Иерархия заголовков выровнена")
|
|
||||||
|
|
||||||
# 2.3 Добавить разделители
|
|
||||||
content = add_dividers(content)
|
|
||||||
print(" ✓ Разделители добавлены")
|
|
||||||
|
|
||||||
# 2.4 Привести таблицы к единому виду
|
|
||||||
content = fix_tables(content)
|
|
||||||
print(" ✓ Таблицы приведены к единому виду")
|
|
||||||
|
|
||||||
# 2.5 Объединить таблицы терминов
|
|
||||||
content = merge_terms_tables(content)
|
|
||||||
print(" ✓ Таблицы терминов объединены")
|
|
||||||
|
|
||||||
# 2.6 Форматировать блок УТВЕРЖДАЮ
|
|
||||||
content = fix_approval_block(content)
|
|
||||||
print(" ✓ Блок УТВЕРЖДАЮ отформатирован")
|
|
||||||
|
|
||||||
# Сохранить улучшенную версию
|
|
||||||
improved_path = "/tmp/improved_document.md"
|
|
||||||
with open(improved_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
print(f"\n📊 Результаты:")
|
|
||||||
print(f" Исходный размер: {len(md_content)} символов")
|
|
||||||
print(f" Улучшенный размер: {len(content)} символов")
|
|
||||||
|
|
||||||
# Показать структуру
|
|
||||||
sections = re.findall(r'^(##)\s+(.+)$', content, re.MULTILINE)
|
|
||||||
print(f" Разделов: {len(sections)}")
|
|
||||||
|
|
||||||
tables = re.findall(r'<table>', content)
|
|
||||||
print(f" Таблиц: {len(tables)}")
|
|
||||||
|
|
||||||
# Показать первые 300 символов
|
|
||||||
print(f"\n📝 Начало файла:")
|
|
||||||
print(content[:300].replace('\n', '\\n'))
|
|
||||||
|
|
||||||
# Загрузить в filebrowser
|
|
||||||
print("\n📤 Шаг 3: Загрузка в filebrowser...")
|
|
||||||
upload_to_filebrowser(content, md_path)
|
|
||||||
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
print(f"\n{'=' * 60}")
|
|
||||||
print(f"✅ Готово! Время выполнения: {elapsed:.1f} сек")
|
|
||||||
print(f"{'=' * 60}")
|
|
||||||
|
|
||||||
|
|
||||||
def upload_to_filebrowser(content, md_path):
|
|
||||||
"""Загрузить файл в filebrowser."""
|
|
||||||
# Извлечь имя файла из пути
|
|
||||||
filename = md_path.split('/')[-1]
|
|
||||||
# Добавить "_improved" перед расширением
|
|
||||||
if filename.endswith('.md'):
|
|
||||||
improved_filename = filename[:-3] + '_improved.md'
|
|
||||||
else:
|
|
||||||
improved_filename = filename + '_improved'
|
|
||||||
|
|
||||||
session = requests.Session()
|
|
||||||
session.cookies.set("filebrowser_quantum_jwt", FILEBROWSER_TOKEN, domain="smb.dddennnisss.ru")
|
|
||||||
|
|
||||||
url = f"{FILEBROWSER_URL}/api/resources"
|
|
||||||
params = {"path": improved_filename, "source": SOURCE_NAME}
|
|
||||||
|
|
||||||
r = session.put(url, params=params, data=content)
|
|
||||||
|
|
||||||
if r.status_code == 200:
|
|
||||||
print(f"✓ Файл загружен: {improved_filename}")
|
|
||||||
|
|
||||||
# Проверить
|
|
||||||
r = session.get(f"{FILEBROWSER_URL}/api/resources", params={"path": "/", "source": SOURCE_NAME})
|
|
||||||
if r.status_code == 200:
|
|
||||||
data = r.json()
|
|
||||||
items = data.get("files", []) + data.get("folders", [])
|
|
||||||
for item in items:
|
|
||||||
if improved_filename in item["name"]:
|
|
||||||
print(f"✓ Размер: {item['size']} байт")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print(f"✗ Ошибка загрузки: {r.status_code} - {r.text[:200]}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Использование:")
|
|
||||||
print(" python3 optimize_document.py <путь_к_оригиналу_на_сервере>")
|
|
||||||
print("\nПример:")
|
|
||||||
print(" python3 optimize_document.py \"/opt/documents_xk/АОХКСибцем/07 Служба.../08 Положение Управление ИТ-инцидентами от 20.02.2024 № ПОЛ-123-2.md\"")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
md_path = sys.argv[1]
|
|
||||||
optimize_document(md_path)
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Универсальный скрипт для улучшения MD-файлов документов АО «ХК «Сибцем».
|
|
||||||
|
|
||||||
Выполняется на сервере 192.168.1.106.
|
|
||||||
Применяет паттерны:
|
|
||||||
1. Удаляет футеры страниц
|
|
||||||
2. Форматирует блок "УТВЕРЖДАЮ"
|
|
||||||
3. Выравнивает иерархию заголовков
|
|
||||||
4. Объединяет таблицы терминов
|
|
||||||
5. Добавляет разделители между разделами
|
|
||||||
6. Приводит таблицы к единому виду
|
|
||||||
|
|
||||||
Использование:
|
|
||||||
python3 /opt/trueconf_bot/search_bot/optimize_documents.py <путь_к_каталогу>
|
|
||||||
|
|
||||||
Пример:
|
|
||||||
python3 /opt/trueconf_bot/search_bot/optimize_documents.py "/opt/documents_xk/ready_md/АОХКСибцем/07 Служба Вице-президента по экономике и финансам/ДИТ/02 Регламенты деятельности/"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import requests
|
|
||||||
import base64
|
|
||||||
|
|
||||||
# Конфигурация
|
|
||||||
FILEBROWSER_URL = "https://smb.dddennnisss.ru"
|
|
||||||
FILEBROWSER_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJGaWxlQnJvd3NlciBRdWFudHVtIiwiZXhwIjoyMzAyNzM1NzA3LCJpYXQiOjE3ODQzMzU3MDcsImJlbG9uZ3NUbyI6MSwiUGVybWlzc2lvbnMiOnsiYXBpIjp0cnVlLCJhZG1pbiI6dHJ1ZSwibW9kaWZ5Ijp0cnVlLCJzaGFyZSI6dHJ1ZSwicmVhbHRpbWUiOmZhbHNlLCJkZWxldGUiOnRydWUsImNyZWF0ZSI6dHJ1ZSwiZG93bmxvYWQiOnRydWV9fQ.QBKlh3NQJeh4aeMiKLkhg0tF8hxk-Oh2KLsXLSbuvm8"
|
|
||||||
SOURCE_NAME = "SSD-Storage"
|
|
||||||
|
|
||||||
|
|
||||||
def remove_footers(content):
|
|
||||||
"""Удалить футеры страниц."""
|
|
||||||
# Удалить "АО «ХК «Сибцем»\nТип документа: ...\nНаименование документа: ..."
|
|
||||||
content = re.sub(
|
|
||||||
r'АО «ХК «Сибцем»\nТип документа:.*?\nНаименование документа:.*?\n\n',
|
|
||||||
'',
|
|
||||||
content,
|
|
||||||
flags=re.DOTALL
|
|
||||||
)
|
|
||||||
# Удалить "Стр. X из Y"
|
|
||||||
content = re.sub(r'Стр\. \d+ из \d+\n\n', '', content)
|
|
||||||
# Удалить "Дата утверждения: DD.MM.YYYY"
|
|
||||||
content = re.sub(r'Дата утверждения: \d{2}\.\d{2}\.\d{4}\n\n', '', content)
|
|
||||||
# Удалить "Ведущее подразделение: ..."
|
|
||||||
content = re.sub(r'Ведущее подразделение:.*?\n\n', '', content)
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def fix_headings(content):
|
|
||||||
"""Выровнять иерархию заголовков: все на уровень ##."""
|
|
||||||
content = re.sub(r'^(#+)\s+(.+)$', r'## \2', content, flags=re.MULTILINE)
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def add_dividers(content):
|
|
||||||
"""Добавить разделители между основными разделами."""
|
|
||||||
# Разделитель перед Оглавлением
|
|
||||||
content = re.sub(
|
|
||||||
r'(\n)(## )(Оглавление)',
|
|
||||||
r'\1\n---\n\1\2\3',
|
|
||||||
content
|
|
||||||
)
|
|
||||||
# Разделители перед разделами 1-9
|
|
||||||
content = re.sub(
|
|
||||||
r'(\n)(## )(\s*(?:1\.|2\.|3\.|4\.|5\.|6\.|7\.|8\.|9\.))',
|
|
||||||
r'\1\n---\n\1\2\3',
|
|
||||||
content
|
|
||||||
)
|
|
||||||
# Разделители перед Приложениями
|
|
||||||
content = re.sub(
|
|
||||||
r'(\n)(## )(Приложение)',
|
|
||||||
r'\1\n---\n\1\2\3',
|
|
||||||
content
|
|
||||||
)
|
|
||||||
# Разделитель перед Листом согласований
|
|
||||||
content = re.sub(
|
|
||||||
r'(\n)(## )(Лист согласований)',
|
|
||||||
r'\1\n---\n\1\2\3',
|
|
||||||
content
|
|
||||||
)
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def fix_tables(content):
|
|
||||||
"""Привести все таблицы к единому виду с thead/tbody."""
|
|
||||||
def fix_table(table):
|
|
||||||
if '<thead>' in table and '<tbody>' in table:
|
|
||||||
return table
|
|
||||||
rows = re.findall(r'<tr>(.*?)</tr>', table, re.DOTALL)
|
|
||||||
if not rows:
|
|
||||||
return table
|
|
||||||
|
|
||||||
header = '<thead><tr>' + rows[0] + '</tr></thead><tbody>'
|
|
||||||
body = ''
|
|
||||||
for row in rows[1:]:
|
|
||||||
body += '<tr>' + row + '</tr>'
|
|
||||||
body += '</tbody>'
|
|
||||||
|
|
||||||
return '<table>' + header + body + '</table>'
|
|
||||||
|
|
||||||
return re.sub(r'<table>.*?</table>', lambda m: fix_table(m.group(0)), content, flags=re.DOTALL)
|
|
||||||
|
|
||||||
|
|
||||||
def merge_terms_tables(content):
|
|
||||||
"""Объединить разбитые таблицы терминов."""
|
|
||||||
# Поиск таблиц терминов
|
|
||||||
terms_pattern = r'(<table>.*?Термины.*?</table>)'
|
|
||||||
matches = re.findall(terms_pattern, content, re.DOTALL)
|
|
||||||
|
|
||||||
if len(matches) > 1:
|
|
||||||
# Объединяем все таблицы терминов в одну
|
|
||||||
merged = '<table><thead><tr><th>Термин</th><th>Определение</th></tr></thead><tbody>'
|
|
||||||
for table in matches:
|
|
||||||
rows = re.findall(r'<tr>(.*?)</tr>', table, re.DOTALL)
|
|
||||||
for row in rows[1:]: # Пропускаем заголовок
|
|
||||||
merged += row
|
|
||||||
merged += '</tbody></table>'
|
|
||||||
content = content.replace(matches[0], merged)
|
|
||||||
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def fix_approval_block(content):
|
|
||||||
"""Форматировать блок УТВЕРЖДАЮ."""
|
|
||||||
# Паттерн 1: "УТВЕРЖДАЮ\nПрезидент\n\nО.В. Шарыкин\n\n«XX» месяц год г."
|
|
||||||
approval_patterns = [
|
|
||||||
r'(УТВЕРЖДАУ\nПрезидент\n\nО\.В\. Шарыкин\n\n«\d+» \w+ \d{4} г\.)',
|
|
||||||
r'(УТВЕРЖДАУ\nПрезидент\n\nО\.В\. Шарыкин\n\n«\d+» \w+ \d{4} г\.)',
|
|
||||||
]
|
|
||||||
|
|
||||||
for pattern in approval_patterns:
|
|
||||||
match = re.search(pattern, content)
|
|
||||||
if match:
|
|
||||||
old_text = match.group(0)
|
|
||||||
new_text = f'''<div align="right">
|
|
||||||
<strong>УТВЕРЖДАУ</strong><br>
|
|
||||||
Президент<br>
|
|
||||||
О.В. Шарыкин<br>
|
|
||||||
{old_text.split("«")[1].split("г.")[0]} г.
|
|
||||||
</div>'''
|
|
||||||
content = content.replace(old_text, new_text)
|
|
||||||
break
|
|
||||||
|
|
||||||
return content
|
|
||||||
|
|
||||||
|
|
||||||
def upload_to_filebrowser(content, filename):
|
|
||||||
"""Загрузить файл в filebrowser."""
|
|
||||||
# Добавить "_improved" перед расширением
|
|
||||||
if filename.endswith('.md'):
|
|
||||||
improved_filename = filename[:-3] + '_improved.md'
|
|
||||||
else:
|
|
||||||
improved_filename = filename + '_improved'
|
|
||||||
|
|
||||||
session = requests.Session()
|
|
||||||
session.cookies.set("filebrowser_quantum_jwt", FILEBROWSER_TOKEN, domain="smb.dddennnisss.ru")
|
|
||||||
|
|
||||||
url = f"{FILEBROWSER_URL}/api/resources"
|
|
||||||
params = {"path": improved_filename, "source": SOURCE_NAME}
|
|
||||||
|
|
||||||
r = session.put(url, params=params, data=content)
|
|
||||||
|
|
||||||
if r.status_code == 200:
|
|
||||||
print(f"✓ Файл загружен: {improved_filename}")
|
|
||||||
|
|
||||||
# Проверить
|
|
||||||
r = session.get(f"{FILEBROWSER_URL}/api/resources", params={"path": "/", "source": SOURCE_NAME})
|
|
||||||
if r.status_code == 200:
|
|
||||||
data = r.json()
|
|
||||||
items = data.get("files", []) + data.get("folders", [])
|
|
||||||
for item in items:
|
|
||||||
if improved_filename in item["name"]:
|
|
||||||
print(f"✓ Размер: {item['size']} байт")
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print(f"✗ Ошибка загрузки: {r.status_code} - {r.text[:200]}")
|
|
||||||
|
|
||||||
return improved_filename
|
|
||||||
|
|
||||||
|
|
||||||
def optimize_document(md_path):
|
|
||||||
"""Основная функция оптимизации документа."""
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
print(f"\n📄 Обработка документа: {md_path.split('/')[-1]}")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
# Шаг 1: Читать MD файл
|
|
||||||
print("\n📥 Шаг 1: Чтение MD...")
|
|
||||||
try:
|
|
||||||
with open(md_path, 'r', encoding='utf-8') as f:
|
|
||||||
md_content = f.read()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Ошибка при чтении: {e}")
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"✓ MD прочитан: {len(md_content)} символов")
|
|
||||||
|
|
||||||
# Шаг 2: Применить паттерны
|
|
||||||
print("\n🔧 Шаг 2: Применение паттернов...")
|
|
||||||
|
|
||||||
# 2.1 Удалить футеры
|
|
||||||
content = remove_footers(md_content)
|
|
||||||
print(" ✓ Футеры удалены")
|
|
||||||
|
|
||||||
# 2.2 Выровнять иерархию заголовков
|
|
||||||
content = fix_headings(content)
|
|
||||||
print(" ✓ Иерархия заголовков выровнена")
|
|
||||||
|
|
||||||
# 2.3 Добавить разделители
|
|
||||||
content = add_dividers(content)
|
|
||||||
print(" ✓ Разделители добавлены")
|
|
||||||
|
|
||||||
# 2.4 Привести таблицы к единому виду
|
|
||||||
content = fix_tables(content)
|
|
||||||
print(" ✓ Таблицы приведены к единому виду")
|
|
||||||
|
|
||||||
# 2.5 Объединить таблицы терминов
|
|
||||||
content = merge_terms_tables(content)
|
|
||||||
print(" ✓ Таблицы терминов объединены")
|
|
||||||
|
|
||||||
# 2.6 Форматировать блок УТВЕРЖДАЮ
|
|
||||||
content = fix_approval_block(content)
|
|
||||||
print(" ✓ Блок УТВЕРЖДАУ отформатирован")
|
|
||||||
|
|
||||||
# Сохранить улучшенную версию
|
|
||||||
improved_path = md_path.replace('.md', '_improved.md')
|
|
||||||
with open(improved_path, "w", encoding="utf-8") as f:
|
|
||||||
f.write(content)
|
|
||||||
|
|
||||||
print(f"\n📊 Результаты:")
|
|
||||||
print(f" Исходный размер: {len(md_content)} символов")
|
|
||||||
print(f" Улучшенный размер: {len(content)} символов")
|
|
||||||
|
|
||||||
# Показать структуру
|
|
||||||
sections = re.findall(r'^(##)\s+(.+)$', content, re.MULTILINE)
|
|
||||||
print(f" Разделов: {len(sections)}")
|
|
||||||
|
|
||||||
tables = re.findall(r'<table>', content)
|
|
||||||
print(f" Таблиц: {len(tables)}")
|
|
||||||
|
|
||||||
# Показать первые 300 символов
|
|
||||||
print(f"\n📝 Начало файла:")
|
|
||||||
print(content[:300].replace('\n', '\\n'))
|
|
||||||
|
|
||||||
# Загрузить в filebrowser
|
|
||||||
print("\n📤 Шаг 3: Загрузка в filebrowser...")
|
|
||||||
filename = md_path.split('/')[-1]
|
|
||||||
upload_to_filebrowser(content, filename)
|
|
||||||
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
print(f"\n{'=' * 60}")
|
|
||||||
print(f"✅ Готово! Время выполнения: {elapsed:.1f} сек")
|
|
||||||
print(f"{'=' * 60}")
|
|
||||||
|
|
||||||
|
|
||||||
def process_directory(directory):
|
|
||||||
"""Обработать все MD файлы в каталоге."""
|
|
||||||
start_time = time.time()
|
|
||||||
|
|
||||||
print(f"📂 Обработка каталога: {directory}")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
# Найти все MD файлы
|
|
||||||
md_files = []
|
|
||||||
for root, dirs, files in os.walk(directory):
|
|
||||||
for file in files:
|
|
||||||
if file.endswith('.md'):
|
|
||||||
md_files.append(os.path.join(root, file))
|
|
||||||
|
|
||||||
print(f"Найдено {len(md_files)} MD файлов")
|
|
||||||
|
|
||||||
# Обработать каждый файл
|
|
||||||
for md_path in md_files:
|
|
||||||
try:
|
|
||||||
optimize_document(md_path)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Ошибка при обработке {md_path}: {e}")
|
|
||||||
|
|
||||||
elapsed = time.time() - start_time
|
|
||||||
print(f"\n{'=' * 60}")
|
|
||||||
print(f"✅ Обработка каталога завершена! Общее время: {elapsed:.1f} сек")
|
|
||||||
print(f"{'=' * 60}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Использование:")
|
|
||||||
print(" python3 optimize_documents.py <путь_к_каталогу>")
|
|
||||||
print("\nПример:")
|
|
||||||
print(' python3 optimize_documents.py "/opt/documents_xk/ready_md/АОХКСибцем/07 Служба Вице-президента по экономике и финансам/ДИТ/02 Регламенты деятельности/"')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
directory = sys.argv[1]
|
|
||||||
process_directory(directory)
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Комбинированный парсинг ПОЛ-177:
|
|
||||||
- Текст: Unstructured API (192.168.1.103:8005)
|
|
||||||
- Таблицы: pypdf (локально)
|
|
||||||
|
|
||||||
Сохраняет результат в /opt/okf-regulations/concepts/pol177_parsed.json
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# === НАСТРОЙКИ ===
|
|
||||||
UNSTRUCTURED_API = "http://192.168.1.103:8005/general/v0/general"
|
|
||||||
PDF_PATH = "/opt/documents_xk/АОХКСибцем/07 Служба Вице-президента по экономике и финансам/ДИТ/02 Регламенты деятельности/10 Положение Хранение электронных документов от 03.04.2024 № ПОЛ-177.pdf"
|
|
||||||
OUTPUT_PATH = "/opt/okf-regulations/concepts/pol177_parsed.json"
|
|
||||||
|
|
||||||
def fetch_unstructured(pdf_path: str) -> list[dict]:
|
|
||||||
"""Получаем элементы текста из Unstructured API (103)."""
|
|
||||||
cmd = [
|
|
||||||
"curl", "-X", "POST", UNSTRUCTURED_API,
|
|
||||||
"-F", f"files=@{pdf_path}",
|
|
||||||
"-F", "strategy=fast",
|
|
||||||
"-F", "output_format=json",
|
|
||||||
]
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
||||||
if result.returncode != 0:
|
|
||||||
print(f"❌ Unstructured API error: {result.stderr}")
|
|
||||||
return []
|
|
||||||
return json.loads(result.stdout)
|
|
||||||
|
|
||||||
def extract_tables_pypdf(pdf_path: str) -> list[dict]:
|
|
||||||
"""Извлекаем таблицы через pypdf (локально на 106)."""
|
|
||||||
try:
|
|
||||||
from pypdf import PdfReader
|
|
||||||
except ImportError:
|
|
||||||
import subprocess
|
|
||||||
subprocess.run(["pip3", "install", "pypdf", "--user"], check=True)
|
|
||||||
from pypdf import PdfReader
|
|
||||||
|
|
||||||
reader = PdfReader(pdf_path)
|
|
||||||
tables = []
|
|
||||||
for i, page in enumerate(reader.pages):
|
|
||||||
text = page.extract_text()
|
|
||||||
if text and ("таблица" in text.lower() or "стр" in text.lower()):
|
|
||||||
tables.append({"page": i + 1, "text": text})
|
|
||||||
return tables
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("=" * 60)
|
|
||||||
print("📄 Парсинг ПОЛ-177")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
# 1. Текст через Unstructured API
|
|
||||||
print("\n[1/2] Запрос текста через Unstructured API (103)...")
|
|
||||||
elements = fetch_unstructured(PDF_PATH)
|
|
||||||
print(f" ✅ Получено {len(elements)} элементов")
|
|
||||||
|
|
||||||
# 2. Таблицы через pypdf
|
|
||||||
print("\n[2/2] Извлечение таблиц через pypdf (106)...")
|
|
||||||
tables = extract_tables_pypdf(PDF_PATH)
|
|
||||||
print(f" ✅ Найдено {len(tables)} страниц с текстом")
|
|
||||||
|
|
||||||
# 3. Сохраняем комбинированный результат
|
|
||||||
result = {
|
|
||||||
"document": "ПОЛ-177",
|
|
||||||
"title": "Положение Хранение электронных документов от 03.04.2024 № ПОЛ-177",
|
|
||||||
"unstructured": elements,
|
|
||||||
"tables": tables,
|
|
||||||
"metadata": {
|
|
||||||
"unstructured_api": UNSTRUCTURED_API,
|
|
||||||
"strategy": "fast",
|
|
||||||
"timestamp": "2026-07-06",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
Path(OUTPUT_PATH).write_text(json.dumps(result, ensure_ascii=False, indent=2))
|
|
||||||
print(f"\n✅ Сохранено в {OUTPUT_PATH}")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Binary file not shown.
Reference in New Issue
Block a user