docs: актуализация README/ARCHITECTURE + план паттерна ВМ-буфер+pull (снимок перед ВМ-инкапсуляцией загрузки)
Deploy drhider / validate (push) Canceled after 0s

This commit is contained in:
“Naeel”
2026-08-21 15:35:45 +03:00
parent a9d4139afb
commit 705b39ab7d
3 changed files with 182 additions and 75 deletions
+38 -42
View File
@@ -16,7 +16,7 @@ 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()
└── POST /api/upload (→ /api/process_stream/<sid>) → api_bp → drhider.obfuscate_files()
│
▼
TwoPassObfuscator
@@ -45,7 +45,7 @@ site/app.py ──→ Flask (create_app)
| **`.pdf`** | → Markdown | Текст + таблицы (`pdfplumber`), без форматирования |
| **`.txt`** и прочие текстовые | → как есть | Декодируется UTF-8, ошибки заменяются `�` |
| **`.zip`** | → распаковка | Файлы внутри обрабатываются рекурсивно. Защита от ZIP-бомб: ≤500 файлов, ratio ≤100:1, ≤500 MB |
| **`.doc`** (бинарный) | ❌ не поддерживается | Штурвал не даёт установить LibreOffice. Если понадобится — просить админов платформы |
| **`.doc`** (бинарный) | → Markdown | Через LibreOffice headless: `.doc` → `.docx` → штатный `docx_to_markdown` |
Ограничение на размер загрузки: **200 MB** (`MAX_CONTENT_LENGTH`).
@@ -56,16 +56,17 @@ site/app.py ──→ Flask (create_app)
### Проход 1: сбор сущностей
```
Файлы (.docx, .pdf, .txt, .zip)
Файлы (.docx, .pdf, .doc, .txt, .zip)
│
▼
extractor.expand_zips() ← распаковать ZIP
│
▼
extractor.convert_pdfs_to_docx() ← PDF → DOCX
│
▼
extractor.extract_text() ← извлечь текст из каждого файла
│ (.docx → docx_to_markdown,
│ .pdf → pdf_to_markdown,
│ .doc → doc_to_markdown,
│ .txt → как есть)
│
├──→ scanner.scan_regex() ← regex: телефоны, email, ИНН, ОГРН, КПП,
│ БИК, счета, паспорта, компании, ФИО
@@ -74,7 +75,7 @@ extractor.extract_text() ← извлечь текст из каждог
(только если llm_client передан)
│
▼
mapping = {оригинал → фиктивное} ← глобальный словарь замен
mapping = {оригинал → токен} ← глобальный словарь замен (гибридные токены)
```
### Проход 2: замена
@@ -86,8 +87,7 @@ mapping + sorted_keys (по убыванию длины)
Для каждого файла:
│
├── .docx → replacer.replace_in_docx() ← склеить runs → заменить → сохранить
├── .doc → без изменений (бинарный)
└── .txt/.pdf → replacer.replace_in_text() ← простая строковая замена
└── остальные → replacer.apply_replacements() ← замена в тексте (Markdown/plain)
│
▼
builder.build_mapping_csv() ← mapping.csv
@@ -104,44 +104,33 @@ builder.build_zip() ← ZIP со всеми файлами + mappi
- `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)`
Функции (не класс):
- `docx_to_markdown(content)` → str (Markdown)
- `pdf_to_markdown(content)` → str (Markdown)
- `doc_to_markdown(content)` → str (через LibreOffice headless)
- `extract_text(fname, content, ctype)` → str
- `expand_zips(files)` → распакованный список
- `convert_pdfs_to_docx(files)` → PDF заменены на DOCX
### `drhider/scanner.py`
Две функции (не класс), мутируют переданный `mapping: Dict[str, str]`:
- `scan_regex(text, mapping)` — regex-поиск
Функции (не класс), мутируют переданные `mapping` и `counters`:
- `TYPE_POOLS`, `_next_token(entity_type, counters)` — генерация гибридных токенов (шаблон + номер)
- `scan_regex(text, mapping, counters)` — regex-поиск
- `split_into_chunks(text, size, overlap)` — разбиение текста на чанки для LLM
- `normalize_entity(s)` — нормализация сущности перед дедупом
- `scan_llm_ner(all_texts, mapping, llm_client)` — LLM NER
Фиктивные значения — гибридные токены (`Иванов_0001`, `ООО_Технология_0034`), а не
отдельные функции-генераторы на каждый тип. Отдельного пакета `generators/` нет.
### `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`
Две функции сборки результата:
@@ -161,7 +150,8 @@ HTTP-клиент к OpenAI-совместимому API. Читает пере
Blueprint'ы — каждый в своём файле:
- `main_bp` — только `GET /`
- `health_bp` — только `GET /health`
- `api_bp` — только `POST /api/drhider`
- `api_bp` — `POST /api/upload`, `GET /api/process_stream/<sid>` (SSE),
`POST /api/process/<sid>`, `GET /api/download/<sid>`, `GET /api/csv/<sid>`
`__init__.py` содержит `register_routes(app)` — единая точка регистрации.
@@ -170,14 +160,20 @@ Blueprint'ы — каждый в своём файле:
## Поток данных
```
HTTP POST /api/drhider (multipart/form-data)
1. POST /api/upload (multipart/form-data)
│ request.files.getlist("files")
│ → session.add_file(...) для каждого файла
▼
JSON {ok, session: <sid>, count}
2. GET /api/process_stream/<sid> (SSE)
│
▼
api_bp.drhider()
│ request.files.getlist("files")
│ → [(filename, bytes, mimetype), ...]
api_bp.process_stream(sid)
│ worker-поток:
│ → [(filename, bytes, ""), ...] из сессии
▼
obfuscate_files(files, llm_client=LLMClient())
obfuscate_files(files, llm_client=LLMClient(), progress_cb=...)
│ Scanner → Extractor → LLM NER → Obfuscator → Replacer → Builder
▼
bytes (ZIP-архив с обезличенными документами + mapping.csv)
@@ -284,9 +280,9 @@ BOM (`\ufeff`) нужен для корректного открытия CSV в
▼
TwoPassObfuscator.obfuscate(files)
│
├── expand_zips → convert_pdfs_to_docx → extract_text
├── expand_zips → extract_text
├── scan_regex + scan_llm_ner → mapping
├── replace_in_docx / replace_in_text → обфусцированные файлы
├── replace_in_docx / apply_replacements → обфусцированные файлы
└── build_mapping_csv + build_zip → (zip_bytes, csv_str)
│
▼