8.0 KiB
Архитектура 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 /healthapi_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-архив
Принципы
- Модули — чистые функции. Где возможно — без классов, без состояния.
- Единственный класс — TwoPassObfuscator. Только он управляет состоянием (mapping, sorted_keys).
- Генераторы — изолированы. Каждый в своём файле, не зависят друг от друга.
- Flask — тонкая прослойка. Только принимает запрос, вызывает
obfuscate_files(), отдаёт ответ. - Без БД. Всё в памяти. Никаких внешних зависимостей кроме LLM API.