Files
drhider/docs/ARCHITECTURE.md
T

208 lines
9.7 KiB
Markdown
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.
# Архитектура 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
```
---
## Поддерживаемые форматы файлов
| Формат | Обработка | Детали |
|--------|-----------|--------|
| **`.docx`** | → Markdown | Заголовки (H1H3), **жирный**, *курсив*, таблицы (`python-docx`) |
| **`.pdf`** | → Markdown | Текст + таблицы (`pdfplumber`), без форматирования |
| **`.txt`** и прочие текстовые | → как есть | Декодируется UTF-8, ошибки заменяются `` |
| **`.zip`** | → распаковка | Файлы внутри обрабатываются рекурсивно. Защита от ZIP-бомб: ≤500 файлов, ratio ≤100:1, ≤500 MB |
| **`.doc`** (бинарный) | ❌ не поддерживается | Штурвал не даёт установить LibreOffice. Если понадобится — просить админов платформы |
Ограничение на размер загрузки: **200 MB** (`MAX_CONTENT_LENGTH`).
---
## Двухпроходная обфускация
### Проход 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. **Flask — тонкая прослойка.** Только принимает запрос, вызывает `obfuscate_files()`, отдаёт ответ.
4. **Без БД.** Всё в памяти. Никаких внешних зависимостей кроме LLM API.
5. **⛔ Ноль внешних зависимостей на фронтенде.** Никаких CDN, внешних шрифтов, иконок, JS-библиотек. ВСЁ в коде. Страница грузится за 1 запрос.
6. **⛔ ВСЯ обработка данных — на бэкенде.** Фронтенд только отображает и скачивает. Никакой JS-логики для ZIP, парсинга, обфускации. Один запрос → один ответ.
7. **⛔ Пофайловая обработка — на бэкенде.** Файлы обрабатываются строго последовательно (по одному), без параллельных LLM-запросов. Прогресс — бэкенд сообщает статус каждого файла.