From 9b4060d8c5caf3d5dab641d7a070b50866f967d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Tue, 14 Jul 2026 21:58:56 +0400 Subject: [PATCH] =?UTF-8?q?docs:=20=D1=82=D1=80=D0=B5=D0=B1=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BA=20Flask-=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8E=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=A8=D1=82=D1=83=D1=80=D0=B2=D0=B0=D0=BB=D0=B0=20=E2=80=94?= =?UTF-8?q?=20health,=20no=5Fcache,=20SSE,=20BOM,=20etc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/ARCHITECTURE.md | 102 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 25cb3c6..0a35cbb 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -178,6 +178,108 @@ api_bp.drhider() │ → [(filename, bytes, mimetype), ...] ▼ obfuscate_files(files, llm_client=LLMClient()) + │ Scanner → Extractor → LLM NER → Obfuscator → Replacer → Builder + ▼ +bytes (ZIP-архив с обезличенными документами + mapping.csv) +``` + +--- + +## Требования к Flask-приложению для Штурвала + +### 1. `/health` — liveness probe + +```python +@health_bp.route("/health") +def health(): + return jsonify({"ok": True, "version": "0.0.26"}) +``` + +**Обязательно.** Штурвал дёргает `/health` для readiness/liveness checks. Без этого под не поднимется. Ответ должен быть `200 OK`. + +### 2. `no_cache` на все ответы + +```python +@app.after_request +def no_cache(response): + response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" + response.headers["Pragma"] = "no-cache" + response.headers["Expires"] = "0" + return response +``` + +**Критично для SPA-подобного UI.** Без этого nginx/браузер кэшируют страницу → F5 показывает старую версию, кнопки не работают, версия не обновляется. + +### 3. `X-Accel-Buffering: no` для SSE + +```python +return Response( + stream_with_context(generate()), + content_type="text/event-stream", + headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"} +) +``` + +**Критично для SSE-прогресса.** Без этого nginx буферизует поток → прогресс не отображается в реальном времени, все события приходят одним блоком в конце. + +### 4. `MAX_CONTENT_LENGTH` + +```python +app.config["MAX_CONTENT_LENGTH"] = 200 * 1024 * 1024 # 200 MB +``` + +Без этого Flask использует дефолтный лимит (~1MB) и отвергает docx/pdf. + +### 5. `GeneratorExit` в SSE-генераторе + +```python +try: + yield f"event: start\n..." +except GeneratorExit: + return # клиент отключился — не продолжаем обработку +``` + +**Критично.** При закрытии вкладки браузер рвёт SSE-соединение. Без `GeneratorExit` генератор продолжает слать запросы в LLM, тратя токены впустую. + +### 6. `stream_with_context` + +```python +from flask import stream_with_context + +return Response(stream_with_context(generate()), ...) +``` + +**Обязательно для SSE.** Без этого Flask держит приложение заблокированным на время SSE-соединения, другие запросы не обрабатываются. + +### 7. `sys.path` для импорта пакета drhider + +```python +_sys_path_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if _sys_path_root not in sys.path: + sys.path.insert(0, _sys_path_root) +``` + +Штурвал запускает из `site/`, пакет `drhider/` на уровень выше. Без этого импорт не работает. + +### 8. In-memory session storage + +Сессии хранятся в `site/session.py` как `Dict[str, dict]`. **Не персистентны** — теряются при рестарте пода. Очистка по таймауту (2 часа). + +### 9. LLM-клиент через переменные окружения + +```python +LLM_KEY # API ключ +LLM_URL # эндпоинт (по умолчанию https://api.aillm.ru/v1/chat/completions) +LLM_MODEL # модель (по умолчанию gpt-oss-120b) +``` + +### 10. BOM в CSV + +```python +buf.write('\ufeff'.encode('utf-8') + csv_str.encode('utf-8')) +``` + +BOM (`\ufeff`) нужен для корректного открытия CSV в Excel (чтобы русские буквы не ломались). │ ▼ TwoPassObfuscator.obfuscate(files)