From 9fb276f6d769f22db5e9e6647a6f996c7324bb46 Mon Sep 17 00:00:00 2001 From: Dennis Krivochenko Date: Sat, 18 Jul 2026 12:17:07 +0700 Subject: [PATCH] feat: add document optimization script --- search_bot/optimize_document.py | 289 ++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 search_bot/optimize_document.py diff --git a/search_bot/optimize_document.py b/search_bot/optimize_document.py new file mode 100644 index 0000000..1f1a04b --- /dev/null +++ b/search_bot/optimize_document.py @@ -0,0 +1,289 @@ +#!/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 '' in table and '' in table: + return table + rows = re.findall(r'(.*?)', table, re.DOTALL) + if not rows: + return table + + header = '' + rows[0] + '' + body = '' + for row in rows[1:]: + body += '' + row + '' + body += '' + + return '' + header + body + '
' + + return re.sub(r'.*?
', 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'''
+УТВЕРЖДАУ
+Президент
+О.В. Шарыкин
+{old_text.split("«")[1].split("г.")[0]} г. +
''' + content = content.replace(old_text, new_text) + break + + return content + + +def merge_terms_tables(content): + """Объединить разбитые таблицы терминов.""" + # Поиск таблиц терминов + terms_pattern = r'(.*?Термины.*?
)' + matches = re.findall(terms_pattern, content, re.DOTALL) + + if len(matches) > 1: + # Объединяем все таблицы терминов в одну + merged = '' + for table in matches: + rows = re.findall(r'(.*?)', table, re.DOTALL) + for row in rows[1:]: # Пропускаем заголовок + merged += row + merged += '
ТерминОпределение
' + 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'', 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)