# Архитектура DrHider ## Общая схема ``` Пользователь (браузер) │ ▼ https://drhider.pythonk8s.dev.nubes.ru/ │ ▼ Штурвал (Managed Flask на pythonk8s) │ ▼ site/app.py ──→ Flask (create_app) │ ├── GET / → main_bp → templates/index.html ├── GET /health → health_bp → {"ok": true} └── POST /api/drhider → api_bp → drhider.obfuscate_files() │ ▼ TwoPassObfuscator │ ┌────────────┼────────────┐ │ │ │ extractor scanner replacer (текст) (regex+LLM) (замена) │ │ │ └────────────┼────────────┘ │ builder (ZIP+CSV) │ ▼ application/zip ``` --- ## Двухпроходная обфускация ### Проход 1: сбор сущностей ``` Файлы (.docx, .pdf, .txt, .zip) │ ▼ extractor.expand_zips() ← распаковать ZIP │ ▼ extractor.convert_pdfs_to_docx() ← PDF → DOCX │ ▼ extractor.extract_text() ← извлечь текст из каждого файла │ ├──→ scanner.scan_regex() ← regex: телефоны, email, ИНН, ОГРН, КПП, │ БИК, счета, паспорта, компании, ФИО │ └──→ scanner.scan_llm_ner() ← LLM: имена, адреса, паспорта (только если llm_client передан) │ ▼ mapping = {оригинал → фиктивное} ← глобальный словарь замен ``` ### Проход 2: замена ``` mapping + sorted_keys (по убыванию длины) │ ▼ Для каждого файла: │ ├── .docx → replacer.replace_in_docx() ← склеить runs → заменить → сохранить ├── .doc → без изменений (бинарный) └── .txt/.pdf → replacer.replace_in_text() ← простая строковая замена │ ▼ builder.build_mapping_csv() ← mapping.csv builder.build_zip() ← ZIP со всеми файлами + mapping.csv ``` --- ## Модули ### `drhider/config.py` Константы уровня всего пакета. НЕ содержит логики — только данные. - `ENTITY_PATTERNS` — 10 regex-паттернов - `COMPANY_PATTERN`, `PERSON_PATTERN` — скомпилированные regex - Словари: `RU_SURNAMES`, `RU_NAMES`, `RU_PATRONYMICS`, `RU_CITIES`, `RU_STREETS`, `FAKE_DOMAINS` ### `drhider/checksum.py` Чистые функции для расчёта контрольных сумм. Без побочных эффектов. - `checksum_inn10(inn)` → 1 цифра - `checksum_inn12(inn)` → 2 цифры - `checksum_ogrn(ogrn)` → 1 цифра ### `drhider/random_utils.py` Утилиты генерации случайных строк. - `random_digits(n)` → строка из n цифр - `random_letters(n)` → строка из n строчных латинских букв ### `drhider/generators/` Каждый генератор — независимая функция с сигнатурой `generate_xxx(original: str) -> str`. Параметр `original` нужен для извлечения префикса (например, «ИНН » из «ИНН 1234567890»). Генераторы НЕ общаются друг с другом — только импортируют из `config`, `checksum`, `random_utils`. `__init__.py` содержит `ENTITY_GENERATORS` — словарь, связывающий `entity_type` из `ENTITY_PATTERNS` с функцией-генератором. Используется в `scanner.scan_regex()`. ### `drhider/llm_client.py` HTTP-клиент к OpenAI-совместимому API. Читает переменные окружения. Интерфейс: `.complete(prompt: str) -> str`. ### `drhider/extractor.py` Три независимые функции (не класс): - `extract_text(fname, content, ctype)` → `(text, docx_document_or_None)` - `expand_zips(files)` → распакованный список - `convert_pdfs_to_docx(files)` → PDF заменены на DOCX ### `drhider/scanner.py` Две функции (не класс), мутируют переданный `mapping: Dict[str, str]`: - `scan_regex(text, mapping)` — regex-поиск - `scan_llm_ner(all_texts, mapping, llm_client)` — LLM NER ### `drhider/replacer.py` Три чистые функции: - `apply_replacements(text, mapping, sorted_keys)` → строка с заменами - `replace_in_docx(doc, mapping, sorted_keys)` → bytes (изменённый DOCX) - `replace_in_text(text, fname, mapping, sorted_keys)` → bytes ### `drhider/builder.py` Две функции сборки результата: - `build_zip(files, mapping_csv)` → bytes (ZIP-архив) - `build_mapping_csv(mapping)` → str (CSV) ### `drhider/obfuscator.py` `TwoPassObfuscator` — оркестратор. Единственный класс, который знает о顺序е вызовов. Метод `.obfuscate(files)` вызывает модули в правильном порядке. `obfuscate_files()` — удобная функция-обёртка. ### `site/app.py` Точка входа для Штурвала. `create_app()` создаёт Flask, регистрирует blueprint'ы. `app = create_app()` — глобальный экземпляр. ### `site/routes/` Blueprint'ы — каждый в своём файле: - `main_bp` — только `GET /` - `health_bp` — только `GET /health` - `api_bp` — только `POST /api/drhider` `__init__.py` содержит `register_routes(app)` — единая точка регистрации. --- ## Поток данных ``` HTTP POST /api/drhider (multipart/form-data) │ ▼ api_bp.drhider() │ request.files.getlist("files") │ → [(filename, bytes, mimetype), ...] ▼ obfuscate_files(files, llm_client=LLMClient()) │ ▼ TwoPassObfuscator.obfuscate(files) │ ├── expand_zips → convert_pdfs_to_docx → extract_text ├── scan_regex + scan_llm_ner → mapping ├── replace_in_docx / replace_in_text → обфусцированные файлы └── build_mapping_csv + build_zip → (zip_bytes, csv_str) │ ▼ send_file(BytesIO(zip_data), mimetype="application/zip") │ ▼ HTTP 200 + ZIP-архив ``` --- ## Принципы 1. **Модули — чистые функции.** Где возможно — без классов, без состояния. 2. **Единственный класс — TwoPassObfuscator.** Только он управляет состоянием (mapping, sorted_keys). 3. **Генераторы — изолированы.** Каждый в своём файле, не зависят друг от друга. 4. **Flask — тонкая прослойка.** Только принимает запрос, вызывает `obfuscate_files()`, отдаёт ответ. 5. **Без БД.** Всё в памяти. Никаких внешних зависимостей кроме LLM API. 6. **⛔ Ноль внешних зависимостей на фронтенде.** Никаких CDN, внешних шрифтов, иконок, JS-библиотек. ВСЁ в коде. Страница грузится за 1 запрос.