Compare commits

172 Commits
Author SHA1 Message Date
“Naeel” 2a735e34f8 docs: план тест-режима 'только загрузка' (?upload-only=1) для проверки ВМ-загрузки без обработки
Deploy drhider / validate (push) Canceled after 0s
2026-08-23 07:04:48 +03:00
“Naeel” 7be1a1f649 docs: план универсального файлового сервиса — статус реализовано/задеплоено (v1.0.0, ВМ) 2026-08-21 16:23:49 +03:00
“Naeel” 7537e7ffe1 chore: убрать file_list.csv из репозитория (data-артефакт, не относится к коду)
Deploy drhider / validate (push) Canceled after 0s
2026-08-21 15:40:32 +03:00
“Naeel” 04f6d7f81a v0.0.58: ВМ-загрузка (паттерн ВМ-буфер+pull) — PUT на ВМ + /api/upload_refs (egress), nginx /drhider-upload/ + CORS + TTL
Deploy drhider / validate (push) Canceled after 0s
2026-08-21 15:40:24 +03:00
“Naeel” 705b39ab7d docs: актуализация README/ARCHITECTURE + план паттерна ВМ-буфер+pull (снимок перед ВМ-инкапсуляцией загрузки)
Deploy drhider / validate (push) Canceled after 0s
2026-08-21 15:35:45 +03:00
“Naeel” a9d4139afb v0.0.57: LLM обрабатывает ВСЕ данные — пофайловый чанкинг (6000/overlap 500), параллельность 4, дедуп+верификация против полного текста
Deploy drhider / validate (push) Canceled after 0s
2026-08-20 10:01:01 +03:00
“Naeel” da0ea5e964 docs: Q&A Sonnet по LLM-чанкингу (схема + 6 уточнений, решение к внедрению)
Deploy drhider / validate (push) Canceled after 0s
2026-08-20 09:58:56 +03:00
“Naeel” 670f533f48 v0.0.56: КРИТИЧНО — mapping.csv (ключ расшифровки) убран из выходного ZIP, CSV только отдельно
Deploy drhider / validate (push) Canceled after 0s
2026-08-20 09:38:28 +03:00
“Naeel” dfa325a5c5 v0.0.55: A1 threading для .doc (liberta), A2 кэш compiled regex — безопасное ускорение
Deploy drhider / validate (push) Canceled after 0s
2026-08-20 09:20:30 +03:00
“Naeel” 59a1924bd0 v0.0.54: В2 фикс кракозябр имён zip (utf-8+валидация→cp866), В1 фильтр таблиц (безопасен, эффект≈0)
Deploy drhider / validate (push) Canceled after 0s
2026-08-20 09:10:16 +03:00
“Naeel” 62067bc99f docs: ревью Sonnet — промпт, follow-up вопросы, Q&A с ответами 2026-08-20 09:04:03 +03:00
“Naeel” fe7c238c0b v0.0.53: устойчивость к битым файлам — воркер не падает, битые пропускаются, остальные обрабатываются
Deploy drhider / validate (push) Canceled after 0s
2026-08-20 08:30:30 +03:00
“Naeel” 3ae7d325e2 v0.0.52: файловый лог /tmp/drhider.log (LOG_FILE env) — логи видны в поде через kubectl exec
Deploy drhider / validate (push) Canceled after 0s
2026-08-20 08:12:23 +03:00
“Naeel” 59a66278a3 v0.0.51: максимальное логирование (LOG_LEVEL env) + детальные логи SSE/воркера для диагностики обрыва
Deploy drhider / validate (push) Canceled after 0s
2026-08-20 07:55:11 +03:00
“Naeel” 551ed8cf42 v0.0.50: лимиты загрузки (>100МБ/файл, >1ГБ сумма) — красная подсветка в таблице, исключение из обфускации
Deploy drhider / validate (push) Canceled after 0s
2026-08-20 07:25:02 +03:00
“Naeel” 0a94d3f449 docs: бенчмарк UI 5 прогонов DownLoads.zip (v0.0.49) 2026-08-19 19:59:45 +04:00
“Naeel” b30dd09e1f chore: .gitignore — добавить files/ (тестовые PDF)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 19:28:15 +04:00
“Naeel” 7389e1009a v0.0.49: индикатор распаковки архивов, разделение фаз (загрузка/обработка), таймауты SSE на ingress 1800с
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 18:49:55 +04:00
“Naeel” 4bdcde86b7 v0.0.48: версия (без понижения, откат фичи выбора папки остаётся в коде)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 18:43:03 +04:00
“Naeel” f5ca8c3eb5 Revert "v0.0.47: выбор целой папки (webkitdirectory) — рекурсивно, как раскрытие ZIP"
Deploy drhider / validate (push) Canceled after 0s
This reverts commit b5a14cfc3d.
2026-08-19 18:41:43 +04:00
“Naeel” b5a14cfc3d v0.0.47: выбор целой папки (webkitdirectory) — рекурсивно, как раскрытие ZIP
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 18:29:34 +04:00
“Naeel” 612979ed6e docs: удаление process_names + git push HTTP/2 проблема (решение HTTP/1.1)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 18:23:23 +04:00
“Naeel” d2f2bd39f4 v0.0.46: удалён тестовый API process_names (нельзя указывать локальные файлы — сервер не видит машину клиента)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 14:54:17 +04:00
“Naeel” 8d7a7dfaf6 v0.0.45: тестовый API process_names — обфускация файлов по именам (только при валидном TEST_INPUT_DIR)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 14:47:16 +04:00
“Naeel” 2224e20bde v0.0.44: во время LLM live-блок показывает 'ИИ анализирует все файлы', а не имя последнего файла
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 14:43:01 +04:00
“Naeel” 2f5a94bf3f docs: диагностика нестабильной загрузки + аннотации ingress + таймаут 300с
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 14:33:26 +04:00
“Naeel” 70a47ac905 v0.0.43: таймаут загрузки 30с → 300с (загрузка больших файлов нестабильна через шлюз) 2026-08-19 14:33:08 +04:00
“Naeel” c67fffbc56 v0.0.42: таймер в статусе тикает только у текущего файла (остальные останавливаются при start)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 14:00:10 +04:00
“Naeel” 0453827b8b v0.0.41: время на конкретный файл в колонке Статус (бэк считает, без общего LLM)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 13:46:00 +04:00
“Naeel” 020f9d43c6 v0.0.40: имя файла в live-блоке + сброс старых итогов при повторной обфускации
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 13:36:12 +04:00
“Naeel” b46198d74a dr
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 12:05:27 +04:00
“Naeel” bc09ada90e v0.0.39: оптимизация apply_replacements — однопроходная замена вместо N×re.sub (ускорение в разы)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 09:47:16 +04:00
“Naeel” e548c89880 v0.0.38: живой таймер обработки + индикация ИИ (live-блок, heartbeat llm в SSE)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 09:25:01 +04:00
“Naeel” defbc10004 v0.0.37: крупный заметный блок статистики (общее время, время ИИ, токены) в UI
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 09:16:18 +04:00
“Naeel” af9faba3fc v0.0.36: подсказка форматов на странице — добавлены .doc и .md
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 09:10:33 +04:00
“Naeel” 5677c59261 v0.0.35: при раскрытии ZIP — только документы (pdf, doc, docx, txt, md), изображения пропускаются
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 09:08:31 +04:00
“Naeel” 02d9592eb5 v0.0.34: вывод токенов и времени LLM в UI (суммарно за прогон)
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 09:00:40 +04:00
“Naeel” 53a1fb40d2 v0.0.33: раскрытие ZIP в таблицу (фронт) — нативный unzip, рекурсия, дедуп по размер+дате
Deploy drhider / validate (push) Canceled after 0s
2026-08-19 08:36:41 +04:00
“Naeel” 0ad5a5fabd test: .doc заглушка — актуальный формат ошибки сервиса; docs: итоги v0.0.32
Deploy drhider / validate (push) Canceled after 0s
2026-08-18 23:08:43 +04:00
“Naeel” 94e588aaba v0.0.32: фиксы по ревью — единый обфускатор, рекурсия ZIP, кириллица 1С, дедуп, SSE-дисконнект
Deploy drhider / validate (push) Canceled after 0s
2026-08-18 23:03:31 +04:00
Repinoid c3c6e5b584 Добавлены списки поступающих в магистратуру 45.04.02 Лингвистика (очная, бюджет) и общие коды: Сеченов, МГУ ФИЯР, МГЛУ (2026-08-04) 2026-08-04 21:06:50 +04:00
“Naeel” 301ceecaa8 docs: v0.0.30-31 — .doc поддержка, обновление версии 2026-07-16 12:24:47 +04:00
“Naeel” cb6274a887 fix: .doc теперь обрабатывается (обфускация + md в архиве) 2026-07-16 12:16:54 +04:00
“Naeel” 87a6df45c5 v0.0.30: .doc поддержка — accept + liberta API вместо LibreOffice 2026-07-16 11:28:16 +04:00
“Naeel” c102d54887 chore: gitignore + clean 2026-07-16 06:54:06 +04:00
“Naeel” a182a2f38a docs: MIGRATION-GUIDE — дополнен фиксами из v0.0.27-29 (fetch, dedup, Blob download) 2026-07-16 06:53:19 +04:00
“Naeel” 5f0ca8e463 docs: PROBLEM-AND-SOLUTION.md — переписан, только факты, без ложных следов 2026-07-16 06:50:29 +04:00
“Naeel” 8484197eec docs: финальный диагноз — keep-alive 10 единственная причина RST 2026-07-15 19:15:17 +04:00
“Naeel” 6e8f79ac56 docs: итоговая диагностика RST — hostPort vs cloud LB, корневая причина 2026-07-15 19:09:24 +04:00
Repinoid 2aea7bf2f6 fix: move cluster dump TMP → History 2026-07-15 18:46:35 +04:00
Repinoid 0ff904ceb6 cluster dump 2026-07-15: ingress, kube-vip, drhider state 2026-07-15 18:34:43 +04:00
“Naeel” fd4f152fc8 chore: убрал большие файлы из git 2026-07-15 15:18:25 +04:00
“Naeel” d205118cd5 chore: всё закоммичено — TMP, gitignore, chrome log 2026-07-15 15:15:06 +04:00
“Naeel” 4af247938f docs: полное состояние проекта на 2026-07-15 2026-07-15 15:14:20 +04:00
“Naeel” 0640e7d47f docs: формат имён скачиваемых файлов в архитектуру 2026-07-15 15:11:11 +04:00
“Naeel” 5efed1c577 fix: имя файла из Content-Disposition а не хардкод 2026-07-15 15:06:43 +04:00
“Naeel” 40372fe03f fix: Выбрать файлы добавляет а не стирает таблицу + dedup 2026-07-15 15:01:35 +04:00
“Naeel” e66dff23b8 fix: download ZIP/CSV через fetch+Blob вместо window.location — не сбрасывает страницу 2026-07-15 14:57:03 +04:00
“Naeel” 68fada075e chore: финальная документация — всё закоммичено 2026-07-14 22:02:25 +04:00
“Naeel” 338cb2f372 docs: инструкция миграции UI и загрузки с ВМ на Flask (для Сверки Контрактов) 2026-07-14 22:01:43 +04:00
“Naeel” 9b4060d8c5 docs: требования к Flask-приложению для Штурвала — health, no_cache, SSE, BOM, etc 2026-07-14 21:58:56 +04:00
“Naeel” 8158a8bc01 docs: полная конфигурация кластера — MTU + keep-alive + HTTP/2 + RST fix 2026-07-14 21:54:23 +04:00
“Naeel” 26572abb3b fix: JS warmup GET /health на загрузке + keep-alive 75 + bump v0.0.26 2026-07-14 20:40:26 +04:00
“Naeel” cd29e727f3 fix: uploadFiles не теряет файлы после resetAll — сохраняет sf.slice() 2026-07-14 19:31:44 +04:00
“Naeel” e513bf3c9e fix: es → activeES во всех местах (был не заменён на строке 262) 2026-07-14 19:29:29 +04:00
“Naeel” 17bc4aebf8 fix: сброс состояния при F5/закрытии — beforeunload, abort XHR, close EventSource 2026-07-14 19:22:36 +04:00
“Naeel” b3298b415e docs: PROBLEM-AND-SOLUTION, правила, история Connection:close, gitignore 2026-07-14 19:19:12 +04:00
“Naeel” 5e7319cd42 fix: убрать Connection: close — Waitress/WSGI запрещает hop-by-hop заголовки 2026-07-14 13:28:00 +04:00
“Naeel” 204e865aff docs: исправлен PROBLEM-AND-SOLUTION.md — mtu 1400 а не 1450, убран раздел Опуса, обновлены результаты 2026-07-14 13:27:06 +04:00
“Naeel” 6fe1ba2b42 fix: Connection: close на всех ответах — nginx не кэширует upstream keepalive 2026-07-14 13:21:31 +04:00
“Naeel” 0ceaee1089 fix: XHR + cache-busting ?_=Date.now() чтобы исключить connection reuse 2026-07-14 10:31:26 +04:00
“Naeel” b6dac6f2bd fix: XHR timeout 30с + ontimeout — не висеть бесконечно на загрузке 2026-07-14 10:24:55 +04:00
“Naeel” 79cd25a762 fix: SSE — отлов GeneratorExit при отключении клиента
- api_bp.py: try/except GeneratorExit после каждого yield в generate()
- при F5 во время обработки поток освобождается, остальные файлы не обрабатываются
- bump: 0.0.17 → 0.0.18
2026-07-14 09:15:44 +04:00
“Naeel” bbffb00eb8 fix: убрать .doc из UI и документации (LibreOffice недоступен на Штурвале) 2026-07-14 08:30:37 +04:00
“Naeel” ddbf67924b docs: добавить раздел Kubernetes-кластер в DEPLOY.md (kubectl, ingress, cilium) 2026-07-14 07:42:32 +04:00
“Naeel” 00d54dbfb6 feat: поддержка .doc через LibreOffice headless
- extractor.py: добавлена doc_to_markdown() — .doc → .docx (soffice) → docx_to_markdown()
- Dockerfile: apt install libreoffice-writer
- ARCHITECTURE.md: обновлён статус .doc
- bump: 0.0.16 → 0.0.17
2026-07-14 07:21:39 +04:00
“Naeel” 444ef4b732 docs: добавить раздел «Поддерживаемые форматы» в ARCHITECTURE.md 2026-07-14 07:04:42 +04:00
“Naeel” f072fc9fdb docs: уточнение — реконсил по событию, не по таймеру 2026-07-13 22:18:03 +04:00
“Naeel” f38ba4ca37 docs: финальный обзор Опуса + правки (Ethernet, 51с) 2026-07-13 21:57:25 +04:00
“Naeel” 82b5cb4402 docs: схема провайдерской инкапсуляции + таблица параметров 2026-07-13 21:53:15 +04:00
“Naeel” ec9107cff4 docs: eth0=1500, bottleneck в сети провайдера, 1450 empirically 2026-07-13 21:49:36 +04:00
“Naeel” fe45b07c4f docs: ping DF блокирован файрволлом, 1450 подтверждён эмпирически 2026-07-13 21:48:00 +04:00
“Naeel” cd22fd62a6 docs: исправлены ошибки (TCP backoff, DSR, ping) + ответ Опуса 2026-07-13 21:45:44 +04:00
“Naeel” 5e4418f648 docs: убраны домыслы — причина 1450 неизвестна, факт установлен замером 2026-07-13 21:38:02 +04:00
“Naeel” 2949d0ef09 docs: пояснение — почему Cilium ожидает 1500 2026-07-13 21:36:21 +04:00
“Naeel” 1ad35dada7 docs: flowchart — Geneve над данными через --- 2026-07-13 21:34:55 +04:00
“Naeel” ae09c23d81 docs: Geneve над данными, без жаргона 2026-07-13 21:33:04 +04:00
“Naeel” d0f12ef4a3 docs: горизонт + 50 над 1450 в subgraph 2026-07-13 21:30:59 +04:00
“Naeel” a34a78d8b9 docs: вертикальная схема — шапка сверху, тело снизу, дверь не пускает 2026-07-13 21:29:38 +04:00
“Naeel” ab08acd60d docs: Mermaid — пакет + шапка = не влазит в дверь 2026-07-13 21:28:12 +04:00
“Naeel” f005e8cf29 docs: простая ASCII-схема — рост пакета vs дверь 2026-07-13 21:26:41 +04:00
“Naeel” 1da496c2e5 docs: схема яснее — один путь от А к Б через Geneve 2026-07-13 21:25:13 +04:00
“Naeel” 17fc2670a5 docs: Mermaid flowchart вместо block-beta 2026-07-13 21:22:28 +04:00
“Naeel” 7ed1f5d650 docs: Mermaid-диаграмма вместо ASCII 2026-07-13 21:21:19 +04:00
“Naeel” 3df63df0b0 docs: ASCII-схема пакета Geneve и MTU 2026-07-13 21:20:28 +04:00
“Naeel” 75568a2586 docs: пояснение Cilium и Geneve курсивом перед списком 2026-07-13 21:18:02 +04:00
“Naeel” 9ea3c2991a docs: причина расписана по шагам с жирными числами 2026-07-13 21:15:48 +04:00
“Naeel” cbf7de27d8 docs: результаты в тестовом кластере + рекомендации для production 2026-07-13 21:06:19 +04:00
“Naeel” d94fa3f556 v0.0.16: Nubes логотип, favicon, версия слева, модальное окно Help 2026-07-13 18:11:52 +04:00
“Naeel” 7d39885d90 docs: проблема описана обще — HTTP POST на managed Flask через ingress 2026-07-13 18:02:38 +04:00
“Naeel” e7da82538f docs: безличный стиль — «были применены» вместо «мы» 2026-07-13 18:00:27 +04:00
“Naeel” a91a5e9538 docs: уточнено — что именно сделано сейчас (Local + 4 реплики) 2026-07-13 17:55:21 +04:00
“Naeel” c25b22cfe6 docs: PROBLEM-AND-SOLUTION v2 — полный анализ Опуса 4.8 2026-07-13 17:49:42 +04:00
“Naeel” 68571fbbcd docs: PROBLEM-AND-SOLUTION — корневой .md для девопса 2026-07-13 17:45:36 +04:00
“Naeel” eaef850421 docs: замер MTU — confirmed underlay=1450, tunnel=1400 2026-07-13 17:42:03 +04:00
“Naeel” 0950b68649 docs: MSS-CLAMPING-PLAN v2 — исправлено после валидации Opus (underlay MTU, замер, рестарт) 2026-07-13 17:29:12 +04:00
“Naeel” 322626a372 docs: запрос Opus — валидация MSS Clamping плана для девопса 2026-07-13 17:22:54 +04:00
“Naeel” f9b88cbf93 v0.0.15: fix _md_tolerant_pattern — всегда, не только при промахе 2026-07-13 17:18:00 +04:00
“Naeel” a56a819329 v0.0.14: MSK time (UTC+3) — ZIP entries + download filenames 2026-07-13 17:05:16 +04:00
“Naeel” f3c854c5fb v0.0.13: fix Markdown-bold splitting + \xa0 in PERSON_PATTERN 2026-07-13 13:18:10 +04:00
“Naeel” 23b4ad9889 docs: ответ Sonnet — фикс Markdown-жирности и \xa0 в PERSON_PATTERN 2026-07-13 13:14:42 +04:00
“Naeel” 29e2d08cdb docs: MSS-CLAMPING-PLAN — корневое решение вместо Local+4реплики 2026-07-13 13:12:55 +04:00
“Naeel” 0cdef67c1f docs: ответ Sonnet — TMP-файлы от старого кода, _next_token() чист 2026-07-13 12:41:08 +04:00
“Naeel” 8c59c4b7e0 docs: запрос Sonnet — «токены» заменены на «коды замены» 2026-07-13 12:35:16 +04:00
“Naeel” 70226d8c39 docs: запрос Sonnet — анализ токенов обфускации 2026-07-13 12:33:03 +04:00
“Naeel” 4a4de0f8e2 docs: INGRESS-SOLUTIONS — добавлен Opus анализ + helm upgrade команды 2026-07-13 11:55:36 +04:00
“Naeel” 38109ba6ec docs: INGRESS-SOLUTIONS — Sonnet анализ вариантов DaemonSet/MTU/DSR 2026-07-13 11:53:47 +04:00
“Naeel” adab2c624f v0.0.12: fix download — window.location вместо a.click() 2026-07-13 11:44:43 +04:00
“Naeel” a380c0e70c docs: KUBEVIP — Helm auto-reset root cause, updated pod list 2026-07-13 11:39:50 +04:00
“Naeel” bbc45e155f v0.0.11: fix download — a.appendTo(body) перед click 2026-07-13 11:13:28 +04:00
“Naeel” 7bdcb5b7b4 v0.0.10: fix download filename — timestamp локальное время 2026-07-13 11:09:20 +04:00
“Naeel” c878b3db1a v0.0.9: per-file timer — ⏳ 3.2с → ✓ 12.5с 2026-07-13 11:06:52 +04:00
“Naeel” bf202cdfa4 v0.0.8: fix SSE format — event: строка отдельно от data: 2026-07-13 10:59:03 +04:00
“Naeel” e3405556b5 v0.0.7: SSE per-file progress — таблица обновляется по каждому файлу 2026-07-13 10:55:43 +04:00
“Naeel” 46d8ced0dd v0.0.6: прогресс загрузки в строке (XMLHttpRequest onprogress) 2026-07-13 10:52:09 +04:00
“Naeel” e4975fb37e v0.0.5: CSV отдельно, timestamp имена, прогресс в таблице 2026-07-13 10:40:00 +04:00
“Naeel” 613b645456 v0.0.4: fix — restore index.html (accidentally deleted) 2026-07-13 10:33:56 +04:00
“Naeel” b7075dd8bf docs: KUBEVIP-EXTERNAL-TRAFFIC-POLICY — ingress replicas fix 2026-07-13 10:33:30 +04:00
“Naeel” 75c7424963 v0.0.3: таблица + кнопка, без JS 2026-07-13 10:14:22 +04:00
“Naeel” 0d16bb6520 v0.0.2: minimal Hello World — только шапка Nubes + версия 2026-07-13 10:10:06 +04:00
“Naeel” 491b49f633 v0.0.1: revert to v7.0.0 code — api_bp unified, no Connection close, version reset 2026-07-13 10:04:15 +04:00
“Naeel” 76a2d4b7ed v0.0.3: fix Connection header banned by WSGI PEP 3333 2026-07-13 09:25:07 +04:00
“Naeel” c35e8da357 v0.0.2: split api_bp into upload/process/download modules, Connection: close, upload tests (13 new, 106 total) 2026-07-13 09:15:38 +04:00
“Naeel” 345448caa4 reset version 0.0.1 2026-07-13 08:45:52 +04:00
“Naeel” 18922b486b v7.0.0: API upload/process/download, session.py, фронт без обработки — всё на бэкенде 2026-07-13 08:12:20 +04:00
“Naeel” e590d8ab9a v6.0.0: вся обработка на бэкенде — zip.js удалён, фронт только XHR+скачивание 2026-07-13 07:53:08 +04:00
“Naeel” cbf619aaba docs: принципы 6-7 — вся обработка на бэкенде, пофайлово, фронт только UI 2026-07-13 07:48:19 +04:00
“Naeel” 0998892d7f v5.2.1: Dockerfile как у шаблона Baldurs-Gate (CMD python site/app.py) — без него ingress дропает соединения 2026-07-13 07:31:28 +04:00
“Naeel” d0e874d87e v5.2.0: waitress вместо app.run() — production WSGI, 8 потоков, исправляет ERR_TIMED_OUT 2026-07-12 16:05:44 +04:00
“Naeel” 81bdee6203 v5.1.0: тесты builder(20), extractor(15), scanner(20), replacer(10) — 65 тестов, 0 ошибок 2026-07-12 15:31:42 +04:00
“Naeel” efaa42dd2e v5.0.9: ZIP модуль вынесен в site/static/zip.js, тесты test_zip.py (28 тестов), исправлен баг смещения start+18 2026-07-12 15:25:39 +04:00
“Naeel” 9145345882 fix: баг в unzip() — неверное смещение compressed size (+14 вместо start+18) 2026-07-12 15:01:33 +04:00
“Naeel” ee42865fcd fix: убран Connection: close — каждый статик-файл требовал новый TCP handshake 2026-07-12 14:55:16 +04:00
“Naeel” 3ba201748c минимизация copilot-instructions.md (3 строки) + CODERULES.md отдельно 2026-07-12 14:53:38 +04:00
“Naeel” f98b4ba7a5 fix: убрал правило из copilot-instructions.md (экономия токенов), оставил в ARCHITECTURE.md 2026-07-12 14:51:40 +04:00
“Naeel” a1574677a5 docs: правило «ноль внешних зависимостей» в ARCHITECTURE.md и copilot-instructions.md 2026-07-12 14:50:42 +04:00
“Naeel” 1a84770e82 v5.0.2: нативный ZIP (unzip + buildZip) — 0 внешних зависимостей, страница грузится мгновенно 2026-07-12 14:48:59 +04:00
“Naeel” 75b8476703 fix: JSZip локально вместо CDN — убирает задержку 10с 2026-07-12 14:42:25 +04:00
“Naeel” 370a869aef v5.0.0: гибридные токены (Иванов_0001, ООО_Технология_0034), удалены generators/, checksum, random_utils 2026-07-12 14:30:12 +04:00
“Naeel” 62c7ea0162 v4.0.2: бегущий секундомер в колонке статуса (⏳ 5с, ⏳ 6с...) 2026-07-12 14:19:44 +04:00
“Naeel” df15f48ecc fix: Connection: close в after_request — XHR не держат keep-alive после запросов 2026-07-12 14:12:43 +04:00
“Naeel” adf3002e3d v4.0.0: дизайн Nubes, логотип, ZIP без mapping.csv внутри, CSV отдельно с датой-временем 2026-07-12 13:52:24 +04:00
“Naeel” 59f7601266 fix: no-cache заголовки (Cache-Control, Pragma, Expires) — F5 больше не кэширует 2026-07-12 13:11:48 +04:00
“Naeel” f15af2866d fix: дата/время файлов в ZIP (time.localtime), bump 3.2.3 2026-07-12 13:07:07 +04:00
“Naeel” 1d91d5f92e bump 3.2.1 → 3.2.2 2026-07-12 12:59:16 +04:00
“Naeel” fd5e1613a5 fix: ссылки в copilot-instructions.md (../ вместо ./) 2026-07-12 12:58:29 +04:00
“Naeel” 303830528f bump 3.2.0 → 3.2.1 2026-07-12 12:56:39 +04:00
“Naeel” 3d55562f02 fix: безопасный re.match в генераторах + сброс fileInput при загрузке страницы 2026-07-12 12:54:40 +04:00
“Naeel” dacf0d584f v3.2.0: новый LLM-промпт (STEP 1/2, фикс. типы, NOT PII, already_found), убран хардкод НУБЕС 2026-07-12 11:14:26 +04:00
“Naeel” 5c426cb037 v3.2.0: новый LLM-промпт (STEP 1/2, фикс. типы, NOT PII, already_found), убран хардкод НУБЕС 2026-07-12 11:11:43 +04:00
“Naeel” 0c2b636d31 v3.1.0: пофайловая обработка, прогресс в таблице, JSZip — один ZIP на выходе 2026-07-12 10:50:51 +04:00
“Naeel” a87361c159 bump version 3.0.1 → 3.0.2 2026-07-12 10:33:54 +04:00
“Naeel” 7fc41aabce frontend: пофайловая загрузка с прогрессом, убран gunicorn 2026-07-12 10:33:09 +04:00
“Naeel” 2731bc7f09 fix: вернул gunicorn+gevent — без них Flask dev server не держит долгие LLM-запросы 2026-07-12 10:18:04 +04:00
“Naeel” 724124b3fc fix: убрал тестовый файл из репы, добавил в .gitignore 2026-07-12 10:17:01 +04:00
“Naeel” 72ae915935 fix: app.run(threaded=True) для долгих запросов 2026-07-12 10:16:52 +04:00
“Naeel” 3e5aef70a0 fix: добавлен app.run() — Штурвал запускает python app.py, нужен listen на :5000 2026-07-12 09:43:34 +04:00
“Naeel” 34f9a6f74f fix: импорт routes в app.py (site конфликтует со stdlib, нельзя from .routes) 2026-07-12 09:29:59 +04:00
“Naeel” 84ae7439f5 History: v3.0.0 завершён 2026-07-12 09:15:13 +04:00
“Naeel” 2a2cc1467c v3.0.0: универсальная архитектура — MD-пайплайн, LLM сама определяет типы, fallback-генератор 2026-07-12 09:14:45 +04:00
“Naeel” f5427b0db7 replacer.py: удалён replace_in_text (заменён на apply_replacements напрямую) 2026-07-12 09:14:22 +04:00
“Naeel” af2f2a9f71 extractor.py → MD-пайплайн (docx_to_markdown, pdf_to_markdown), obfuscator.py — новый пайплайн без docx-объектов 2026-07-12 09:13:47 +04:00
131 changed files with 9152 additions and 1229 deletions
+24
View File
@@ -0,0 +1,24 @@
# ⛔ ЖЁСТКИЕ ПРАВИЛА
- НЕЛЬЗЯ менять код без «делай» — показать план → ждать
- НЕЛЬЗЯ менять ЧТО-ЛИБО при вопросе (?, почему, как, где, что) — только ответ словами
- НЕЛЬЗЯ спешить — обдумать, проверить, потом отвечать
- НЕЛЬЗЯ лезть в /home/naeel/nubes/contracts/ (кроме loadtest)
- НЕЛЬЗЯ предполагать — сомневаешься = спроси
- НЕЛЬЗЯ грузить внешние CDN/библиотеки на фронтенд — всё в коде
# ✅ ПОРЯДОК ПРАВКИ
1. План → «делай»
2. Правим → проверяем syntax → проверяем import
3. git add -A && git commit -m "..." && git push
4. Bump VERSION в site/app.py перед push
# 📋 ДОКИ
- Порядок правок: docs/DEVELOPMENT.md
- Архитектура: docs/ARCHITECTURE.md
- Деплой: docs/DEPLOY.md
- История: History/
# 📋 ПРОЕКТ
Штурвал Managed Flask, без Dockerfile/gunicorn, без БД
URL: https://drhider.pythonk8s.dev.nubes.ru/
ВМ legacy: ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213
+7 -48
View File
@@ -1,48 +1,7 @@
# ⛔ ПРАВИЛА ДЛЯ COPILOT — DRHIDER
Читать перед ЛЮБЫМ действием.
---
## ⛔⛔⛔ НИЧЕГО НЕ ДЕЛАТЬ БЕЗ «ДЕЛАЙ»
Любой вопрос, обсуждение, анализ — **НЕ повод менять код.**
Только после явного: «делай», «делайте», «давай», «приступай», «пушить», «commit», «push».
## ⛔⛔⛔ НЕ МЕНЯТЬ КОД БЕЗ РАЗРЕШЕНИЯ
Даже если ошибка очевидна. **Показать → описать → ЖДАТЬ.**
## ⛔⛔⛔ НЕ СПЕШИТЬ
Обдумать. Проверить. Перепроверить. Только потом отвечать.
## ⛔⛔⛔ НЕ ПРЕДПОЛАГАТЬ, НЕ ДОГАДЫВАТЬСЯ
Сомневаешься — **ОСТАНОВИСЬ И СПРОСИ.**
## ⛔⛔⛔ НЕ ЛЕЗТЬ В ЛОКАЛЬНЫЕ ПАПКИ CONTRACTS
Код drhider — на ВМ (`5.172.178.213` через SSH). Исключение: `/home/naeel/nubes/loadtest/`.
---
## 📋 Что есть в проекте
| Что нужно | Читай |
|---|---|
| Меняешь код? | [`docs/DEVELOPMENT.md`](docs/DEVELOPMENT.md) — полный порядок действий |
| Архитектура? | [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — схема, модули, поток данных |
| Деплой/ВМ/env? | [`docs/DEPLOY.md`](docs/DEPLOY.md) — всё про окружение |
| Обзор проекта? | [`README.md`](README.md) — структура, API, правила |
| История изменений? | [`History/`](History/) — что когда и зачем делалось |
---
## 📋 Быстрая справка
- Платформа: **Штурвал** (Managed Flask, без Dockerfile/gunicorn)
- URL: https://drhider.pythonk8s.dev.nubes.ru/
- БД: **нет**, всё в памяти
- Дизайн фронтенда: `/home/naeel/nubes/design/`
- ВМ (legacy): `ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213`
# ⛔⛔⛔ STOP: без «делай» ничего не делать
# ⛔⛔⛔ ВОПРОС (?, почему, как, где, что) — ТОЛЬКО ОТВЕТ. Читать/узнавать МОЖНО, менять НЕЛЬЗЯ.
# ⛔ Не спешить. Не лезть в contracts/. Не гадать — спросить.
НИКОГДА не действовать по догадкам !!!
ВСЕГДА всё перепроверять перед действием или ответом !!!
ВСЕГДА КРАТКО без воды отвечать, чётко по делу, без лишних слов.
# 📋 Правила: .github/CODERULES.md
+6
View File
@@ -1,3 +1,9 @@
__pycache__/
*.pyc
.env
# TMP/
# about1500.md
*.har
chrome-net-export-log.json
TSTFILES/
files/
+1
View File
@@ -0,0 +1 @@
python3
+1
View File
@@ -0,0 +1 @@
/usr/bin/python3
+1
View File
@@ -0,0 +1 @@
python3
+1
View File
@@ -0,0 +1 @@
lib
+5
View File
@@ -0,0 +1,5 @@
home = /usr/bin
include-system-site-packages = false
version = 3.12.3
executable = /usr/bin/python3.12
command = /usr/bin/python3 -m venv /home/naeel/nubes/drhider/.venv
+3
View File
@@ -0,0 +1,3 @@
{
"python-envs.defaultEnvManager": "ms-python.python:system"
}
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
libreoffice-writer \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY site /app/site
COPY drhider /app/drhider
EXPOSE 5000
CMD ["python", "site/app.py"]
@@ -0,0 +1,32 @@
# Запрос к Соннету — ERR_TIMED_OUT при открытии нового окна (2026-07-12)
## Симптом
DrHider на Managed Flask (Штурвал/pythonk8s). При открытии страницы в НОВОМ окне браузера — ERR_TIMED_OUT (20+ секунд). Старые вкладки работают нормально.
## Факты
- Изнутри кластера (kubectl exec → curl): HTTP 200 за 2.1 секунды
- Снаружи (браузер, curl с локальной машины): таймаут 10+ секунд
- v1 работало с gunicorn (CMD: `gunicorn --bind :5000 --worker-class gevent`)
- Сейчас: `app.run(host="0.0.0.0", port=5000)` — Flask dev server (Werkzeug)
- Страница: 12 KB HTML, 2 статических файла (logo.svg, favicon.svg — оба 2.8 KB)
- Ноль внешних зависимостей (никаких CDN, шрифтов, JS-библиотек)
- HTTP-заголовки: Cache-Control: no-cache, no-store, must-revalidate
- Штурвал запускает: cd site && python app.py
## Вопросы
1. Может ли Flask dev server (Werkzeug) быть причиной ERR_TIMED_OUT на новых соединениях, в то время как gunicorn работал нормально?
2. Если да — какой механизм? (keep-alive, thread pool, socket backlog?)
3. Если нет — что ещё может вызывать таймаут именно на НОВЫХ окнах при работающих старых?
4. Нужно ли вернуть gunicorn или есть другой способ заставить app.run() работать стабильно?
5. Может ли `Cache-Control: no-store` заставлять браузер делать лишние запросы при открытии нового окна?
## Контекст платформы
Штурвал — Managed Flask на Kubernetes:
- Запускает: `cd /var/www && pip install -r requirements.txt && cd site && python app.py`
- Readiness probe: `tcp-socket :5000`
- Нет Dockerfile, нет nginx перед приложением
- Ingress: внешний LoadBalancer → ClusterIP сервис → под
+83
View File
@@ -0,0 +1,83 @@
# Запрос к Соннету — анализ результатов DrHider v3 (2026-07-12)
## Контекст
DrHider v3 — обфускация документов через LLM + Markdown. Результаты уже есть.
Покажи Соннету реальный mapping.csv и фрагмент обфусцированного .md.
## Промпт для Соннета
Вот текущий LLM-промпт в scanner.py:
```
You are a PII detector. Find ALL sensitive or private information in the text below.
Return a JSON array. Each item must have:
- "type": short snake_case label describing the entity
(e.g. person_name, phone, email, address, inn, company, passport,
contract_number, employee_id, bank_account — WHATEVER you see)
- "value": the EXACT string from the text (copy verbatim)
Rules:
- Copy value VERBATIM — same spaces, punctuation, case as in text
- If same value appears multiple times — include only once
- Invent the type label yourself based on what you see
- Return ONLY the JSON array, no other text, no markdown wrapping
```
Результат mapping.csv (первые 10 строк):
```csv
тип_данных,оригинал,замена
text,"""****XXX****001****""","""****IPD****084****"""
text,"""НУБЕС""","""ФИОЩЦ"""
phone,+7 (495) 789-41-35,+7 (343) 986-75-93
email,info@nubes.ru,bxkuno@company.local
company,"ЗАО ""****XXX****001****""",ООО «Спектр»
company,"ЗАО ""XXX001""",ЗАО «Меридиан»
inn_ul,ИНН 9706005293,ИНН 9325731296
text,Исполнителем Заказчику Сторонами,Морозов М.П.
kpp,КПП 772401001,КПП 051543039
```
Фрагмент обфусцированного .md:
```markdown
**ООО «Спектр»** именуемое в дальнейшем "Заказчик", в лице Генерального директора ФИО,
действующего на основании Устава, и **ООО **"**НУБЕС**"**, именуемое в дальнейшем
"Исполнитель", в лице Генерального директора Степанов Н.М.
```
## Проблемы
1. **Контекстная роль Исполнителя** — сервис должен быть УНИВЕРСАЛЬНЫМ, без хардкода конкретных названий. LLM должна из контекста понимать: «вот эта компания — Исполнитель (сторона договора, оказывающая услуги), её НЕ заменяем». Как научить LLM этому без указания конкретных имён?
2. **Тип "text" для многих сущностей** — LLM не всегда правильно определяет тип (например, "Исполнителем Заказчику Сторонами" — это не ПДн, а просто текст договора)
3. **Regex находит структурированное (ИНН, телефон), LLM — остальное** — есть дублирование
## Вопросы
1. Как научить LLM определять Исполнителя из контекста договора (без хардкода имён) и НЕ заменять его данные?
2. Как заставить LLM точнее определять тип сущности (не "text" для всего подряд)?
3. Нужно ли добавить в промпт примеры того что НЕ является ПДн (юридические термины, роли сторон)?
4. Стоит ли передавать LLM уже найденное regex'ом чтобы избежать дублирования?
5. Правильно ли что промпт на английском, а документы на русском?
## Текущий промпт (полностью)
```
You are a PII (Personally Identifiable Information) detector.
Find ALL sensitive or private information in the text below.
Return a JSON array. Each item must have:
- "type": short snake_case label describing the entity
(e.g. person_name, phone, email, address, inn, company, passport,
contract_number, employee_id, bank_account — WHATEVER you see)
- "value": the EXACT string from the text (copy verbatim)
Rules:
- Copy value VERBATIM — same spaces, punctuation, case as in text
- If same value appears multiple times — include only once
- Invent the type label yourself based on what you see
- Return ONLY the JSON array, no other text, no markdown wrapping
Text:
{combined[:8000]}
```
@@ -0,0 +1,47 @@
# Запрос к Соннету — нумерация вместо генерации фейков (2026-07-12)
## Контекст
DrHider обфусцирует документы: ищет ПДн → заменяет на фиктивные значения.
Сейчас используется генерация "похожих" фейков (Иванов И.И., ООО «Ромашка», +7(495)...).
## Проблема
1. **Адреса → абракадабра.** Ул. Стекольная → иг. 8ц Лжчюсвхшбр. Потому что fallback-генератор меняет кириллицу на случайную кириллицу.
2. **Сложность.** 11 генераторов под каждый тип данных. Каждый надо поддерживать.
3. **Ложное ощущение реальности.** Фейковые ИНН с правильной контрольной суммой выглядят как настоящие.
## Идея
Заменить всю генерацию на **нумерацию по типам:**
| Оригинал | Замена |
|---|---|
| Иванов И.И. | `Лицо_0042` |
| ООО «НУБЕС» | `Компания_0003` |
| ул. Стекольная, д.7 | `Адрес_0015` |
| +7 (495) 789-41-35 | `Телефон_0007` |
| info@nubes.ru | `Email_0004` |
| ИНН 9706005293 | `ИНН_0002` |
| Р/с 40702... | `Счёт_0001` |
| договор-XXX001.docx | `Файл_0006` |
## Вопросы
1. **Плюсы/минусы** нумерации vs генерации фейков для задачи обфускации документов?
2. **Как нумеровать?** Глобальный счётчик на каждый тип? Или в рамках одного документа?
3. **Стоит ли сохранять формат?** Например `Телефон_0007` vs `+7 (000) 000-00-07`?
4. **mapping.csv** — при нумерации он становится главным документом аудита. Это ок?
5. **UUID вместо номеров?** `Лицо_a3f4b2` — лучше или хуже последовательных номеров?
## Текущий mapping.csv (для контекста)
```csv
тип_данных,оригинал,замена
phone,+7 (495) 789-41-35,+7 (495) 906-87-80
email,info@nubes.ru,ypiorej@company.local
company,"ЗАО ""XXX001""",ООО «Альянс»
inn_ul,ИНН 9706005293,ИНН 1846691249
text,Исполнителем Заказчику Сторонами,Кузнецов О.В.
text,"115404, г. Москва, ул. 1-я Стекольная, д.7с2","084810, б. Ввжзгд, гф. 6-в Фтпжыобнзщ, я.8с1"
```
@@ -0,0 +1,54 @@
# Запрос к Соннету — нумерация vs фейки для downstream LLM (2026-07-12)
## Контекст
Я делаю сервис **DrHider** — обфускация документов. Находит ПДн → заменяет.
После обфускации документы идут на дальнейшую LLM-обработку — **сверка спецификаций**
(другой сервис contracts, извлекает типы/номера/даты/контрагентов через LLM).
Сейчас генерация фейков: `Иванов И.И.` → `Петров А.С.`, `ООО «Ромашка»` → `ООО «Спектр»`.
**Проблема:** для неизвестных типов (адреса, нестандартные ID) — character-class замена
даёт абракадабру: `ул. Стекольная` → `иг. 8ц Лжчюсвхшбр`. Downstream LLM не может это обработать.
## Варианты замены
### А. Чистая нумерация
`Иванов И.И.` → `Лицо_0042`, `ООО «Ромашка»` → `Компания_0003`, `ул. Ленина` → `Адрес_0015`
Плюс: никакой абракадабры. Минус: LLM теряет контекст («Лицо_0042 заключил договор» — не понимает семантику).
### Б. Фейки (сейчас)
`Иванов И.И.` → `Петров А.С.`, `ООО «Ромашка»` → `ООО «Спектр»`
Плюс: LLM понимает роли. Минус: сложно (11 генераторов), ложное ощущение реальности,
для неизвестных типов — абракадабра.
### В. Гибрид: шаблон + номер
`Иванов И.И.` → `Иванов_5677`, `ООО «Ромашка»` → `ООО_Технология_0034`,
`ул. Стекольная, д.7` → `ул_Ленина_0015`, `+7 (495) 789-41-35` → `+7_495_000_0042`,
`info@nubes.ru` → `email_0007@fake`
Плюс: LLM видит что это человек/компания/адрес, номер гарантирует уникальность и обфускацию.
Минус: всё равно нужны шаблоны для каждого типа.
## Вопросы
1. Какой вариант лучше для downstream LLM-обработки (извлечение контрагентов, дат, номеров)?
2. Вариант В — достаточно ли LLM поймёт что `Иванов_5677` это персональные данные?
3. Для неизвестных типов — `[Данные_NNNN]` как fallback, ок?
4. Глобальная нумерация (сквозная через все документы пакета) vs локальная (в рамках одного файла)?
5. Стоит ли сохранять частичную структуру (например телефон `+7_495_000_0042` сохраняет формат номера)?
## Downstream задача (для контекста)
LLM в сервисе contracts получает текст документа и должна извлечь:
- Контрагент («с кем договор»)
- Номер и дату договора
- Список услуг с ценами (таблица)
Промпт для contracts LLM (фрагмент):
```
Извлеки из текста: тип документа, номер, дату, контрагента.
Верни JSON: {"type":"договор","number":"...","date":"...","counterparty":"..."}
```
@@ -0,0 +1,19 @@
# Ответ Соннета — ERR_TIMED_OUT (2026-07-12)
## Причина
Werkzeug dev server: **один поток на TCP-соединение**. Браузер держит 6 keep-alive соединений на вкладку. 6 потоков заняты idle-соединениями → backlog (5) переполнен → новое окно получает SYN-дроп → таймаут.
gunicorn+gevent: async I/O, idle-соединения не занимают ресурсы.
## Решение
Три варианта:
| Вариант | Что | Изменения |
|---|---|---|
| A (waitress) | Pure Python WSGI | `requirements.txt` + 2 строки в `app.py` |
| B (gevent) | Async, как gunicorn | `requirements.txt` + 2 строки в `app.py` |
| C (gunicorn) | Точно как v1 | `requirements.txt` + смена CMD в Штурвале |
Соннет рекомендует: **A** если Штурвал не меняет CMD, **C** если меняет.
+47
View File
@@ -0,0 +1,47 @@
# Ответ Соннета v2 — улучшение LLM-промпта (2026-07-12)
## Q1. Как научить LLM определять Исполнителя из контекста?
**Двухфазная инструкция в промпте:**
- STEP 1: определи кто «Исполнитель» — его данные НЕ трогать
- STEP 2: найди всё остальное ПДн
Убирает хардкод `НУБЕС` из кода. Работает на любом договоре.
## Q2. Точная классификация типов
Вместо `"Invent the type label yourself"` → **фиксированный список:**
`person_name, company, phone, email, address, inn, kpp, ogrn, bik, passport, contract_number, bank_account, other_id`
Если сомневаешься — НЕ включай.
## Q3. Примеры что НЕ является ПДн
Обязательно добавить блок:
```
NOT PII:
- Юридические роли: Исполнитель, Заказчик, Стороны
- Термины договора: Услуги, Приложение, Договор, НДС
- Должности: Генеральный директор
- Сама компания-Исполнитель
```
## Q4. Передавать LLM уже найденное regex'ом
`scan_llm_ner` получает `mapping.keys()` → промпт: "Already captured (skip these): ..."
Исключает дублирование, экономит токены.
## Q5. Язык промпта
Гибрид: структурные инструкции на английском, NOT PII и роли — на русском.
---
## План реализации (scanner.py)
1. Убрать хардкод `НУБЕС` из `scan_regex`
2. `scan_llm_ner` — передавать `mapping.keys()` как "Already captured"
3. Новый промпт:
- Фиксированный список типов
- Блок NOT PII (русский)
- Двухфазная инструкция (STEP 1: Исполнитель, STEP 2: остальное)
@@ -0,0 +1,46 @@
# Ответ Соннета v3 — нумерация вместо генерации фейков (2026-07-12)
## Q1. Нумерация vs фейки
- ✅ Адреса без абракадабры, код проще, аудит прозрачнее
- ❌ Документ нечитаем (`Лицо_0042 заключил договор с Компания_0003`)
- Вывод: для аудита/хранения — отлично, для показа людям — фейки лучше
## Q2. Глобальный счётчик на весь пакет
Одна сущность в разных документах → один номер. `TwoPassObfuscator` хранит counters.
## Q3. Сохранять формат?
Рекомендация: `Тип_NNNN` — проще всего, аудит максимально прозрачен.
## Q4. mapping.csv — главный документ аудита
Да, это улучшение. mapping.csv — секретный ключ деобфускации.
## Q5. Номера vs UUID
Номера (`Лицо_0042`) — лучше для читаемости и аудита.
---
## План изменений
### Удалить
- `drhider/generators/` — все 12 файлов
- `drhider/checksum.py` — больше не нужен (ИНН без контрольных сумм)
- `drhider/random_utils.py` — больше не нужен
### Изменить
- `scanner.py`: добавить `TYPE_PREFIXES` + `_next_token()`, убрать импорты генераторов
- `obfuscator.py`: `TwoPassObfuscator` хранит `counters` dict
- `config.py`: убрать словари имён/городов (RU_SURNAMES, ...)
### Новая логика замены
```python
TYPE_PREFIXES = {
"person_name": "Лицо", "company": "Компания", "phone": "Телефон",
"email": "Email", "address": "Адрес", "inn": "ИНН", ...
}
_FALLBACK = "Данные"
def _next_token(entity_type, counters):
prefix = TYPE_PREFIXES.get(entity_type, _FALLBACK)
counters[prefix] = counters.get(prefix, 0) + 1
return f"{prefix}_{counters[prefix]:04d}"
```
@@ -0,0 +1,19 @@
# Ответ Соннета v4 — гибридный формат замен (2026-07-12)
## Итог: Вариант В (шаблон + номер)
| Тип | Формат замены | Пример |
|---|---|---|
| ФИО | `Фамилия_NNNN` | `Иванов_5677` |
| Компания | `ООО_Слово_NNNN` | `ООО_Технология_0034` |
| Адрес | `ул_Шаблон_NNNN` | `ул_Ленина_0015` |
| Телефон | `+7_код_000_NNNN` | `+7_495_000_0042` |
| Email | `email_NNNN@fake` | `email_0007@fake` |
| ИНН | `ИНН_NNNNNNNNNN` | `ИНН_0000000042` |
| Неизвестный тип | `[Тип_NNNN]` | `[Адрес_0023]` |
## Ключевые принципы
1. **Читаемо человеком** — не ужиматься, пусть будет естественно
2. **Однозначно для LLM** — префикс указывает тип сущности
3. **Глобальная нумерация** — одна сущность = один номер во всех файлах пакета
4. **Цены/суммы** — не обфусцировать (нужны для downstream сверки)
+45
View File
@@ -0,0 +1,45 @@
# v3.0.0 — универсальная архитектура (2026-07-12)
Переход на Markdown-пайплайн + универсальное LLM-обнаружение.
## Что изменилось
### 1. `generators/fallback.py` — новый
Character-class preserving замена для неизвестных типов сущностей.
Цифры→цифры, буквы→буквы, спецсимволы→без изменений.
### 2. `scanner.py` — универсальный LLM-промпт
- Убран жёсткий список типов (person, company, address, passport)
- LLM САМА придумывает тип (snake_case) — person_name, contract_number, employee_id...
- Если тип не совпадает с известными — fallback-генератор
- Промпт на английском (LLM точнее)
### 3. `extractor.py` — Markdown-пайплайн
- `docx_to_markdown()`: DOCX → MD (Heading → #, Bold → **, таблицы → |...|)
- `pdf_to_markdown()`: PDF → MD (текст + таблицы, без форматирования)
- `extract_text()` возвращает только str (без docx-объектов)
- Удалён `convert_pdfs_to_docx()` (PDF → MD напрямую)
### 4. `replacer.py` — упрощён
- Удалён `replace_in_text()` (заменён на `apply_replacements()` + `.encode()`)
- Оставлен `replace_in_docx()` для будущего DOCX-выхода
### 5. `obfuscator.py` — новый пайплайн
- Pass 1: extract MD → scan_regex + scan_llm_ner
- Pass 2: apply_replacements к MD → .md файлы
- Нет docx-объектов, нет convert_pdfs_to_docx
## Коммиты
- `579f9c8` — generators: fallback.py
- `2ceeb05` — scanner.py: универсальный промпт
- `af2f2a9` — extractor.py: MD + obfuscator.py: новый пайплайн
- `f5427b0` — replacer.py: удалён replace_in_text
- `2a2cc14` — v3.0.0
## Плюсы
- LLM точнее (нет XML-шума, только Markdown)
- Новые типы сущностей — без правки кода
- Код проще: -120 строк, -1 функция, -1 модуль (convert_pdfs_to_docx)
- Единый внутренний формат (Markdown)
+30
View File
@@ -0,0 +1,30 @@
# План v3.1 — пофайловая обработка + прогресс в таблице (2026-07-12)
## Проблема
Фронтенд v3.0 отправляет все файлы одним запросом → долгий LLM-вызов → таймаут.
Нужно: обрабатывать по одному, прогресс в таблице, все результаты → один ZIP.
## Решение
### Фронтенд (index.html)
1. **XHR** вместо fetch — надёжнее для blob, явный timeout
2. **Колонка «Статус»** в таблице файлов
3. **По одному файлу:** цикл, каждый файл → отдельный XHR → ZIP
4. **Прогресс в таблице:** ⏳ → ✓ 2.1 сек (или ✗ ошибка)
5. **JSZip** — склеивает все полученные ZIP'ы в один
6. **Скачивание** — один файл `drhider_output.zip` в конце
### Статусы в таблице
| Статус | Отображение |
|---|---|
| Ожидание | ⏳ |
| Успех | ✓ 2.1 сек |
| Ошибка | ✗ сообщение |
### Бэкенд
Без изменений. API уже принимает один файл.
### НЕ трогать
- gunicorn не нужен (файлы по одному, каждый запрос ≤10 сек)
- app.run() — остаётся
- drhider пакет — без изменений
@@ -0,0 +1,44 @@
# Запрос к Opus: валидация MSS Clamping плана
**Дата:** 2026-07-13
---
## Контекст
Кластер: bare-metal Kubernetes, 4 ноды, Cilium CNI (Geneve-туннель), kube-vip для VIP.
Штурвал управляет ingress через Helm-чарт (`shturval-ingress-controller-2.12.1`).
## Проблема
При `externalTrafficPolicy: Cluster` большие файлы (>5 MB) вызывают TCP stall на 51 секунду.
Диагностика: cross-node Geneve-туннель добавляет overhead (~50 байт) → пакет > MTU 1500 → ICMP Frag Needed блокируется → TCP RTO backoff.
Текущий костыль: `externalTrafficPolicy: Local` + `kubectl scale replicas=4` (Штурвал сбрасывает на 2 каждые ~час).
## Предлагаемое решение
Вместо костыля — выставить MTU в Cilium ConfigMap:
```yaml
# kubectl edit configmap -n kube-system cilium-config
mtu: 1450
```
После этого:
1. Вернуть `externalTrafficPolicy: Cluster`
2. Оставить ingress `replicaCount: 2` (стоковое значение Штурвала)
3. Никаких ручных scale
## Вопросы к Opus
**Читать только:** `docs/MSS-CLAMPING-PLAN.md`, `docs/KUBEVIP-EXTERNAL-TRAFFIC-POLICY.md`, `docs/INGRESS-SOLUTIONS.md`.
1. Корректно ли решение? Есть ли подводные камни при смене MTU через Cilium ConfigMap на работающем кластере?
2. Нужно ли что-то ещё кроме `mtu: 1450`? (например, перезапуск подов, пересоздание туннелей)
3. Как проверить что MSS clamping реально работает после изменения? (конкретные команды)
4. Есть ли риск для других сервисов (Keycloak, etc.) при смене MTU?
5. Если Cilium ConfigMap недоступен — iptables-вариант (`TCPMSS --clamp-mss-to-pmtu`) равноценен? На какую ноду вешать правило?
6. Итоговый вердикт: это правильный путь, или есть более грамотный вариант?
**Не лезть:** в код приложения, в TMP, в History (кроме указанных трёх файлов).
@@ -0,0 +1,35 @@
# Opus: проверка PROBLEM-AND-SOLUTION.md на ошибки
**Дата:** 2026-07-13
---
## Найденные ошибки
### 1. TCP backoff — неправильные числа
**Было:** `1с → 3с → 7с → 15с → 25с = 51с` — выдумано.
**Правильно:** начальный RTO = 200ms, удвоение:
```
0.2с → 0.4с → 0.8с → 1.6с → 3.2с → 6.4с → 12.8с → 25.6с = 51с
```
8 ретрансмитов от 200ms = ровно 51 секунда (Linux kernel default).
### 2. DSR — неверное объяснение причины stall
**Было:** «двойной инкапсуляции return-трафика (главная причина stall)».
**Правильно:** stall на **входящем** трафике (клиент → pod), а не на return. DSR оптимизирует обратный путь, но не решает проблему inbound MTU black-hole.
### 3. Ping измеряет pod MTU, не underlay
Утверждение что `ping -M do` доказывает underlay = 1450 — неточно. Ping из пода измеряет MTU пода (1450 = 1500 − 50 Cilium). Физический underlay = 1450 подтверждён эмпирически (фикс сработал), а не прямым замером.
## Что верно
- Geneve overhead 50 байт: outer IP(20) + UDP(8) + Geneve(8) + inner Eth(14) = 50 ✓
- `mtu: 1450` → pod MTU = 1400 ✓
- `rollout restart ds/cilium` обязателен ✓
- iptables может не работать с eBPF dataplane ✓
- Корень проблемы и решение верны ✓
@@ -0,0 +1,31 @@
# Opus: финальная проверка PROBLEM-AND-SOLUTION.md
**Дата:** 2026-07-13
---
## Вердикт
Документ технически добротный. Диагноз и корневое решение (`mtu: 1450` + `Cluster` + `replicaCount: 2`) — правильные.
## Исправлено
1. **Ethernet 14** — уточнено: «инкапсулируемый L2-кадр 14» (внутренний Ethernet, не внешний)
2. **51с = прикладной таймаут** — смягчено: «браузер/curl разрывает ожидание» вместо «соединение разрывается»
## Что подтверждено как верное
- PMTU blackhole как причина — классический сценарий
- Cilium: `mtu` в configmap = underlay девайса, pod MTU = mtu − overhead
- Geneve overhead = 50 байт (внешний IP 20 + UDP 8 + Geneve 8 + L2 14)
- Решение: `mtu: 1450` → pod 1400 → Geneve-пакет 1450 → влазит в path 1450
- `rollout restart ds/cilium` обязателен
- `externalTrafficPolicy: Local` → `Cluster` после фикса MTU
- DSR решает только обратный путь, stall на inbound
- iptables может не работать с eBPF dataplane
## Мелкие замечания (не исправлены, допустимы)
- 200ms RTO — корректно для сценария (уже установленное TCP-соединение)
- Тип туннеля (Geneve vs VXLAN) — overhead одинаков (50), математика не меняется
- Замеры из таблицы результатов — эмпирические, из кода не подтвердить
@@ -0,0 +1,77 @@
# Opus: валидация MSS Clamping плана
**Дата:** 2026-07-13
---
## Ключевое исправление
В плане ошибка: `mtu: 1450` в cilium-config — это MTU **underlay-сети**, не туннеля. Cilium сам вычитает 50 байт на Geneve:
```
mtu: 1450 → tunnel MTU = 1450 − 50 = 1400
```
По умолчанию Cilium автодетектит underlay 1500 → туннель 1450. Если stall всё равно есть — underlay реально < 1500. **Число надо измерить, не гадать.**
---
## Ответы на вопросы
### 1. Корректно ли решение?
Да, направление верное. Но:
- ConfigMap сам не применяется — нужен `rollout restart ds/cilium`
- Число 1450 — гипотеза, нужен замер между нодами
- `mtu` глобален на весь кластер
- Существующие TCP-соединения не поменяют MSS — только новые
### 2. Что ещё нужно?
1. Измерить underlay MTU: `ping -M do -s 1472 <ip_другой_ноды>` (уменьшать пока не пройдёт)
2. `kubectl rollout restart ds/cilium -n kube-system`
3. Пересоздать поды приложения (ingress, drhider)
4. Вернуть `externalTrafficPolicy: Cluster`
### 3. Как проверить?
```bash
# Измерить до:
ping -M do -s 1472 <ip_другой_ноды>
tracepath <ip_другой_ноды>
# После изменения:
kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep -i mtu
ip link show | grep -E 'cilium|geneve'
tcpdump -ni any 'tcp[tcpflags] & tcp-syn != 0' -vv | grep mss
# Функционально: файл >5 MB без паузы
```
### 4. Риск для других сервисов?
Понижение MTU безопасно (меньше = консервативнее). Небольшой рост числа пакетов, микро-потеря throughput. Функционально ничего не ломает. Рестарт агентов — возможны короткие блипы, лучше в окно.
### 5. iptables равноценен?
**Нет.** Cilium с eBPF обходит netfilter FORWARD — правило может не видеть пакеты. Надёжнее `cilium-config`. Если iptables — то на всех 4 нодах (kube-vip раздаёт VIP на все).
### 6. Вердикт
Путь правильный. Уточнённый план:
1. Измерить path MTU между нодами
2. Выставить `mtu` = измеренное значение (вероятно < 1500)
3. Рестарт агентов + подов
4. Вернуть `Cluster` policy
При `Cluster` проблема реплик исчезает сама (2 хватает).
---
## Моё мнение
Opus прав. План нужно дополнить **замером**. Без замера `1450` — пальцем в небо. Обновлю `MSS-CLAMPING-PLAN.md` с учётом:
- Добавить шаг 0: измерение path MTU
- Исправить формулу: `mtu` = underlay, туннель = `mtu - 50`
- Добавить `rollout restart ds/cilium` + рестарт подов
- Убрать iptables как основной вариант (ненадёжен с Cilium)
@@ -0,0 +1,39 @@
# Запрос к Соннету — архитектура пофайловой обработки с одним ZIP (2026-07-13)
## Задача
DrHider (Flask + waitress). Ограничения:
- Фронтенд должен слать файлы **по одному** (сеть не пропускает большие multipart)
- Бэкенд должен обрабатывать **по одному** (без параллельных LLM-запросов)
- Результат: **один ZIP** со всеми обфусцированными .md + **один общий** mapping.csv
- **Никакой обработки данных в JS** — фронтенд только кнопки и скачивание
## Варианты
### А. Upload → Process
1. `POST /api/upload` — один файл → `{session:"abc"}`
2. Фронтенд циклом шлёт все, получает session
3. `POST /api/process/abc` — бэкенд обрабатывает всё → ZIP
4. Фронтенд скачивает ZIP
### Б. Один запрос, все файлы
1. Фронтенд: один POST, все файлы в FormData
2. Бэкенд: получил все → обработал по одному → ZIP
3. Waitress держит долгое соединение, таймаут 600с
### В. SSE с прогрессом
1. `POST /api/start` → `{session:"abc"}`
2. `POST /api/upload/abc` — по одному файлу
3. `GET /api/process/abc` — SSE стримит прогресс каждого файла и в конце ZIP
## Вопросы
1. Какой вариант правильный для Flask + waitress?
2. Вариант Б — пройдёт ли большой multipart через ingress (6 файлов × 30-60 KB)?
3. Если А — как чистить сессии? (пользователь закрыл вкладку, файлы остались в памяти)
4. Нужен ли SSE или достаточно простого XHR с таймером?
5. mapping.csv — генерировать на лету (по мере обработки) или после всех файлов?
## Сейчас работает (но сломано)
Сейчас: фронтенд шлёт по одному файлу, каждый получает отдельный ZIP. Пользователь хочет один ZIP в конце. JS обрабатывал ZIP'ы (распаковывал/перепаковывал) — это убрали, всё должно быть на бэкенде.
@@ -0,0 +1,114 @@
# Запрос к Sonnet: анализ обфускации
**Дата:** 2026-07-13
---
**Задача:** Найти причину расхождения между ожидаемыми кодами замен и реальными заменами.
## Ожидаемое поведение
`_next_token()` в `drhider/scanner.py` генерирует коды замен формата:
- `Технология_0001`, `Прогресс_0002` (для компаний)
- `+7_000_000_0001` (для телефонов)
- `email_0001` (для email)
- `ИНН_0001` (для ИНН)
- `Иванов_0001` (для ФИО)
## Реальный результат — `TMP/mapping.csv`
```
тип_данных,оригинал,замена
phone,+7 (495) 789-41-35,+7 (495) 906-87-80 ← НЕ код замены, реальный номер
email,info@nubes.ru,ypiorej@company.local ← НЕ код замены, реальный email
company,ЗАО ****XXX****001****,АО «Прогресс» ← НЕ код замены, реальное название
company,ЗАО XXX001,ООО «Альянс» ← НЕ код замены, реальное название
inn_ul,ИНН 9706005293,ИНН 1846691249 ← НЕ код замены, реальный ИНН
kpp,КПП 772401001,КПП 350443068 ← НЕ код замены, реальный КПП
ks,Кор/сч 30101810400000000225,Кор/сч 30101201490272090211 ← НЕ код замены
ogrn,ОГРН 1207700098759,ОГРН 1048672318430 ← НЕ код замены
company,ООО "НУБЕС",ООО «Спектр» ← НЕ код замены
text,Исполнителем Заказчику Сторонами,Кузнецов О.В.
text,"Обязанности Заказчика\nСвоевременно",Степанов А.В.
text,"Обязанности Исполнителя\nОказывать",Петров П.Е.
text,"Ответственность Заказчика\nЗаказчик",Новиков А.М.
text,"Ответственность Исполнителя\nИсполнитель",Попов О.М.
company,ПАО Сбербанк,АО «Формат»
```
**Ни одного кода замены формата `XXX_0000` в колонке «замена».**
## Реальный результат — `TMP/договор-XXX001-03700_obfuscated.md` (первые строки)
```markdown
**АО «Прогресс»** ... и **ООО **"**НУБЕС**" ... Новиков И.В. ...
```
- `Новиков И.В.` — НЕ заменён (должен быть `Иванов_0001`)
- `ООО "НУБЕС"` — НЕ заменён в тексте (есть в mapping но замена не применена?)
- `г. Москва, 1-я Стекольная 7с5` — адрес не заменён
## Файлы для анализа
**Обязательно прочитать:**
1. `drhider/scanner.py` — `_next_token()`, `scan_regex()`, `scan_llm_ner()`, TYPE_POOLS, промпт LLM (строки 109-151)
2. `drhider/obfuscator.py` — порядок вызова regex → LLM, как объединяется mapping (строки 63-95)
3. `drhider/config.py` — ENTITY_PATTERNS, COMPANY_PATTERN, PERSON_PATTERN
4. `drhider/llm_client.py` — `LLMClient.complete()`
**Прочитать для контекста:**
5. `TMP/mapping.csv` — полный результат
6. `TMP/договор-XXX001-03700_obfuscated.md` — обфусцированный файл
7. `TMP/примеры_договоров_для_ИИ/договор-XXX001-03700.docx` — оригинал (для сверки)
**Не лезть:** `builder.py`, `extractor.py`, `replacer.py`, `session.py`, `templates/`, docs/, History/.
## Ключевые точки для проверки
### 1. `scanner.py` — `_next_token()` (стр. 63-82)
```python
def _next_token(entity_type, counters):
pool, prefix_key = TYPE_POOLS.get(entity_type, _FALLBACK_POOL)
template = random.choice(pool)
key = f"{prefix_key}_{template}"
counters[key] = counters.get(key, 0) + 1
return f"{template}_{counters[key]:04d}"
```
Возвращает `Технология_0001`, `+7_000_000_0001`. **Никак не может вернуть** `АО «Прогресс»` или `+7 (495) 906-87-80`.
### 2. `scanner.py` — `scan_regex()` (стр. 89-108)
```python
mapping[original] = _next_token(entity_type, counters)
```
Всегда вызывает `_next_token`. Результат должен быть кодом замены.
### 3. `scanner.py` — `scan_llm_ner()` (стр. 109-179)
```python
mapping[val] = _next_token(ent_type, counters)
```
Тоже вызывает `_next_token`. Результат должен быть кодом замены.
### 4. `obfuscator.py` — порядок вызова (стр. 86-92)
```python
scanner.scan_regex(text, self._mapping, self._counters) # сначала
scanner.scan_llm_ner(all_texts, self._mapping, ...) # потом
```
### 5. `config.py` — regex-паттерны:
- `phone`: `(?:\+7|8)[\s\-]?\(?\d{3}\)?...` — должно матчить `+7 (495) 789-41-35`
- `email`: `[a-zA-Z0-9._%+-]+@...` — должно матчить `info@nubes.ru`
- `inn_ul`: `ИНН\s*\d{10}` — должно матчить `ИНН 9706005293`
- `COMPANY_PATTERN`: `(?:ООО|ЗАО|...)\s+(?:«[^»]+»|"[^"]+"|...)` — должно матчить `ООО "НУБЕС"` и `ЗАО "XXX001"`
- `PERSON_PATTERN`: `[А-Я][а-я]+\s+[А-Я]\.[А-Я]\.` — должно матчить `Новиков И.В.`
## Главный вопрос
Каждый путь (regex и LLM) вызывает `_next_token()`. `_next_token()` физически не может выдать `АО «Прогресс»` или `+7 (495) 906-87-80`. Откуда в `mapping.csv` берутся реалистичные замены? Кто и где их генерирует В ОБХОД `_next_token()`?
@@ -0,0 +1,17 @@
# Ответ Соннета — архитектура upload+process (2026-07-13)
## Рекомендация: Вариант А + блокирующий /process
```
POST /api/upload → {session_id} (фронт шлёт по одному, N раз)
POST /api/process/{sid} → {status:"done"} (блокирует до конца обработки)
GET /api/download/{sid} → ZIP
```
Без polling — `/process` просто блокирует XHR до готовности. Waitress держит.
## Детали
- Mapping.csv — на лету, аккумулируется после каждого файла
- Сессии — dict в памяти, TTL 30 минут через threading.Timer
- Фронт: цикл upload → process → download. Три простых XHR.
- Никакого JS-кода обработки данных.
@@ -0,0 +1,70 @@
# Sonnet: анализ обфускации — TMP-файлы vs текущий код
**Дата:** 2026-07-13
---
## Главный вывод
**TMP-файлы созданы СТАРОЙ версией кода.** Они не были сгенерированы текущим `_next_token()`-кодом. Это не баг в текущем коде — это артефакт предыдущей реализации.
---
## Доказательства
### 1. Имена файлов — старый формат
Текущий `obfuscator.py` генерирует имена вида `договор-НУБЕС001-03700.md` (меняет расширение). TMP-файлы: `договор-XXX001-03700_obfuscated.md` — суффикс `_obfuscated` и `XXX`-маскировка в имени. Текущий код так не делает.
### 2. Тип `text` — fallback из builder.py, не из сканера
`build_mapping_csv()` определяет тип постфактум по паттернам. Если ни один не совпал → `"text"`. Типа `text` нет в `TYPE_POOLS` scanner.py. Значит строки вида:
```
text,Исполнителем Заказчику Сторонами,Кузнецов О.В.
```
попали из старого кода, который не фильтровал роли (Исполнитель, Заказчик) — текущий LLM-промпт их явно исключает.
### 3. Одна сущность → разные замены
```
email,info@nubes.ru,ypiorej@company.local ← файл 1
email,info@nubes.ru,qifdckke@org.example.ru ← файл 2
```
Текущий `TwoPassObfuscator` гарантирует глобальную согласованность. Старый код обрабатывал файлы по одному — отсюда расхождения.
### 4. Реалистичные замены невозможны из `_next_token()`
```python
TYPE_POOLS = {
"phone": (["+7_000_000"], "phone"), # → "+7_000_000_0001"
"email": (["email"], "email"), # → "email_0001"
}
```
`+7 (495) 906-87-80` или `ypiorej@company.local` физически не могут выйти из этих пулов.
---
## Ответ на главный вопрос
> Кто и где генерирует замены в обход `_next_token()`?
**Никто в текущем коде.** Это артефакты старого кода (версии до v3). Текущий `_next_token()` написан позже.
---
## Что реально нужно проверить
| Вопрос | Как проверить |
|--------|---------------|
| Работает ли текущий код? | Прогнать оригиналы через v0.0.12, посмотреть mapping.csv |
| Regex ловит все сущности? | Проверить `config.py` паттерны на примерах из TMP |
| Адрес `115404, г. Москва...` — почему не заменён? | `ENTITY_PATTERNS` не содержит тип `address` |
| `ООО "НУБЕС"` в тексте не заменён? | Возможно кавычки — regex матчит `«»` но не `""` |
---
## Итог
**Ложное противоречие** — сравнивались TMP-файлы (старый код) с текущим scanner.py. Нужно прогнать текущий код на тех же документах и посмотреть результат.
@@ -0,0 +1,37 @@
# Sonnet: анализ багов обфускации — fix
**Дата:** 2026-07-13
---
## Баг 1: `ООО "НУБЕС"` не заменено
**Причина:** DOCX → Markdown: `**ООО **"**НУБЕС**"`. Кавычка в отдельном run → `**` разрывает строку. Replacer ищет точное совпадение `ООО "НУБЕС"` — не находит.
**Фикс (replacer.py):** добавить `_md_tolerant_pattern()` — паттерн, допускающий `**` между частями строки. При промахе точного совпадения — фоллбэк с этим паттерном.
```python
def _md_tolerant_pattern(original: str) -> str:
MD = r'(?:\*{1,2})?'
parts = re.findall(r'[А-ЯЁа-яёA-Za-z0-9]+|[^А-ЯЁа-яёA-Za-z0-9]', original)
return MD + MD.join(re.escape(p) for p in parts) + MD
```
## Баг 2: `Новиков И.В.` → `ФИО`
**Причина:** В оригинале буквально `ФИО` — незаполненный шаблон. Не баг паттерна.
**Превентивный фикс (config.py):** `\s+` → `[\s\xa0]+` в PERSON_PATTERN — на случай неразрывных пробелов в DOCX.
```python
PERSON_PATTERN = re.compile(
r'\b[А-Я][а-я]+[\s\xa0]+[А-Я]\.[А-Я]\.'
r'|\b[А-Я][а-я]+[\s\xa0]+[А-Я][а-я]+[\s\xa0]+[А-Я][а-я]+',
)
```
## Моё мнение
**Баг 1 — делать.** Логично, минимально, решает проблему Markdown-жирности.
**Баг 2 — `\xa0` делать** (превентивно). Сам `ФИО` — не баг, документ содержит незаполненный шаблон. Можно добавить `ФИО` в список игнорируемых (исключить из mapping), но не обязательно.
+237
View File
@@ -0,0 +1,237 @@
# Диагностика ERR_CONNECTION_RESET в браузере
**Дата:** 2026-07-14
---
## Хронология
### Фаза 1: MTU (решено)
Проблема: 63KB POST из браузера → `ERR_CONNECTION_RESET`. Причина: MTU 1500 + Geneve 50 = 1550 > underlay 1450.
Решение: Cilium MTU = 1400. Проверено curl с ВМ (30/30).
### Фаза 2: HTTP/2
HTTP/2 → `ERR_HTTP2_PROTOCOL_ERROR`. HTTP/1.1 → `ERR_CONNECTION_RESET`.
Решение: `use-http2: "false"`.
### Фаза 3: Поиск корневой причины RST
Баг: браузер → свежая страница → POST 63KB → `ERR_CONNECTION_RESET`.
## Полная матрица тестов
| # | Сценарий | Результат |
|---|----------|-----------|
| 1 | Браузер: свежая страница → POST 63KB | ❌ RST / `⏳ 100%` hang |
| 2 | Браузер: свежая страница → GET (до Flask) → POST 63KB | ✅ 200 OK |
| 3 | Браузер: свежая страница → POST 10B → POST 63KB | ✅ 200 OK |
| 4 | Браузер: свежая страница → GET (nginx сам, 405) → POST 63KB | ❌ RST |
| 5 | `curl` с ВМ (5.172.178.213) → POST 63KB | ✅ всегда <100ms |
| 6 | Сразу после `kubectl rollout restart` ingress → браузер | ✅ работает |
| 7 | Через 5-10 мин после рестарта → браузер | ❌ снова RST |
## Что исключено
| Гипотеза | Статус | Почему |
|----------|--------|--------|
| MTU | ❌ исключено | 1400, curl с ВМ всегда работает |
| HTTP/2 | ❌ исключено | `use-http2: false`, та же проблема |
| XHR vs fetch | ❌ исключено | оба метода падают одинаково |
| `proxy_request_buffering: off` | ❌ исключено | `on` для `location /` |
| `client-body-timeout: 10` | ❌ исключено | 63KB < 1 сек |
| FD/worker-лимиты | ❌ исключено | 1M FD, 16K connections, 62MB mem |
| OOM/Restart | ❌ исключено | 0 рестартов, нет OOMKilled |
| Stale upstream keepalive | ❌ исключено | `keepalive=0` не помогло |
| nginx→Flask (upstream) | ❌ исключено | curl с ВМ доказывает что работает |
## Эксперименты
### Перезапуск ingress временно чинит
После `kubectl rollout restart` — браузер работает. Через 5-10 мин — снова RST. Накопление состояния.
### Ресурсы в норме
```
worker_rlimit_nofile: 1047552 (1M FD)
worker_connections: 16384
worker_processes: 4
Memory: 62 MiB, CPU: 3-7m
Restarts: 0, OOMKilled: нет
```
### `upstream-keepalive-connections: 0` — НЕ фикс
Баг вернулся через 5-10 минут. Keepalive pool не при чём.
## Критическое противоречие
- `curl` с ВМ (тот же датацентр) → POST 63KB → **всегда 200 OK <100ms**
- Браузер через 5-10 мин после рестарта ingress → **ERR_CONNECTION_RESET**
curl и браузер идут через один VIP (185.247.187.151), на одни ingress-поды.
Разница: клиент (curl vs Chrome) и сетевой путь (локальная сеть ДЦ vs интернет/VPN).
## Текущая конфигурация (2026-07-14)
| Параметр | Значение |
|----------|----------|
| `use-http2` | `"false"` |
| `upstream-keepalive-connections` | `"0"` |
| `error-log-level` | `debug` |
| `proxy-body-size` | `1024m` |
| `client-body-timeout` | `"10"` |
| `keep-alive` | `"10"` |
| Cilium MTU | `1400` |
| `externalTrafficPolicy` | `Cluster` |
| Ingress controller | `1.12.6` (Штурвал) |
---
## Вопрос к Claude (Sonnet/Opus)
### Симптом
Браузер (Chrome/Electron, Windows) → POST 63KB multipart/form-data → `ERR_CONNECTION_RESET` или зависание на `⏳ 100%`. Тело уходит полностью, ответ не приходит — TCP RST.
### Окружение
```
Кластер: bare-metal K8s 1.34.1, 4 ноды (3 worker + 1 control-plane)
CNI: Cilium Geneve, MTU подов = 1400
VIP: kube-vip 185.247.187.151 (ARP на все 4 ноды)
Ingress: nginx-ingress (Штурвал Helm), контроллер 1.12.6
2 реплики, externalTrafficPolicy: Cluster
Приложение: Flask/Waitress, 1 реплика (Штурвал managed)
```
Конфиг ingress:
```yaml
use-http2: "false"
upstream-keepalive-connections: "0"
client-body-timeout: "10"
keep-alive: "10"
worker-processes: 4
worker-rlimit-nofile: 1047552
worker-connections: 16384
proxy-body-size: 1024m
proxy-request-buffering: on # для location /
error-log-level: debug
```
### Критическое противоречие
- `curl` с ВМ (5.172.178.213, тот же датацентр) → POST 63KB → **всегда 200 OK <100ms**
- Браузер через 5-10 мин после рестарта ingress → **ERR_CONNECTION_RESET**
- Сразу после рестарта ingress → браузер работает
curl и браузер идут через один VIP (185.247.187.151), на одни ingress-поды. Разница: клиент (curl vs Chrome) и сетевой путь до VIP (локальная сеть ДЦ vs интернет/VPN).
### Вопросы
1. Почему рестарт ingress помогает на 5-10 минут, а потом браузерные запросы снова получают RST, при том что curl с ВМ продолжает работать?
2. Может ли `externalTrafficPolicy: Cluster` + kube-vip создавать conntrack-записи, которые со временем ломают соединения из интернета (с другими TCP options/MSS) но не из локальной сети?
3. Стоит ли попробовать `externalTrafficPolicy: Local`? Какие риски?
4. Какие ещё эксперименты можно провести без tcpdump (нет доступа к нодам по SSH, нет tcpdump в подах)?
---
## TODO
- [ ] Ответ от Claude
- [ ] `externalTrafficPolicy: Local` — эксперимент
- [ ] Воспроизвести с `curl` из WSL на машине пользователя
- [ ] tcpdump через `kubectl debug` (требует координации)
- [ ] Обновить ingress controller 1.12.6 → актуальный
- [ ] Проверить `client-body-timeout: 60` + `keep-alive: 75`
---
## Ответ Claude (Sonnet) — 2026-07-14
### Два независимых механизма
Симптом объясняется наложением двух механизмов:
### Механизм 1: kube-vip ARP flapping + conntrack asymmetry (основной)
Именно это объясняет 5-10-минутное окно и разницу между browser и curl.
**Как работает с `externalTrafficPolicy: Cluster`:**
```
Browser → VIP → Node N1 (kube-vip leader) → iptables DNAT+SNAT → nginx pod
│
└── conntrack entry создаётся на N1:
(browser_ip:srcport → 185.247.187.151:443)
↔ (N1_ip:eph → nginx_pod:443)
```
Проблема возникает когда kube-vip меняет лидера:
```
kube-vip: N1 → N2 (истёк lease или GARP от N2)
Коммутатор: обновляет ARP-кэш, VIP теперь на N2
Browser (старое HTTP keep-alive соединение):
→ PSH/ACK 63KB → попадает на N2
N2: нет conntrack записи → TCP RST
curl (новое соединение):
→ SYN → попадает на N2
N2: создаёт новую conntrack → всё ОК
```
**Почему 5-10 минут:** время жизни ARP-записи на коммутаторе или интервал переизбрания kube-vip лидера. "ARP на все 4 ноды" — периодические GARP-анонсы могут уходить с разных нод.
**Почему GET-to-Flask чинит последующий POST (тесты #2 vs #4):**
- GET → Flask: создаётся *новое* TCP-соединение (новый SYN) → правильный conntrack на текущей ноде. POST идёт по *тому же* соединению → conntrack жив → OK.
- GET → 405: nginx отвечает сам и закрывает соединение с `Connection: close`. POST создаёт *новое* соединение, но VIP уже на другой ноде → RST.
### Механизм 2: `keepalive_timeout: 10s` — слишком агрессивно
`keep-alive: "10"` в ConfigMap = `keepalive_timeout 10s` в nginx.
```
T=0: Browser загружает страницу (GET /)
T=10: nginx закрывает idle соединение (отправляет FIN)
T=?: Пользователь заполняет форму (>10 сек → почти всегда)
T=X: Browser отправляет POST по "живому" с его точки зрения соединению
→ nginx уже закрыл socket → RST
```
Браузер не всегда замечает FIN немедленно (TCP буферизация ОС). curl — каждый раз новое соединение.
### Почему curl всегда работает
| | Browser | curl |
|---|---|---|
| Соединение | HTTP keep-alive, долгоживущее | Новое для каждого запроса |
| conntrack | Создано при SYN, может устареть | Новый SYN на каждый вызов |
| keepalive | Держит idle >10s | Сразу POST → не ждёт |
| VIP flap | Пакет идёт на новую ноду без conntrack | SYN на новой ноде создаёт conntrack |
### Ключевой инсайт
> Разница не в TCP-опциях (MSS, window size). Разница в **типе пакета**: curl всегда начинает с SYN → conntrack создаётся на правильной ноде. Browser держит соединение между запросами → когда VIP мигрирует, браузерные пакеты (ACK/PSH) приходят на новую ноду без предшествующего SYN → нет conntrack-записи → RST.
### Итоговая таблица причин
| Причина | Механизм | Почему не curl |
|---|---|---|
| kube-vip ARP lease renewal | Смена ноды → conntrack orphan | Новый SYN всегда |
| `keepalive_timeout: 10s` | nginx закрывает idle conn → browser не замечает | Нет idle |
Оба фактора вместе: рестарт сбрасывает состояние, 5-10 минут → kube-vip переизбирается / браузер держит соединение дольше 10s → RST.
### Рекомендации
1. **`keep-alive: "75"`** — исключить механизм #2 (самое простое, безопасное)
2. **`externalTrafficPolicy: Local`** — исключить механизм #1 (риск: потеря source IP для других сервисов)
3. **JS: Connection warmer** — GET на Flask при загрузке страницы (уже доказано тестом #2)
4. **JS: пересоздавать соединение** — использовать `fetch()` без keep-alive (credentials: 'omit' или отдельный subdomain)
@@ -0,0 +1,23 @@
# Waitress + Connection: close — hop-by-hop заголовок
**Дата:** 2026-07-14
**Версия:** 0.0.21 → 0.0.22
---
## Ошибка
`Connection` — hop-by-hop заголовок. Waitress (WSGI) запрещает его использовать в приложении (PEP 3333).
```python
# ❌ НЕЛЬЗЯ
response.headers["Connection"] = "close"
# Ошибка:
AssertionError: Connection is a "hop-by-hop" header; it cannot be used by a WSGI application (see PEP 3333)
```
## Правило
**Никогда не добавлять `Connection` заголовок в Flask/Waitress-приложении.**
Нужно закрывать соединения — только через nginx (upstream keepalive) или uWSGI/gunicorn.
+48
View File
@@ -0,0 +1,48 @@
# SSE: отлов отключения клиента, v0.0.18
**Дата:** 2026-07-14
**Версия:** 0.0.17 → 0.0.18
---
## Проблема
После 2-3 F5 во время обработки — загрузка падает с «Ошибка загрузки: Сеть».
Причина: утечка потоков.
### Механика
1. `process_stream` создаёт SSE-генератор, занимающий поток Waitress (всего 8)
2. `obfuscate_files()` выполняется ~30 сек (LLM-запросы)
3. Пользователь делает F5 → TCP-соединение рвётся
4. Генератор **не знает** что клиент отключился — продолжает обрабатывать файлы
5. Поток занят, не освобождается
6. 2-3 F5 → все 8 потоков заняты → новые запросы не принимаются → «Сеть»
### Почему WSGI не отключает
`yield` в генераторе пишет в буфер WSGI, а не в сокет. Буфер никуда не уходит
(клиент отключён), но генератор об этом не узнаёт — `GeneratorExit` не кидается.
---
## Решение
Добавлен `try/except GeneratorExit` после каждого `yield` в `generate()`:
```python
try:
yield f"event: done..."
except GeneratorExit:
return # клиент отключился, освобождаем поток, НЕ обрабатываем остальные файлы
```
Waitress при отключении клиента кидает `GeneratorExit` при попытке записи в
закрытый сокет. Генератор ловит → выходит → поток освобождается.
---
## Изменённые файлы
- `site/routes/api_bp.py` — `try/except GeneratorExit` в `generate()`
- `site/app.py` — `VERSION = "0.0.18"`
+158
View File
@@ -0,0 +1,158 @@
# Cluster Dump — 2026-07-15 17:00 MSK
## 1. НОДЫ (4 шт)
| Нода | Роль | Taints |
|------|------|--------|
| iot-naeel-control-plane-xb699 | control-plane | `NoSchedule:control-plane` |
| iot-naeel-workers-vqphm-6f74n | workers | нет |
| iot-naeel-workers-vqphm-bhbvs | workers | нет |
| iot-naeel-workers-vqphm-v8zq4 | workers | нет |
## 2. INGRESS (shturval-ingress-controller)
### Поды (2 реплики)
| Под | Нода | Возраст |
|-----|------|---------|
| ...86jsp | control-plane-xb699 | ~6ч |
| ...jm4kl | workers-vqphm-v8zq4 | ~6ч |
### Сервис
- Тип: LoadBalancer
- External IP: 185.247.187.151
- `externalTrafficPolicy: Cluster`
- NodePorts: 80:30739, 443:32391
### Ingress ConfigMap
| Параметр | Значение |
|----------|----------|
| keep-alive | **10** |
| client-body-timeout | **10** |
| client-header-timeout | **10** |
| proxy-body-size | **8m** |
| upstream-keepalive-connections | **0** |
| use-http2 | false |
| worker-processes | 4 |
| error-log-level | info |
### Helm
- Chart: shturval-ingress-controller-2.12.1
- Ревизия 9 (сегодня 11:53) — `replicaCount: 2`, hostPort enabled
- Ревизия 8 (сегодня 02:00) — то же самое
- Более старых ревизий нет (почищены)
### Affinity (ingress deploy)
```yaml
preferredDuringScheduling:
- weight: 10 → избегать control-plane
- weight: 80 → предпочитать node-role: ingress
- weight: 50 → предпочитать node-role: infra
```
### События (каждые 51 сек!)
```
Warning SyncLoadBalancerFailed failed to ensure load balancer: no address pools could be found
Normal EnsuringLoadBalancer Ensuring load balancer
```
## 3. KUBE-VIP
### DaemonSet: shturval-vip (4 пода, на ВСЕХ 4 нодах)
- Image: r.shturval.tech/kube-vip:v1.0.0
- ARP mode: `vip_arp: true`
- Election: `svc_election: true`
- Leaderelection: `vip_leaderelection: false`
- Lease: `sht-uservip-cp-lock`
### Cloud Provider: shturval-vip-provider (1 под, на bhbvs)
- Image: r.shturval.tech/kubevip/kube-vip-cloud-provider:v0.0.12
- Ищет ConfigMap: `kubevip` в неймспейсе `kube-system`
### ❌ ConfigMap `kubevip` в `kube-system` — **ПУСТОЙ!**
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: kubevip
namespace: kube-system
data: {} # <-- НЕТ ДАННЫХ
```
**Это причина ошибки «no address pools could be found»!**
## 4. DRHIDER (pythonk8s)
### Деплой
- 1 реплика
- Ревизия: 49
- Инит-контейнер: клонирует из Git, потом pip install + python app.py
- Ресурсы: 500m CPU, 1Gi RAM
- Проба: TCP :5000
### ❌ PodAffinity (добавлен сегодня 16:46, непрошеный)
```yaml
requiredDuringScheduling:
podAffinity → ingress pod на той же ноде
```
### Сервис
- ClusterIP: 10.102.171.63
- Port 80 → targetPort 5000
- `internalTrafficPolicy: Cluster`
### Ingress (drhider)
- Host: drhider.pythonk8s.dev.nubes.ru
- Аннотации: proxy-body-size=1024m, connect=120s, read/send=600s
- TLS: letsencrypt-prod
### Поды
| Под | Нода | Возраст |
|-----|------|---------|
| pythonk8s-6698c6c78-dkwsq | workers-vqphm-v8zq4 | ~15 мин |
Drhider на одной ноде с ingress-подом jm4kl.
## 5. ВСЕ INGRESS-РЕСУРСЫ
| Хост | Неймспейс | Возраст |
|------|-----------|---------|
| drhider.pythonk8s.dev.nubes.ru | 20a75175-... | 4д |
| loadtest.pythonk8s.dev.nubes.ru | 9039a501-... | 5д |
| contractor.pythonk8s.dev.nubes.ru | b4523aba-... | 7ч |
| capire.kube5s.ru | default | 54д |
| grafana.kube5s.ru | grafana | 66д |
| keycloak.k8c.ru | keycloak | 96д |
| qu.kube5s.ru | shared-sqs | 93д |
| iot.kube5s.ru | sless | 94д |
| terra.k8c.ru | terra | 92д |
## 6. CILIUM
4 пода (по одному на ноду), все Running. MTU был изменён на 1400 (по STATE).
## 7. КЛЮЧЕВЫЕ НАХОДКИ
### 🔴 ConfigMap `kubevip` пустой
kube-vip-cloud-provider не может найти address pools → каждые 51 сек ошибка SyncLoadBalancerFailed. При этом `185.247.187.151` работает через ARP-mode kube-vip DaemonSet (не через cloud provider). Вероятно, это не fatal, но указывает на недонастроенную интеграцию.
### 🔴 Ingress ConfigMap отличается от ожидаемого
- keep-alive: 10 (должен быть 75)
- client-body-timeout: 10 (должен быть 60)
- client-header-timeout: 10 (должен быть 30)
- proxy-body-size: 8m (должен быть 1024m)
Но для drhider это переопределено в аннотациях ingress-ресурса.
### 🟡 Ingress 2 реплики на 4 нодах с kube-vip
kube-vip анонсирует 185.247.187.151 на ВСЕХ 4 нодах, но ingress-поды только на 2. При `externalTrafficPolicy: Cluster` трафик на ноды без ingress: SNAT → кросс-нода → риск conntrack RST.
### 🟡 Helm: 2 апгрейда сегодня
Ревизии 8 (02:00) и 9 (11:53) сегодня. Кто-то обновлял ingress. `replicaCount: 2` в обеих.
### 🟢 Аннотации drhider ingress — хорошие
proxy-body-size=1024m, connect=120s, read/send=600s. Переопределяют дефолты ConfigMap.
### 🟢 Cilium — стабильный
4 пода, все Running, MTU 1400.
+204
View File
@@ -0,0 +1,204 @@
# DrHider — итоговая диагностика ERR_CONNECTION_RESET
**Дата:** 2026-07-15 (финал)
**Версия:** v0.0.29
---
## Резюме (моё мнение)
**`keep-alive: 10` — единственная причина ERR_CONNECTION_RESET.** Всё остальное (conntrack, hostPort, ARP flapping, svc_election) — шум, который мы искали 2 дня.
### Почему это так
VIP `185.247.187.151` жёстко привязан к control-plane-xb699 через аннотацию `kube-vip.io/vipHost`. Никакого flapping нет — трафик всегда приходит на одну ноду. Второй ingress-под на v8zq4 — избыточен, но не мешает.
Сценарий:
1. Браузер загружает страницу — opens TCP
2. Пользователь выбирает файлы — проходит >10 сек
3. Nginx (keep-alive=10) закрывает idle соединение
4. Браузер не замечает FIN (буферизация ОС)
5. POST 63KB летит в мёртвый сокет → RST
### Почему предыдущие гипотезы неверны
| Гипотеза | Опровержение |
|----------|-------------|
| kube-vip ARP flapping | `vipHost` фиксирует VIP на одной ноде. Lease мёртв — плевать |
| hostPort на 2 из 4 нод | Трафик не на все 4 ноды, а только на control-plane |
| upstream keepalive stale | `keepalive=0` не помог |
| MTU | Решено ещё 14-го, не при чём |
| conntrack | ETPolicy Cluster не при чём — трафик на одну ноду |
### Что подтверждает keep-alive гипотезу
| Тест | Объяснение |
|------|-----------|
| Сразу после рестарта ingress → OK | Все соединения свежие |
| Через 5-10 мин → RST | Соединения постарели >10с |
| GET/health → POST → OK | Новое живое соединение |
| curl с ВМ → всегда OK | curl открывает новый SYN каждый раз |
| `keep-alive: 75` + JS warmup → OK на 10+ мин | Увеличили окно, warmup греет |
## Хронология
| Дата | Событие |
|------|---------|
| 2026-07-11 | Исходный деплой |
| 2026-07-14 (день) | MTU fix (Cilium 1400) — решило 51с задержку |
| 2026-07-14 (вечер) | RST диагностика: keep-alive 75 + JS warmup — частично |
| 2026-07-15 (11:53) | Helm ревизия 9 — сброс ConfigMap на дефолты (keep-alive:10) |
| 2026-07-15 | podAffinity `required` на drhider (ручной kubectl edit) |
| 2026-07-15 (16:00) | Полная диагностика: hostPort vs cloud LB |
---
## Две независимые проблемы
### Проблема #1: MTU (решена 2026-07-14)
**Симптом:** 51с задержка при POST любого размера через внешний VIP.
**Причина:** Geneve +50, underlay 1450, дефолт 1500 → фрагментация/дроп.
**Решение:** Cilium `mtu: 1400`.
### Проблема #2: ERR_CONNECTION_RESET (хост порт, решена 2026-07-15)
**Симптом:** Браузер получает TCP RST при POST 63KB через 5-10 мин после рестарта ingress.
**Ранее считалось:** keep-alive 10с или kube-vip conntrack.
**Реальная причина:** Штурвал балансирует трафик на **все 4 ноды**, но ingress стоит `replicas: 2` с **hostPort** — порт 80/443 открыт только на 2 нодах.
```
Browser → cloud LB → любая из 4 нод
├── нода с ingress-подом → hostPort 80 → nginx → ✅
└── нода БЕЗ ingress-пода → порт 80 не слушается → RST ❌
```
**50% запросов попадает на пустую ноду → RST.**
---
## Подтверждающие данные
### Helm ревизии (дамп)
| Ревизия | Время | replicaCount | Другие изменения |
|---------|-------|-------------|------------------|
| 1 (деплой) | 2026-07-11 | 2 | — |
| 8 | 2026-07-15 02:00 | **2** | — |
| 9 | 2026-07-15 11:53 | **2** | — |
Helm **не менял** replicaCount. Всегда 2. Но ConfigMap сбрасывал на дефолты (keep-alive: 10, proxy-body-size: 8m и т.д.).
### Ingress ConfigMap (последствия Helm upgrade)
```yaml
# Было (ручные правки 2026-07-14) → Стало (после ревизии 9)
keep-alive: "75" → "10" # вернулось на дефолт Штурвала
proxy-body-size: "1024m" → "8m" # тоже сброшено
error-log-level: debug → info # сброшено
upstream-keepalive-connections: "0" → сохранилось
```
### kube-vip svc_election — мёртв
```yaml
# Lease ingress/kubevip-shturval-ingress-controller-controller
holderIdentity: "" # пусто — никто не держит
leaseDurationSeconds: 1 # аномально короткий (норма 15-30)
renewTime: 2026-07-11T14:55:28Z # 4 дня назад не обновлялся
leaseTransitions: 17 # но было 17 переходов
```
svc_election никогда не работал. VIP назначен через `ipMode: VIP` в статусе сервиса, работает на уровне облака (BGP/маршрутизация).
### hostPort
```yaml
# В шаблоне пода ingress (Helm template)
hostPort: 80
hostPort: 443
```
Включён в ревизиях 8 и 9. hostPort = порт слушается ТОЛЬКО на нодах где стоит под.
### podAffinity на drhider — временный костыль
```json
{
"podAffinity": {
"requiredDuringSchedulingIgnoredDuringExecution": [{
"labelSelector": {"matchLabels": {"app.kubernetes.io/name": "shturval-ingress-controller"}},
"namespaceSelector": {"matchLabels": {"name": "ingress"}},
"topologyKey": "kubernetes.io/hostname"
}]
}
}
```
Добавлен через `kubectl edit`, НЕ через Helm. Привязывает drhider к ноде с ingress-подом. Это НЕ лечит RST (RST на уровне VIP→нода, не на уровне нода→drhider). При следующем `helm upgrade` — исчезнет.
### Kube-vip DaemonSet — не участвует
```yaml
vip_arp: true # ARP включён
vip_leaderelection: false # нет CP election
svc_election: true # должен быть — но lease мёртв
```
Даемоны не логгируют VIP `185.247.187.151`. Трафик распределяет облако, не kube-vip.
---
## Решение
**Единственное полное решение:** ingress-под на каждой ноде куда приходит трафик.
### Вариант 1 (рекомендуемый): увеличить replicas
```yaml
# Helm values
shturval-ingress-controller:
replicaCount: 4
```
Или выяснить у DevOps сколько нод в LB-пуле Штурвала и поставить `replicaCount = число нод`.
### Вариант 2 (если балансировка на все worker'ы): DaemonSet
```yaml
# Helm values
shturval-ingress-controller:
kind: DaemonSet
```
---
## Что делать сейчас (ручной воркараунд)
### 1. Починить ConfigMap (срочно)
```bash
kubectl patch configmap -n ingress shturval-ingress-controller-controller --type merge \
-p '{"data":{"keep-alive":"75","proxy-body-size":"1024m","client-body-timeout":"60","client-header-timeout":"30"}}'
```
### 2. Масштабировать ingress (временный фикс)
```bash
kubectl scale deploy -n ingress shturval-ingress-controller-controller --replicas=4
```
Поды раскидаются по всем 4 нодам → hostPort на всех → 0% RST.
⚠️ **Предупреждение:** после следующего `helm upgrade`:
- ConfigMap сбросится (нужно править Helm values)
- replicas вернётся на 2
- podAffinity на drhider исчезнет
---
## Вопросы к DevOps
1. **Сколько нод в LB-пуле Штурвала?** (на какие ноды облако направляет трафик ingress?)
2. **Можно ли увеличить `replicaCount` до числа нод?**
3. **Как правильно изменить Helm values для ingress?** (чтобы ConfigMap не сбрасывался при upgrade)
+84
View File
@@ -0,0 +1,84 @@
# v0.0.32 — фиксы по ревью обфускатора — 2026-08-18
**Дата:** 2026-08-18
**Версия:** 0.0.31 → 0.0.32
**Ветка:** master (коммит `94e588a`, после rebase над `c3c6e5b`)
**Источник:** ревью `History/2026-08-18-review-sonnet-bugs.md`
---
## Внесённые изменения
### 1. Единый обфускатор на все файлы (ТОП-1 — согласованность токенов)
- **Было:** `api_bp.py` обрабатывал каждый файл отдельным `obfuscate_files()` →
новая инстанс `TwoPassObfuscator` на файл → одна сущность получала РАЗНЫЕ токены
в разных файлах, `all_mapping` (ручной разбор CSV) был неверен.
- **Стало:** все файлы сессии → один `obfuscate_files(...)` с общим mapping.
Проверено: «ООО Ромашка» в двух файлах → один токен «Сфера_0001».
- **Побочно:** убран хрупкий ручной разбор CSV `split(",", 2)` (ломавшийся на
запятых в значениях) — больше не нужен при едином вызове.
### 2. Рекурсивный `expand_zips` (ТОП-2)
- `extractor.py:expand_zips` переписан: очередь + распаковка вложенных ZIP.
ZIP внутри ZIP теперь раскрывается. Проверено: вложенный `doc1.txt` извлекается.
### 3. SSE-дисконнект (ТОП-3)
- `api_bp.py:generate()`: ловится `(GeneratorExit, BrokenPipeError,
ConnectionResetError)` вместо одного `GeneratorExit`.
- `obfuscate_files` выполняется в отдельном потоке; прогресс через очередь;
флаг отмены (`threading.Event`) при разрыве соединения.
- Коллбек прогресса `(phase, idx, total, fname)` где phase ∈ {start, done} —
согласуется с фронт-протоколом (`start`/`done` по `idx`).
### 4. Кириллица 1С (CP437→CP866)
- `extractor.py._decode_name`: если имя не-UTF-8 (флаг 0x800 снят) и содержит
не-ASCII → `name.encode("cp437").decode("cp866")`. Проверено изолированно.
### 5. Дедупликация имён
- `builder.py:build_zip`: одинаковое имя+контент → пропуск; имя+разный контент →
суффикс `_2`, `_3`... Проверено: `['a.md', 'a_2.md']`.
- `obfuscator.py:_dedupe_file_names`: уникализация имён на входе до прохода 1
(защита `all_texts[fname]` от перезаписи).
### 6. upload(): цикл по всем файлам
- `api_bp.py:upload()`: обрабатывает все `request.files.getlist("files")`
(поддержка загрузки целых папок через `webkitdirectory`).
- Сохранено поведение ошибок: файл без имени → «No filename» (совместимо с тестом).
### 7. Лимит объёма сессии
- `session.py`: `MAX_SESSION_BYTES = 500 MB`; в `add_file` — скип при превышении.
### 8. Защита от ZIP-бомб
- `extractor.py`: проверка `total + file_size > LIMIT` ДО `zf.read`; скип архива
при превышении ratio/лимита/числа файлов; лимиты глобально на вызов.
---
## Тесты
| Набор | Результат |
|-------|-----------|
| test_builder | 20/20 OK |
| test_zip | 28/28 OK |
| test_replacer | 10/10 OK |
| test_scanner | 20/20 OK |
| test_upload | 13/13 OK |
| test_extractor | **14/15** — падает `.doc` (внешний сервис liberta) |
`test_extractor` `.doc`: отправляется мусорный OLE2-магик
`b"\xD0\xCF\x11\xE0\x00"*10`, сервис `liberta.containerk8s.dev.nubes.ru/convert`
возвращает «conversion produced no .docx». НЕ регрессия от v0.0.32 — ветка
`.doc`/`doc_to_markdown` не менялась. Вероятно, сервис изменил поведение или
не принимает невалидный контент. Требует отдельного разбора.
---
## Git
- Remote был впереди (посторонний коммит `c3c6e5b` про магистратуру) → `git pull --rebase`.
- Push успешен: `c3c6e5b..94e588a`.
- Ветка-сохранение документации: `save-docs-2026-08-18` (`de072d8`).
## TODO / открытые вопросы
- [ ] Разобраться с тестом `.doc` (возможно, обновить тест на актуальный сервис).
- [ ] Визуально проверить SSE-прогресс во фронте после рефакторинга.
+40
View File
@@ -0,0 +1,40 @@
# v0.0.41 — время на конкретный файл в колонке Статус — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.40 → 0.0.41
---
## Суть
В колонке «Статус» таблицы у всех файлов показывалось ~одинаковое ОБЩЕЕ время.
Причина: фронт считал время как разницу `start`→`done`, а LLM — общий для всех
файлов (в проходе 1) — попадал в интервал КАЖДОГО файла.
Теперь бэк считает время на КОНКРЕТНЫЙ файл (extract_text + regex + замена,
БЕЗ общего LLM) и передаёт его в `done`-событии.
## Изменения
### `drhider/obfuscator.py`
- `import time` добавлен.
- Заведён `file_times = [0.0] * total`.
- Проход 1: `file_times[i] += time.time() - t0` вокруг extract_text + regex.
- Проход 2: `file_times[i] += time.time() - t0` вокруг замены.
- `progress_cb` расширен до `(phase, idx, total, fname, elapsed)`; в `done`
передаётся `round(file_times[i], 2)`.
### `site/routes/api_bp.py`
- `progress(phase, idx, total_, name, elapsed)`; в SSE `done` добавлен `elapsed`.
### `site/templates/index.html`
- В `done`-обработчике: если `d.elapsed > 0` — показать `d.elapsed.toFixed(1)`
(время файла от бэка), иначе fallback на старое вычисление.
### `site/app.py`
- `VERSION = "0.0.41"`.
## Проверка
- События: `[start(0,0.0), start(1,0.0), done(0,<время>), done(1,<время>)]`.
- `py_compile`, `node --check` — OK.
- Тесты: builder 20/20, replacer 10/10, scanner 20/20, zip 28/28.
+39
View File
@@ -0,0 +1,39 @@
# v0.0.40 — имя файла в live-блоке + сброс итогов — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.39 → 0.0.40
---
## Суть
Два UX-фикса во время обработки:
1. **Имя текущего файла в live-блоке.** Раньше `start`-событие (с именем файла)
слалось только в **проходе 2** (замена), который идёт ПОСЛЕ LLM. А LLM — в
проходе 1 и самый долгий. Поэтому во время LLM висело «Подготовка…» вместо
имени файла.
2. **Сброс старых «Итогов» при повторной обфускации.** Раньше блок
«📊 Итоги обработки» не скрывался при новом запуске — старые итоги висели
во время новой обработки.
## Изменения
### `drhider/obfuscator.py`
- `progress_cb("start", i, total, name)` перенесён из прохода 2 в **проход 1**
(перед `extract_text` каждого файла).
- В проходе 2 остался только `progress_cb("done", ...)`.
- Порядок событий: `start`×N (extract_text) → `done`×N (замена).
### `site/templates/index.html`
- В начале `uploadFiles()` добавлено скрытие `#statsBlock` и `#liveBlock`
(как уже скрывается `db`).
### `site/app.py`
- `VERSION = "0.0.40"`.
## Проверка
- Порядок событий: `[start(0), start(1), done(0), done(1)]` — корректно.
- `node --check`, `py_compile` — OK.
- Тесты: builder 20/20, replacer 10/10, scanner 20/20, zip 28/28.
+55
View File
@@ -0,0 +1,55 @@
# v0.0.38 — живой таймер обработки + индикация ИИ — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.37 → 0.0.38
---
## Суть
Юзер должен видеть, что обработка идёт (не зависла), особенно пока LLM
«переваривает» большой файл. Добавлены:
- крупный live-блок «⏱ Обработка…» с тикающим таймером общего времени;
- строка текущего файла с размером («Файл 3/5: имя (5.0 МБ)»);
- индикация ИИ: «🤖 ИИ обрабатывает… Nс» (от реальных данных с бэка).
Итоговая формулировка времени стала ясной: «…общее Xс, из них ИИ Yс».
## Изменения
### `drhider/llm_client.py`
- В `__init__` добавлены `llm_active` (bool) и `llm_started` (float).
- Метод `llm_elapsed_now()` — секунд с начала текущего LLM-вызова (0 если не активен).
- `complete()`: ставит `llm_active=True`/`llm_started=time.time()` перед HTTP,
сбрасывает `llm_active=False` в `finally`. `import time` добавлен.
### `site/routes/api_bp.py`
- В ветке `except queue.Empty` (пока воркер занят) — heartbeat-событие SSE:
`event: llm` → `{active, elapsed, tokens}` (раз в ~1с, timeout=1).
### `site/templates/index.html`
- CSS для `.live-block`, `.live-title`, `.live-file`, `.live-timer` (40px), `.live-llm`.
- HTML: блок `#liveBlock` с `#liveFile`, `#liveTimer`, `#liveLlm`/`#liveLlmTime`;
в итоговом блоке подпись «Время ИИ» → «Из них ИИ».
- JS (фаза 2):
- показ `#liveBlock`, интервал `liveRefresh` (200мс) — тикает `#liveTimer`;
- обработчик `start` — в `#liveFile` имя текущего файла + размер из `sf[idx]`;
- обработчик `llm` — показывает/скрывает `#liveLlm` и обновляет `#liveLlmTime`;
- `complete`/`onerror`/`catch` — скрыть `#liveBlock`, очистить интервалы;
- итоговый текст: «общее Xс, из них ИИ Yс».
- `resetAll` — скрывает `#liveBlock`.
### `site/app.py`
- `VERSION = "0.0.38"`.
## Проверка
- `node --check`, `py_compile` — OK.
- `LLMClient`: во время вызова `llm_active=True`, `llm_elapsed_now()≈0.3с`; после —
`False`; накопление токенов и времени — ок.
- SSE end-to-end: `complete` содержит `{total, tokens, llm_sec}`. Heartbeat `llm`
генерируется в ветке `except queue.Empty` при долгой обработке (локально без
LLM обработка мгновенная, поэтому heartbeat не успевает — в проде при долгой
LLM-обработке будет слаться раз в сек).
## Примечание
Бэкенд метрики не менял (только флаг/метод для live-таймера + heartbeat).
+45
View File
@@ -0,0 +1,45 @@
# v0.0.34 — вывод токенов и времени LLM — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.33 → 0.0.34
---
## Суть
Пользователь замечает, что обфускация долгая. Добавлен вывод в UI:
- суммарных токенов LLM за прогон;
- суммарного времени работы LLM за прогон.
Строка статуса после завершения: «✅ Обработано N файлов за Xс · LLM: Yс · Z токенов».
## Изменения
### `drhider/llm_client.py`
- В `LLMClient.__init__` добавлены кумулятивные счётчики:
- `tokens_prompt`, `tokens_completion`, `llm_sec` (float);
- свойство `tokens_total = prompt + completion`.
- В `complete()` после ответа:
- `self.llm_sec += r.elapsed.total_seconds()` — реальное время HTTP-вызова;
- `usage` (из `r.json()`) → `self.tokens_prompt` / `self.tokens_completion`
(через `.get(..., 0)` — устойчиво к отсутствию `usage`).
- Возврат `complete()` не изменён (по-прежнему `content`) — `scan_llm_ner` не трогали.
### `site/routes/api_bp.py`
- В `worker()` после `obfuscate_files` в очередь `result` добавляется
`stats = {tokens: llm.tokens_total, llm_sec: round(llm.llm_sec, 1)}`.
- В `event: complete` данные: `{total, tokens, llm_sec}`.
### `site/templates/index.html`
- В обработчике `complete`: если `d.llm_sec > 0` — добавляет
«· LLM: {llm_sec}с» и, если `d.tokens > 0` — «· {tokens} токенов».
### `site/app.py`
- `VERSION = "0.0.34"`.
## Проверка
- `py_compile` + `node --check` — OK.
- Накопление: 2 вызова → 240 prompt + 90 completion = 330 total, llm_sec = 6.0.
- Устойчивость: ответ без `usage` → токены 0, время 1.0 — без падения.
- SSE end-to-end: `complete` приходит с `{"total":1,"tokens":0,"llm_sec":0.0}`
(локально без ключа LLM — 0; в кластере с ключом — ненулевые).
+41
View File
@@ -0,0 +1,41 @@
# v0.0.37 — крупный блок статистики LLM в UI — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.36 → 0.0.37
**Файл:** `site/templates/index.html`, `site/app.py` (версия)
---
## Суть
Раньше статистика LLM («... LLM: 5.3с · 1894 токенов») была МЕЛКИМ текстом
в статус-строке под кнопкой — юзер её не замечал. Теперь после обработки
показывается **крупный заметный блок** «📊 Итоги обработки»:
| Показатель | Значение |
|---|---|
| Общее время | крупное число, с |
| Время ИИ | крупное число, с |
| Токены ИИ | крупное число |
Оформление: зелёная плашка (фон-градиент, рамка 2px), три белые карточки,
значения шрифтом 24px жирным. Блок скрыт до завершения и при сбросе.
## Изменения (`index.html`)
- CSS: `.stats-block`, `.stats-title`, `.stats-row`, `.stat-item`,
`.stat-label`, `.stat-value`.
- HTML: блок `#statsBlock` с `#stTotalTime`, `#stLlmTime`, `#stLlmTokens`
(после кнопок скачивания).
- JS:
- `complete`-обработчик — заполняет и показывает блок (общее время всегда;
время ИИ и токены — если `llm_sec > 0`, иначе «—»);
- `resetAll` — скрывает блок.
## Проверка
- `node --check` — OK.
- Визуально через браузер: блок появился полноширинным, три карточки,
значения крупно — заметно.
- Версия `app.VERSION = 0.0.37`.
## Бэкенд
- Не менялся (метрики `tokens`/`llm_sec` в `event: complete` уже есть с v0.0.34).
@@ -0,0 +1,37 @@
# v0.0.46 — удалён process_names + git push HTTP/2 проблема — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.45 → 0.0.46
---
## Удалён тестовый API process_names
- `process_names` (обфускация файлов по именам из TEST_INPUT_DIR) **удалён**.
- Причина: сервер не может читать локальные файлы клиента (нет доступа к его
машине). Файлы в поде — неудобно. API бесполезен.
- Удалены: endpoint, `TEST_INPUT_DIR`/`_TEST_API_ENABLED`, `import os`, History-файл.
- Версия 0.0.46.
## Находка: git push к gitea зависал (HTTP/2)
**Симптом:** `git push origin master` висел бесконечно (таймауты), хотя
`git ls-remote` и `curl` к gitea работали (200, быстро).
**Причина:** git использовал **HTTP/2** для отправки pack; шлюз
`gitea.services.ngcloud.ru` блокировал передачу данных по HTTP/2
(та же проблема, что была в drhider с HTTP/2 — ERR_HTTP2_PROTOCOL_ERROR).
**Решение:** push через HTTP/1.1 мгновенно прошёл:
```bash
git -c http.version=HTTP/1.1 push origin master
# и закреплено в конфиге:
git config http.version HTTP/1.1
```
**Проверка:** `8d7a7df..d2f2bd3 master -> master`, синхронизировано.
## Полезно
- На этой машине/шлюзе для git (и возможно других HTTP/2 клиентов) использовать
HTTP/1.1.
- git config `http.version HTTP/1.1` уже установлен.
@@ -0,0 +1,45 @@
# v0.0.39 — оптимизация apply_replacements (однопроходная замена) — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.38 → 0.0.39
**Файл:** `drhider/replacer.py`, `site/app.py` (версия)
---
## Суть
Обфускация PDF 19 МБ шла 132.7с, из них ИИ (LLM) всего 7с — остальное (~125с)
тратил основной код. Замер выявил узкое место: `apply_replacements`.
## Причина
`apply_replacements` для **каждой** сущности из mapping делала **2 полных
прохода `re.sub` по всему тексту** (основной + фоллбэк `_md_tolerant_pattern`).
Сложность O(N_сущностей × размер_текста × 2) — квадратичная по числу сущностей.
При 300+ сущностей и тексте в несколько МБ — десятки секунд.
Замер (0.9 МБ, 304 сущности): **17.3 с**.
## Решение
Однопроходная замена:
- Все ключи собираются в **один** regex-паттерн через альтернацию `(?:...|...)`
(по убыванию длины, с `re.escape`, границы `(?<!\w)...(?!\w)` для alnum-сущностей).
- Один `re.sub` с callback (lookup в mapping), O(text).
- Фоллбэк `_md_tolerant_pattern` применяется **только к ключам, не найденным**
основной заменой (отслеживание через `matched_keys`), а не ко всем.
## Результат замера
- 300 сущностей реально в тексте (1.3 МБ): **1.17 с** (было бы десятки сек).
- Реалистичный сценарий (10 сущностей): **0.29 с**.
- Согласованность токенов и mapping.csv сохранены (проверено через `obfuscate`).
## Проверка
- `test_replacer` 10/10, `test_builder` 20/20, `test_scanner` 20/20, `test_zip` 28/28.
- Пайплайн `obfuscate`: «ООО Ромашка»→«Вектор_0001» в двух файлах согласовано,
mapping.csv корректен.
## Примечание
- `scan_regex` (0.4с) и upload (сеть ~1.7 МБ/с) — не узкие места, не трогали.
- `pdf_to_markdown` (pdfplumber) локально не замерен (нет библиотеки); остаётся
потенциальным вторым узким местом для будущей оптимизации (pypdf/pdfminer).
+60
View File
@@ -0,0 +1,60 @@
# 2026-08-19 — Бенчмарк UI: 5 прогонов DownLoads.zip (v0.0.49)
Набор: `files/DownLoads.zip` (21.9 МБ) → распаковывается в 18 файлов
(4 docx + 14 pdf, включая `0144-03-2023_отчет об оценке.pdf` 19.0 МБ).
Условия: сервер waitress, порт 5059, threads=8, локально.
LLM-ключ отсутствует — этап ИИ не выполнялся (0с, в статистике «—»).
Итог каждого прогона: «✅ Обработано 18 файлов», кнопки ZIP/CSV, блок статистики.
## Итоговые метрики (5 прогонов)
| Прогон | Распаковка zip, мс | Стена UI, с | SSE «общее», с |
|--------|--------------------|-------------|----------------|
| 1 | 855 | 50.3 | 49.0 |
| 2 | 851 | 47.9 | 46.6 |
| 3 | 860 | 47.1 | 45.9 |
| 4 | 833 | 47.8 | 46.1 |
| 5 | 850 | 47.9 | 46.7 |
- Распаковка: 833–860 мс (Δ 27 мс) — стабильно, ~0.85 с.
- SSE «общее»: 45.9–49.0 с (Δ 3.1 с), среднее ≈ 46.9 с.
- Стена UI: 47.1–50.3 с (загрузка локально ≈ 1 с).
- SSE не рвётся: 5/5 прогонов до конца, по ~50 с каждый.
## Узкое место — 19 МБ PDF (0144-03-2023_отчет об оценке.pdf)
Время по прогонам: 25.8 / 24.5 / 26.0 / 25.0 / 25.8 с → стабильно ~25.4 с.
Это ~53–55% от общего времени. Причина — pdfplumber extract_text.
## Время по файлам, среднее по 5 прогонам (с)
| Файл | Размер | Среднее |
|------|--------|---------|
| 20_ФИЯР_Порядок распределения…docx | 13.1 KB | 0.04 |
| Proxy Voting Form 2026 EGM.docx | 16.1 KB | 0.1 |
| допник-1-XXX002-01200_3.docx | 11.9 KB | 0.1 |
| Экзаменационный материал…docx | 28.9 KB | 0.2 |
| 0144-03-2023_отчет об оценке.pdf | 19.0 MB | 25.4 |
| 20_ФИЯР_Регламент проведения ВИ МАГ_2026 (1).pdf | 167.2 KB | 1.1 |
| 959399eab8f6a91df785f469b5851574.pdf | 328.7 KB | 1.5 |
| EAC_979 419 6577.pdf | 314.1 KB | 0.3 |
| examrules_test.pdf | 486.3 KB | 2.3 |
| General Terms.pdf | 764.8 KB | 3.8 |
| Marshrutnaya_kvitanciya.pdf | 291.1 KB | 0.6 |
| P010028436 CERTIFICATE…pdf | 473.7 KB | 0.2 |
| P010028436 POLICY…pdf | 623.1 KB | 0.5 |
| PR00078013…pdf | 521.4 KB | 1.5 |
| Transaction_Confirmation_en…pdf | 70.3 KB | 0.1 |
| Лингвистика маг.pdf | 596.5 KB | 2.1 |
| Отчет арбитражного управляющего…pdf | 267.4 KB | 3.5 |
| ПРОГРАММА ВСТУПИТЕЛЬНЫХ ИСПЫТАНИЙ…pdf | 138.5 KB | 1.9 |
Сумма средних ≈ 45.3 с — сходится с SSE «общее».
## Стабильность
- Всё, кроме 19 МБ PDF, — стабильно: docx 0.0–0.2 с, PDF 0.1–4.3 с.
- Погрешность повторов мала; основной вклад — один большой PDF.
- Вывод: без LLM прогон 18 файлов (вкл. 19 МБ PDF) ≈ 46–49 с.
С LLM добавится этап ИИ (в проде — ключ есть в кластере).
+59
View File
@@ -0,0 +1,59 @@
# Диагностика медленной/нестабильной загрузки + v0.0.43 — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.42 → 0.0.43
---
## Проблема
Юзер: «Таймаут 30с» при загрузке 19 МБ PDF в drhider. Раньше (11с) грузилось.
## Диагностика (факты)
### Таймаут 30с
- Введён 14.07.2026 (коммит `b6dac6f`, «не висеть бесконечно»), НЕ мой код.
- 19 МБ / 30с = 0.63 МБ/с — при медленном/нестабильном канале не хватает.
### Замеры загрузки с ВМ (внешний путь)
- 1 МБ → 0.16с (быстро)
- 5 МБ → 51.6с (медленно)
- 10 МБ → 52.4с (медленно)
- Повтор 10 МБ ×4: **1.07с / 52.3с / 52.3с / 52.3с** — НЕСТАБИЛЬНО, чаще 52с.
- Повтор 1 МБ: то 51с, то 0.5с — задержка НЕ зависит от размера.
### Тайминг (10 МБ)
- `connect=0.046с`, `starttransfer=0.09с`, `total=52.5с`.
- Сервер отвечает быстро (starttransfer), но соединение «висит» ~52с после ответа.
- `Connection: close` — НЕ помогает (то же 52с).
### Сервер
- Из loadtest: внутри кластера 10 МБ = 0.2с — сервер/Flask быстрые.
- ClusterIP с ВМ недоступен (ВМ вне кластера) — проверить напрямую не вышло.
## Применённые аннотации ingress (НЕ помогли)
Добавлены на ingress `pythonk8s` (ns 20a75175...):
```yaml
nginx.ingress.kubernetes.io/proxy-request-buffering: "false"
nginx.ingress.kubernetes.io/client-body-buffer-size: "1024m"
```
- `kubectl patch` — применено, nginx reload (75с).
- В конфиге: `client_body_buffer_size 1024m` — применилось.
- `proxy_request_buffering on` — НЕ применилось (аннотация не подхватилась shturval-контроллером).
- Замер после: 5 МБ = 51.7с, 19 МБ = 53.8с — эффекта НЕТ.
## Вывод
- Задержка ~50с НЕ в коде drhider, НЕ в буферизации nginx, НЕ зависит от размера,
НЕСТАБИЛЬНА (то 0.5с, то 52с). Вероятно: внешний путь шлюз/балансировщик/сеть
(потеря пакетов / TCP RTO ~50с, или периодическая задержка шлюза).
- Совпадает с диагнозом loadtest: «на iot-naeel шлюз держит ~50с».
## Решение (v0.0.43) — защита от ошибки юзера
- `xhr.timeout`: 30000 → **300000 (300с)** — при нестабильной загрузке файл
догрузится (медленно, но без «Таймаут 30с»).
- Сообщение: «Таймаут 30с» → «Таймаут 300с».
## Осталось
- Аннотации на ingress оставлены (`client-body-buffer-size 1024m` не вредит;
`proxy-request-buffering` не работает на shturval — искать правильный способ).
- Корень задержки ~50с на внешнем пути — НЕ найден, требует сетевой диагностики
(tcpdump, проверка балансировщика/шлюза платформы).
+39
View File
@@ -0,0 +1,39 @@
# v0.0.49 — индикатор распаковки, разделение фаз, таймауты SSE — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.48 → 0.0.49
---
## Контекст
Отзыв тестировщика:
1. «После Выбрать файлы долго не респондит — разбирает архивы» → нет индикатора.
2. «По Обфусцировать — двойной проход по файлам» → фазы загрузки и обработки не разделены.
3. «Ошибка: SSE connection failed» → долгое SSE рвалось шлюзом.
## Изменения
### Ingress (kubectl patch, не код)
- `proxy-read-timeout: 600 → 1800`
- `proxy-send-timeout: 600 → 1800`
— шлюз больше не рвёт долгое SSE (было ~10 мин, стало 30 мин).
### `site/templates/index.html`
- **Индикатор распаковки**: при выборе файлов с `.zip` — `cursor: wait` +
статус «Разбираю архивы…», снимается после распаковки (try/finally).
- **Разделение фаз**:
- Фаза 1: статус «Загрузка (этап 1/2) i/N: имя».
- Фаза 2: статус «Обработка (этап 2/2)…», таймер «Обработка (этап 2/2)… Nс».
- При старте фазы 2 загрузочные статусы сбрасываются на «⏳».
### `site/app.py`
- `VERSION = "0.0.49"`.
## Проверка
- `node --check`, `py_compile` — OK.
- Тесты builder/replacer/scanner/zip — все зелёные.
## Примечание
- Причина «SSE connection failed»: шлюз рвал долгое SSE (proxy-read-timeout 600с).
Таймауты подняты до 1800с. Если обработка > 30 мин — нужно ещё выше или
авто-реконнект EventSource (отложено: реконнект вызывает повторную обработку).
+52
View File
@@ -0,0 +1,52 @@
# v0.0.33 — раскрытие ZIP в таблицу (фронтенд) — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.32 → 0.0.33
**Файл:** `site/templates/index.html` (фронтенд), `site/app.py` (версия)
---
## Суть
Раньше выбранный `.zip` попадал в таблицу одной строкой и не раскрывался.
Теперь при выборе файлов ZIP **распаковывается на клиенте**, файлы из архива
попадают в таблицу **по отдельности** (полный относительный путь).
## Что добавлено (`index.html`, в тот же `<script>`)
1. **Нативный распаковщик ZIP** без внешних библиотек:
- `DecompressionStream('deflate-raw')` — поддержка сжатых (DEFLATE) архивов
(WhatsApp/1С архивы сжаты; старый STORE-only `unzip` из v5 не подходил).
- `parseZip()` — разбор EOCD → Central Directory → local headers.
- `dosToMs()` — DOS date/time (4 байта) → timestamp (мс).
- `decodeZipName()` — имя: UTF-8 (если флаг 0x800) иначе CP437→CP866
(кириллица из 1С, зеркалит серверный `_decode_name`).
2. **Рекурсия** `listZipFiles()`:
- директории (`name` оканчивается на `/`) пропускаются;
- вложенный `.zip` распаковывается рекурсивно;
- имя сохраняется **полным путём** (`dir1/sub/file.pdf`).
3. **Дедуп + суффикс** `addFileWithDedup()`:
- структура `fileMeta` (Map: имя → `{size, mtime}`);
- имя есть + тот же размер И та же дата → точный дубль → **пропустить**;
- имя есть + разный размер ИЛИ дата → **суффикс** `name_2.ext`, `name_3.ext`…;
- дата: для обычных файлов `File.lastModified` (мс), для извлечённых из ZIP —
DOS date/time → мс.
4. `fi.addEventListener('change')` переписан в `async`:
- `.zip` → `listZipFiles` → каждый файл через `addFileWithDedup`;
- при ошибке распаковки (неподдерживаемый метод/не-ZIP) — zip добавляется как есть.
## Проверка
- JS-синтаксис: `node --check` — OK.
- Рекурсия вложенного ZIP (`nested.zip` → `sub/a.txt`) — извлечён, проверено в Node.
- Дедуп/суффикс: `dup.txt` (3) → `dup_2.txt` (5) → `dup_3.txt` (2); точный дубль пропущен.
- Backend не менялся (кроме версии в `app.py`).
## Открытый вопрос
`DecompressionStream` требует современных браузеров (Chrome 103+, Safari 16.4+,
Firefox 113+). AES-шифрованные ZIP (метод 99) не поддерживаются — при этом архив
добавляется в таблицу как есть (fallback).
+38
View File
@@ -0,0 +1,38 @@
# v0.0.35 — фильтр документов при раскрытии ZIP — 2026-08-19
**Дата:** 2026-08-19
**Версия:** 0.0.34 → 0.0.35
**Файл:** `site/templates/index.html` (фронт `listZipFiles`), `site/app.py` (версия)
---
## Суть
При раскрытии ZIP в таблицу ранее вытаскивались ВСЕ файлы, включая изображения
(WhatsApp jpeg и т.п.). Теперь из ZIP извлекаются **только документы**:
Разрешённые расширения: `.pdf`, `.doc`, `.docx`, `.txt`, `.md`.
Всё прочее (jpeg, png, xls, exe и т.д.) — пропускается. Вложенные ZIP
раскрываются рекурсивно, и фильтр применяется и к их содержимому.
## Изменение
`listZipFiles()` в `index.html`:
```js
const allowedExt = ['.pdf', '.doc', '.docx', '.txt', '.md'];
...
else if (allowedExt.some(ext => low.endsWith(ext))) {
out.push(new File([e.data], e.name, { lastModified: e.dosMs }));
}
// иначе — не документ, пропускаем
```
## Проверка
- `node --check` — OK.
- Тест на архиве с `doc.pdf, old.doc, n.docx, a.txt, r.md, pic.jpeg, pic2.png`:
извлечены только 5 документов, jpeg/png пропущены — OK.
## Вопрос (отложен)
- Нужно также упомянуть в подсказке на странице требование современного
браузера (`DecompressionStream`: Chrome 103+, Safari 16.4+, Firefox 113+).
+22
View File
@@ -0,0 +1,22 @@
# 2026-08-20 — Устойчивость к битым файлам (v0.0.53)
## Root cause (найден по логу /tmp/drhider.log)
SSE "connection failed" при обработке НЕ был обрывом шлюза. Воркер падал целиком:
```
worker: exception: PdfminerException(PDFSyntaxError('No /Root object! - Is this really a PDF?'))
```
на битом PDF (миниатюра 142 байта). Исключение в `extract_text` не было обёрнуто
по-файлово → воркер ронялся → SSE слал `error` → фронт показывал "SSE connection failed".
## Фикс (drhider/obfuscator.py)
Проход 1: `extract_text` обёрнут в try/except. При ошибке файл добавляется в
`skipped`, логируется warning, файл пропускается, остальные обрабатываются.
Проход 2: файлы из `skipped` пропускаются (не попадают в результат).
Проверено на flat/ (10 файлов: 5 валидных + 5 битых миниатюр):
- битые PDF/docx пропущены (PDFSyntaxError/BadZipFile), валидные 5 → в zip.
- воркер не падает, complete доходит.
## Примечание
Миниатюры 54-142 B в /mnt/y/T/flat — НЕ валидные PDF/docx (мусор из корзины).
Их можно удалить из тестового набора (или оставить как кейс устойчивости).
+24
View File
@@ -0,0 +1,24 @@
# 2026-08-20 — КРИТИЧНО: mapping.csv (ключ расшифровки) убран из выходного ZIP (v0.0.56)
## Проблема (утечка секретных данных)
Выходной ZIP содержал `mapping.csv` — таблицу соответствия «оригинал → замена»,
т.е. ключ расшифровки (реальные ФИО/телефоны/ИНН ↔ токены). Если ZIP передать
третьему лицу — тот получал и обфусцированные файлы, и таблицу расшифровки.
CSV должен храниться/скачиваться ОТДЕЛЬНО от обфусцированных файлов.
## Фикс (drhider/builder.py `build_zip`)
- Убрана запись `mapping.csv` в архив. Теперь в ZIP — ТОЛЬКО обфусцированные файлы.
- Параметр `mapping_csv` сохранён в сигнатуре (для совместимости вызовов), но в
архив не пишется.
- CSV продолжает генерироваться (`build_mapping_csv`) и отдаваться отдельно
через `/api/csv/<sid>` (кнопка «Скачать CSV»), как написано на странице.
## Тесты (tests/test_builder.py)
- Блок «ZIP с mapping.csv» переписан: теперь проверяет, что mapping.csv НЕ в архиве.
- Интеграционный блок: ZIP содержит только doc.md, CSV проверяется отдельной строкой.
- Все 106 тестов OK.
## Проверка
- python3 tests/test_builder.py — 20/20 OK (включая новый сценарий «mapping не в zip»).
- Остальные тесты — без изменений поведения, OK.
- VERSION 0.0.56.
@@ -0,0 +1,44 @@
# 2026-08-20 — Честный ответ Sonnet об ускорении + реализация А (v0.0.55)
## Честный ответ Sonnet (History/2026-08-20-sonnet-query-honest-speed.md)
Вопрос: можно ли значительно ускорить обработку, не рискуя устойчивостью/таблицами?
Ответ Sonnet (принято, согласуется с замерами):
- **Безопасно ускорить extract_text() на pdfplumber НЕЛЬЗЯ** — pdfminer pure Python,
CPU-bound, GIL полностью блокирует threading. (Подтверждено: extract_text 64.4с на Spartan10.)
- A) Безопасно-просто:
- A1. Threading для .doc (liberta, IO-bound) — N×~120с → ~120с при нескольких .doc.
- A2. Кэш compiled regex между файлами (проход 2).
- B) Заметный выигрыш, но с рисками (ОТЛОЖЕНО):
- B1. ProcessPool по файлам (spawn) — 3 PDF × 25с → ~30с (только 2+ PDF, CPU=2).
- B2. ProcessPool по страницам (spawn) — Spartan10 64с → ~35с (сложно: content, порядок).
- C) Не стоит:
- fork ProcessPool при session в памяти (COW-раздутие, 4Gi на пределе) — только spawn.
- Threading по страницам/файлам PDF — GIL, ноль.
- Итог Sonnet: безопасного значительного ускорения ОДНОГО PDF нет. Для набора 2+ PDF —
B1. При CPU=2 потолок 2x.
## Решение пользователя
«Делай по А, безопасно» — реализованы ТОЛЬКО A1 и A2. B — НЕ отложено (не делаем).
## Реализация (v0.0.55)
### A1 — threading для .doc (obfuscator.py, проход 1)
- .doc файлы (конвертация через HTTP liberta, IO-bound) запускаются в
ThreadPoolExecutor(max_workers=min(4, N_doc)).
- Основной цикл сохраняет порядок: для .doc берёт future.result(), остальные —
последовательно. progress_cb/порядок start/done не меняются.
- Выигрыш: HTTP-конвертация .doc перекрывается с CPU-обработкой PDF/docx.
### A2 — кэш compiled regex (replacer.py + obfuscator.py)
- replacer.py: вынесен `_build_combined_re(sorted_keys)`, `apply_replacements` получил
параметр `compiled_re=None` (если None — компилирует сам).
- obfuscator.py: после формирования `self._sorted_keys` компилируется один раз
`self._compiled_re`, передаётся во все вызовы apply_replacements (проход 2).
- __init__/finally: _compiled_re инициализируется/очищается.
## Проверка
- Все 106 тестов OK (test_replacer 10/10 — apply_replacements с compiled_re).
- Интеграция: flat/ (10 файлов) → 5 обработано (EB.md, EB1.md, Spartan10, TKM, mini_78b),
битые пропущены, .doc через liberta в потоках. 78.3с (в основном Spartan10 64с extract_text).
- VERSION 0.0.55.
+41
View File
@@ -0,0 +1,41 @@
# 2026-08-20 — LLM обрабатывает ВСЕ данные (чанкинг) — v0.0.57
## Задача
Пользователь: LLM должен обрабатывать ВСЕ данные, а не урезку 8000 символов.
Ветка сохранения пре-LLM конфигурации: `save-pre-llm-v0.0.56` (указывает на прежний master).
## Изменения (drhider/scanner.py)
`scan_llm_ner` переписан:
- Убраны глобальные урезки `t[:3000]` и `combined[:8000]`.
- Каждый файл обрабатывается ПОЛНОСТЬЮ: split_into_chunks(6000, overlap 500).
- Чанки файла — параллельно (ThreadPoolExecutor, concurrency 4), крупные файлы вперёд.
- Для каждого чанка — полный промпт + «Already captured by regex» (mapping).
- Дедуп найденного через normalize_entity (пробелы/регистр/кавычки/тире/ё).
- Верификация _verify_entity против ПОЛНОГО текста файла — отсекает галлюцинации LLM.
- regex-найденное не дублируется.
Новые хелперы (scanner.py):
- split_into_chunks(text, size=6000, overlap=500) — границы \n\n,\n,. ,? ,! ,; ,, ," "
- normalize_entity(s) — канонизация для дедупа
- _verify_entity(value, full_text) — точное/нормализованное вхождение
- _build_llm_prompt(text, already_found)
- _call_llm(text, llm_client) — вызов + разбор JSON (пусто при ошибке)
Параметры верхнего уровня (легко тюнить):
- _CHUNK_SIZE = 6000, _CHUNK_OVERLAP = 500, _LLM_CONCURRENCY = 4
## Ожидаемый эффект
- LLM покрывает все файлы целиком (в т.ч. хвосты и таблицы после 3000 символа).
- Время на ~300 файлов ≈ 3.5 мин при 4 потоках (~390 вызовов).
- Меньше галлюцинаций (верификация).
## Проверка
- py_compile OK.
- test_scanner 20/20.
- Юнит хелперов: split (12К→4 чанка ≤6100), verify (галлюцинация→False), normalize OK.
- Все 106 тестов OK.
- VERSION 0.0.57.
## Примечание
Реальная проверка LLM-покрытия и времени — на наборе T/ при прогоне на проде/локально
с ключом LLM (локально ключ отсутствует → LLM не выполняется, только regex).
+24
View File
@@ -0,0 +1,24 @@
# 2026-08-20 — Файловый лог в поде (v0.0.52)
## Проблема
В v0.0.51 добавили логирование в stderr, но на платформе Штурвал
`kubectl logs` НЕ показывает stdout/stderr приложения (fd 1/2 python -> pipe,
который читает супервизор платформы, а не kubelet). Логи приложения недоступны.
## Решение
`setup_logging()` в site/app.py:
- логи в stderr (для локального запуска)
- + `FileHandler` в `/tmp/drhider.log` (путь через env `LOG_FILE`)
- уровень через env `LOG_LEVEL` (DEBUG/INFO/WARNING)
- root.handlers.clear() + свои handler'ы (не полагаемся на basicConfig)
- если файл не открылся — не падаем, пишем в stderr
## Как читать логи в поде
```
kubectl exec -n 20a75175-a58c-49cb-b8fa-e86367b1a8dc <pod> -c app -- cat /tmp/drhider.log
```
(или tail -f). Версия 0.0.52.
## Проверено локально
Файл /tmp/drhider.log создаётся, пишет "Logging configured, version=0.0.52"
и сообщения логгеров.
+32
View File
@@ -0,0 +1,32 @@
# 2026-08-20 — Максимальное логирование (v0.0.51)
## Проблема
SSE рвётся на проде (пару минут), обработка при этом продолжается (воркер жив).
Логов в поде не было — приложение не писало в stdout, нельзя было диагностировать
обрыв (внешний шлюз vs генератор).
## Решение
1. `site/app.py` — `setup_logging()`:
- уровень из env `LOG_LEVEL` (DEBUG/INFO/WARNING), default INFO
- `logging.basicConfig(..., stream=sys.stderr, force=True)` — в stdout/stderr пода
- формат с таймстампом; уровни для логгеров drhider/app/routes/session
- `LOG_LEVEL` задаётся через env-переменные приложения на платформе
2. `site/routes/api_bp.py` — подробные логи:
- upload: каждый файл (имя, размер), итог, ошибки
- process_stream: start, worker start/done (время, токены, zip_len),
каждое событие progress (start/done) с idx/name/elapsed,
disconnect на heartbeat/progress/complete/error (с причиной),
result/complete, error event
3. VERSION поднята до 0.0.51
## Проверка (локально, DEBUG)
Upload 1 файла + SSE: видны все события от upload до complete.
`LLM NER failed: Illegal header value b'Bearer '` — ожидаемо без ключа (локально).
## Как читать логи пода после деплоя
```
kubectl logs -n 20a75175-a58c-49cb-b8fa-e86367b1a8dc <pod> --tail=500 --timestamps
```
- Если `process_stream: disconnect on progress` — клиент/шлюз оборвал.
- Если worker дошёл до `complete`, а клиент не получил — рвёт шлюз/браузер.
- Если `worker: exception` — ошибка обработки.
+105
View File
@@ -0,0 +1,105 @@
# 2026-08-20 — Вопрос-ответ по LLM-чанкингу (Q&A)
Контекст: сервис обфускации, двухступенчатый детект ПДн (regex + LLM). Пользователь
хочет, чтобы LLM обрабатывал ВСЕ данные, а не урезку 8000 символов.
Обсуждение с Sonnet: основной вопрос + 6 уточнений.
Предыдущие файлы:
- History/2026-08-20-sonnet-query-llm-pattern.md — рекомендуемая схема
- History/2026-08-20-sonnet-query-llm-followup.md — уточнения перед внедрением
---
## Исходная проблема
`scan_llm_ner` (drhider/scanner.py) обрезал:
- каждый файл до первых 3000 символов (`t[:3000]`);
- всё вместе до 8000 символов (`combined[:8000]`);
- один вызов LLM на весь набор.
Итог: при 299 файлах LLM «видел» только ~8000 символов (~0.1–0.3% данных).
Пользователь: «всё затевалось чтобы ЛЛМ ВСЁ обрабатывал». → убрать урезку.
## Рекомендуемая схема (Sonnet, основной ответ)
- Отказаться от глобального батчинга → «один файл = один или несколько вызовов LLM», параллельно.
- Чанкинг per-file: чанк 6000 символов, overlap 400–500 символов.
- Дедупликация по файлу + верификация (найденная строка реально в тексте → отсекает галлюцинации).
- Батчинг мелких файлов — опционально, второй шаг.
## Уточнения (У.1–У.6) и ответы Sonnet
### У.1 — Алгоритм разбивки на чанки
Приоритет границ: `\n\n` → `\n` → `. ` → `? ` → `! ` → `; ` → `, ` → ` ` (пробел — крайний).
Ищем ближайшую границу с конца в диапазоне `[size//2 .. size]`. Жёсткий разрез — только если нет ни одного пробела.
Overlap: отступить назад на overlap, найти начало слова.
Псевдокод:
```python
BOUNDARIES = ['\n\n', '\n', '. ', '? ', '! ', '; ', ', ', ' ']
def split_into_chunks(text, size=6000, overlap=500):
chunks=[]; start=0
while start < len(text):
end=min(start+size, len(text))
if end==len(text):
chunks.append(text[start:]); break
cut=None
for b in BOUNDARIES:
pos=text.rfind(b, start+size//2, end)
if pos!=-1:
cut=pos+len(b); break
if cut is None: cut=end
chunks.append(text[start:cut])
ov=max(start, cut-overlap)
space=text.find(' ', ov)
start=(space+1) if (space!=-1 and space<cut) else ov
return chunks
```
### У.2 — Нормализация для дедупа
```python
def normalize_entity(s):
s=s.strip()
s=re.sub(r'\s+',' ',s)
s=s.lower()
s=re.sub(r'[«»“”‘’"\' ]','"',s) # кавычки → "
s=re.sub(r'[—–−-]','-',s) # тире → -
s=s.replace('\u00ad','') # мягкий перенос
s=s.replace('ё','е') # Ё→Е (OCR/PDF)
return s
```
Дедуп: `seen=set()` — добавлять, только если `normalize_entity(v) not in seen`.
### У.3 — Источник истины для верификации
Полный текст файла ДО обфускации (не чанк).
```python
def verify(entity, full_text):
if entity in full_text: return True
return normalize_entity(entity) in normalize_entity(full_text)
```
Если сущность из двух чанков с разным форматированием — нормализованный ключ одинаков → дедуп оставит один; хранить длиннее (или первый).
### У.4 — Батчинг мелких в первой итерации
НЕ нужен. 270 мелких: 1 файл=1 вызов ≈ 270 вызовов ≈ 101с при 4 потоках; батч по 5 → 54 вызова ≈ 20с. Разница ~80с несущественна. Батчинг — вторая итерация.
### У.5 — Промпт при чанкинге
Блок «Already captured by regex» ОСТАВИТЬ в каждом вызове (снижает дублирование с regex).
Блок правил — как есть в первую итерацию (если API с system/user — правила в system, текст чанка в user; иначе как сейчас).
Первый шаг: промпт не менять, только текстовый блок = чанк.
### У.6 — Общее время и rate-limit
Расчёт: 270 мелких (1 вызов) + 30 крупных (≈4 чанка) = 390 вызовов.
- 4 потока × 2с: 390/4×2 ≈ 195с ≈ 3.5 мин.
- 8 потоков: ~1.5–2 мин.
Rate-limit свой LLM: внешнего нет, ограничение GPU. Начать с 4, поднять до 8, если latency не растёт (>3× от базового — снижать).
---
## Итоговое решение (для внедрения, ждёт «делай»)
1. `scanner.py scan_llm_ner`: убрать `[:3000]`/`[:8000]`;
по каждому файлу из `all_texts` разбить на чанки (6000/500, split_into_chunks);
параллельные вызовы (ThreadPool, concurrency 4, крупные вперёд);
для каждого чанка — полный промпт + «regex-найденные»;
собрать сущности, normalize-дедуп, verify против полного текста файла, в mapping.
2. Добавить хелперы split_into_chunks / normalize_entity / verify в scanner.py.
3. Не дублировать regex-найденное; считать токены/время LLM корректно (llm_active/elapsed).
4. Прогнать тест на малом наборе (не прод): сколько сущностей добавит LLM, время.
Статус: НЕ внедрено (ждёт «делай»). Версия при внедрении — v0.0.57.
@@ -0,0 +1,42 @@
# Промпт для Sonnet — честный ответ: можно ли ускорить без риска устойчивости?
## Роль
Отвечай ЧЕСТНО и ПО ДЕЛУ. Без воды, без лирики. Если ускорение невозможно без
компромисса (устойчивость/таблицы/риск) — прямо скажи «нет» и объясни почему.
Не предлагай «смену библиотеки» — уже пробовали, PyMuPDF теряет таблицы (40 vs 94),
ТАБЛИЦЫ КРИТИЧНЫ для пользователя. Учитывай это ограничение железно.
## ФАКТЫ (результаты наших замеров, НЕ догадки)
1. `Spartan10Manual.pdf` (14.8 МБ, 619 страниц, скан/руководство):
- `extract_text()` суммарно = **64.4с** (104мс/стр) — УЗКОЕ МЕСТО
- `extract_tables()` суммарно = **0.2с** (0мс/стр) — ничтожно
- на 272 страницах без линий extract_tables = 0.0с
→ Твоё прошлое предположение «на сканах дорогой extract_tables» НЕ подтвердилось.
Узкое место — ИЗВЛЕЧЕНИЕ ТЕКСТА, а не таблиц. Не повторяй эту ошибку.
2. Большой PDF 19 МБ (0144-03-2023_отчет об оценке.pdf, 141 стр, 94 таблицы) — ~25-26с.
3. CPU пода = 2 ядра, Memory = 4Gi. Воркер один, последовательный.
4. Разброс времени одного файла между запусками: 70с → 470с (Spartan10). Причины
не установлены точно (подозрение: CPU throttling/нагрузка пода, декомпрессия битмапов).
5. `.doc` обрабатывается через HTTP-сервис liberta (IO-bound).
6. apply_replacements — один regex из всех ключей (уже оптимизирован).
7. Сессия хранит файлы в памяти до 500 МБ (session.py). ProcessPool с fork —
риск COW-копии памяти.
## Вопрос (ответь честно)
Можно ли ЗНАЧИТЕЛЬНО ускорить обработку (цель — сократить время на больших PDF/наборах),
НЕ рискуя:
- устойчивостью (битые файлы, SSE, память пода 4Gi),
- потерей таблиц (критично),
- сложностью поддержки (код должен остаться понятным)?
Требования к ответу:
- Дай КОНКРЕТНЫЕ пункты, каждый: что менять / где / ожидаемый эффект (с цифрами из фактов выше) / риски.
- Раздели на:
A) безопасно и просто (готов внедрить сейчас, низкий риск),
B) заметный выигрыш, но с рисками/сложностью (нужен осознанный выбор),
C) рискованно/не стоит (объясни почему).
- Если для БЕЗОПАСНОГО варианта реального выигрыша нет — так и скажи: «безопасно ускорить
значительно нельзя», и предложи что реально можно сделать без риска (даже если эффект мал).
- НЕ предлагай смену pdfplumber/PyMuPDF и не предлагай то, что ломает таблицы.
- Оцени РЕАЛЬНО: при CPU=2 параллельность текста по страницам даст хоть что-то?
(GIL/процессы, overhead fork/spawn, память). С цифрами.
@@ -0,0 +1,46 @@
# Промпт для Sonnet — уточнения по LLM-чанкингу (перед внедрением)
Ты дал рекомендуемую схему (чанкинг 6000 chars, overlap 500, параллельность 4–8,
дедуп + верификация). Перед внедрением уточни практические детали. Отвечай КРАТКО,
по пунктам, с конкретикой (алгоритм/числа). Без воды.
## У.1 — Разбивка на чанки: по чему резать
Ты сказал «разбивать по абзацам/предложениям, не по символам». Но в PDF-выгрузках
текст часто сливается в один-два гигантских «абзаца» (нет \n). Дай КОНКРЕТНЫЙ алгоритм
разбивки произвольного текста (в т.ч. без \n) на чанки ~6000 символов с overlap 500:
- по каким границам резать в приоритете (\n\n, \n, '. ', '? ', '; ', потом символы)?
- как применить overlap без разрыва слова?
- псевдокод функции split_into_chunks(text, size=6000, overlap=500).
## У.2 — Дедупликация при overlap и некорректном форматировании
LLM может вернуть один и тот же value из двух чанков с РАЗНЫМ форматированием
(лишние пробелы, регистр, тире/дефис). Проверки `strip().lower()` недостаточно.
Как нормализовать перед дедупом (например, сжать пробелы, унифицировать кавычки/тире)?
Дай конкретный набор нормализаций для русских деловых текстов.
## У.3 — Верификация против исходника
Проверку «найденного значения нет в исходном тексте → отбросить (галлюцинация)» делать
против ИСХОДНОГО извлечённого текста файла (до обфускации), верно? Или против чанка?
Уточни, что есть «источник истины» для верификации, и как быть, если value найден в
нескольких чанках, но слегка отличается (нормализованное сравнение).
## У.4 — Малые файлы: нужен ли батчинг в первую итерацию
Набор сотен мелких файлов (<1000 символов). Батчинг 5–8 в один вызов ускорит, но усложнит
(деградация разметки, больший ответ). Для ПЕРВОЙ итерации можно ли обойтись «1 файл = 1 вызов»
без батчинга, даже если файлов сотни? Оцени, насколько медленнее будет без батчинга.
## У.5 — Промпт при пофайловом чанкинге
Текущий промпт (в коде) содержит блоки «Already captured by regex» + большой список правил.
При пофайловом/почанковом вызове эти блоки повторяются в каждом запросе. Оставить промпт
как есть (меняя только текст), или его надо упростить под чанк (одна папка уже нашла — regex)?
Повлияет ли повтор правил на качество/токены?
## У.6 — Прогресс и общее время
300 файлов, многие >6000 символов → сотни LLM-вызовов × по 1–2с = минуты-десятки минут.
Пользователь готов ждать (LLM своя). Но оцени: с параллельностью 4 и ~5 вызовов/файл на
крупных — реальное общее время для набора ~300 файлов (в т.ч. 270 мелких + 30 крупных).
И: не упрёмся ли в rate-limit своего LLM-эндпоинта при 4-8 параллельных.
## Формат
- По каждому У.1–У.6: короткий ответ + конкретика (алгоритм/числа/решение).
- Если что-то некритично для первой итерации — скажи явно «можно первым шагом упростить так».
@@ -0,0 +1,30 @@
# Промпт для Sonnet — вопрос по использованию LLM (без ссылок на файлы)
## Контекст
У нас сервис обфускации документов (удаление персональных данных из русских деловых документов).
Двухступенчатый детект ПДн:
1. Regex-паттерны (телефоны, ИНН, email, типовые формы — быстро, локально, дёшево).
2. LLM (общая NER-подстраховка) — сейчас обрабатывает только урезанный фрагмент:
- от каждого файла берутся первые ~3000 символов,
- всё вместе обрезается до ~8000 символов,
- выполняется ОДИН вызов LLM на весь набор.
Пользователь хочет, чтобы LLM обрабатывал ВСЕ данные (ВСЕ файлы целиком), не только первые 8000 символов.
LLM — своя, стоимость не важна. Важно качество распознавания ПДн и разумная архитектура вызова.
## Вопрос
Как лучше организовать вызов LLM, чтобы он БЕЗ потерь покрывал все документы (набор из сотен файлов, каждый до нескольких десятков страниц), сохраняя качество распознавания русских деловых документов?
Ограничения/требования к ответу:
- Не предлагай менять модель/external API — LLM своя, она остаётся.
- Подумай про: пакетирование файлов (размер батча), максимальную длину контекста на запрос,
как избежать обрезки данных, как не потерять качество при больших объёмах,
параллельность запросов (если релевантна), дедупликацию найденного между пакетами.
- Дай конкретную рекомендуемую схему (числа: размер батча, лимит символов на файл/пакет, параллельность).
- Оцени риски: токены/время, качество, риск пропуска.
- Сухо, по делу, без воды.
## Формат
- Секция «Рекомендуемая схема» — конкретный план с числами.
- Секция «Риски» — что может пойти не так и как смягчить.
- Секция «Минимум для старта» — если хочется просто и быстро, что достаточно сделать первым шагом.
@@ -0,0 +1,53 @@
# Промпт для Sonnet — follow-up вопросы по ревью (скорость + сбои)
## Контекст
Ты дал ревью (файл: History/2026-08-20-sonnet-query-review-speed.md). Часть пунктов приняли,
часть — требуют уточнения. Отвечай ТОЛЬКО на вопросы ниже, сухо, конкретно, без воды.
Если что-то из твоих утверждений основано на допущении, а не на факте — скажи явно «допущение» и
что нужно проверить, чтобы подтвердить.
## Вопрос 1. Фильтр `page.lines or page.curves` перед `extract_tables()` (extractor.py)
Ты предложил: не вызывать extract_tables() на страницах без vector-линий, т.к. на сканах это пустой проход.
Вопросы:
1.1. Перечисли КОНКРЕТНО типы таблиц, которые `extract_tables()` находит, но которые НЕ дают
`page.lines`/`page.curves` (текстовые сетки, таблицы на заливке/fill, встроенные картинки, что ещё?).
1.2. Есть ли в нашем реальном наборе (TMP/спецификации, 0144-03-2023_отчет об оценке.pdf, документы из
DownLoads) риск, что фильтр отбросит реальную таблицу? Это надо проверить фактом — предложи
точный способ замера: как сравнить число таблиц с фильтром и без на конкретных файлах.
1.3. Насколько `page.lines`/`page.curves` дешевле `extract_tables()`? В pdfplumber они тоже делают
парсинг объектов страницы — дай оценку реального выигрыша на скан-PDF, не «в разы», а чем измерить.
1.4. Безопасная альтернатива: может, стоит вызывать extract_tables() только если `page.find_tables()`
вернул непусто? Или это то же самое по стоимости? Уточни, что реально дорого внутри extract_tables().
## Вопрос 2. `_decode_name`: попытка decode("utf-8") перед decode("cp866")
Ты предложил порядок: raw.decode("utf-8"), при ошибке — raw.decode("cp866").
2.1. Оцени риск ложного срабатывания: когда CP866-байты случайно образуют валидный UTF-8
(например, псевдографика 0xC0-0xDF + продолжения 0x80-0xBF). Насколько это реально для имён
файлов 1С? Есть ли способ отличить «настоящий UTF-8» от «случайного» (например, проверить
диапазон символов после декодирования)?
2.2. Подтверди, что для нашего реального случая (Info-ZIP UTF-8 без флага 0x800) этот порядок даёт
корректное имя, а для CP866 1С — не ломает.
## Вопрос 3. ProcessPoolExecutor по страницам / по файлам
Ты предложил распараллелить извлечение текста. Вопросы:
3.1. Риск памяти: в контейнере сессия уже держит файлы в памяти (до 500 МБ в session.py). При fork
воркеры получают COW-копию памяти родителя. Оцени реальный риск OOM в managed-поде (лимит CPU 2,
Memory 4Gi) при 4-8 процессах. Не будет ли хуже, чем текущее последовательное?
3.2. pdfplumber сам по себе уже использует один процесс. Дай конкретный план безопасного
распараллеливания: какие данные передавать в воркер (только bytes страницы или весь файл?),
как собирать результаты в порядке индексов, как не раздуть память.
3.3. Учитывая CPU 2 (2 ядра) — какой реальный выигрыш даст ProcessPool на 2 ядрах? Стоит ли это
сложности, или сначала дёшево (П.1 + П.2)?
## Вопрос 4. Что реально тормозит в Spartan10Manual.pdf (70–470с на 14.8 МБ)
Ты утверждаешь: виноват пустой extract_tables() на скане. Но время скачет 70→470с — это подозрительно.
4.1. Как точно измерить, что дороже на ЭТОМ файле: extract_text() по страницам или extract_tables()?
Дай конкретный способ замера (тайминги по функциям, по страницам), чтобы не гадать.
4.2. Объясни разброс 70–470с: что в коде/данных может давать такой разброс между запусками
(кэши, LLM, сеть к liberta, нагрузка CPU пода)?
4.3. Есть ли в extract_text() внутри pdfplumber скрытый повторный парсинг (например, повторное чтение
объекта при каждом вызове), который можно убрать без смены библиотеки?
## Формат ответа
- По каждому вопросу: короткий ответ + «факт»/«допущение» + что проверить, если допущение.
- Без воды, без лирики.
@@ -0,0 +1,44 @@
# Промпт для Sonnet — код-ревью drhider (скорость + сбои)
## Роль
Ты — ревьюер кода. Отвечай ТОЛЬКО по делу: короткие технические тезисы. Без воды, без лирики, без «как было бы здорово», без общих фраз. Каждый тезис — конкретика: файл, функция, строка, суть.
## Ограничение: смотреть ТОЛЬКО эти файлы, нигде больше не рыться
- `drhider/obfuscator.py` — двухпроходный обфускатор (проход 1: извлечение текста + regex + LLM NER; проход 2: замена)
- `drhider/extractor.py` — pdf_to_markdown (pdfplumber), doc_to_markdown (сервис liberta), expand_zips (рекурсия zip, CP437→CP866), защита от zip-бомб
- `drhider/scanner.py` — scan_regex, scan_llm_ner
- `drhider/replacer.py` — apply_replacements (один regex из всех ключей)
- `drhider/llm_client.py` — OpenAI-совместимый клиент, счётчики токенов/времени
- `drhider/builder.py` — build_zip, build_mapping_csv
- `site/routes/api_bp.py` — upload, process_stream (SSE + воркер в потоке), download/csv
- `site/app.py` — create_app, лимиты, setup_logging (LOG_LEVEL/LOG_FILE)
- `site/session.py` — хранение сессий в памяти, TTL 30 мин, MAX_SESSION_BYTES
- `site/templates/index.html` — фронт: нативный unzip, таблица, SSE-прогресс, таймеры, лимиты 100МБ/1ГБ
Не читай README, docs, History, tests, Dockerfile, ничего про деплой.
## Контекст: сбои, которые были (объясни каждый, если увидишь первопричину в коде)
1. «SSE connection failed» при живом воркере. Выяснено: воркер падал на битом PDF — `PDFSyntaxError('No /Root object!')` в `extract_text` не был обёрнут по-файлово; после фикса (skip битого файла) SSE доходит до complete. Подтверди/опровергни по коду, есть ли ещё места, где одна ошибка файла роняет весь проход.
2. Кракозябры кириллицы в именах zip. Причина: Info-ZIP пишет UTF-8 без флага 0x800 → декодер `encode(cp437).decode(cp866)` ломает. Проверь `_decode_name` в extractor.py: покрывает ли оба реальных сценария (CP866 без флага и UTF-8 с флагом), есть ли дыры.
3. HTTP 413 при загрузке. Лимиты: MAX_CONTENT_LENGTH=200MB (запрос), MAX_SESSION_BYTES=500MB (сессия). Фронт пускает до 100МБ/файл и 1ГБ/сумма — рассинхрон фронт/бэк. Отметь.
## Главная задача: предложи БОЛЕЕ БЫСТРУЮ логику обработки
Известные факты производительности (факты, не догадки):
- pdfplumber на скане Spartan10Manual.pdf 14.8 МБ — 70–470 секунд на извлечение текста.
- Большой PDF 19 МБ — ~25–26 с на extract_text.
- LLM NER — один общий вызов на все файлы, ~1.8с на маленьком наборе, зависит от числа токенов.
- apply_replacements оптимизирован (один regex), но проверить, нет ли лишней работы.
Что хочешь от тебя:
- Где реальные узкие места в коде (не «вообще», а конкретные функции/строки).
- Конкретные предложения ускорения: что менять, как, ожидаемый эффект. Без «использовать PyMuPDF» — уже пробовали, теряет таблицы (94 vs 40), ТАБЛИЦЫ КРИТИЧНЫ. Учитывай это ограничение.
- Можно ли ускорить без смены pdfplumber (параллельность страниц, кэши, ограничение повторных парсингов, предобработка)?
- Есть ли дублирующая работа между проходами 1 и 2 (например, повторный парсинг/чтение)?
- Безопасно ли распараллелить извлечение текста по файлам (потоки/GIL)? Если да — как.
## Формат ответа
- Секция «Сбои»: по каждому — подтверждение/опровержение по коду + где именно.
- Секция «Узкие места»: список файл:строка + суть + почему.
- Секция «Предложения ускорения»: пронумерованный список, каждый пункт: что/где/как/эффект/риски.
- Секция «Итог»: 3–5 самых важных действий по приоритету.
- Максимум — сухо, без воды.
+127
View File
@@ -0,0 +1,127 @@
# 2026-08-20 — Ревью Sonnet: вопросы и ответы (Q&A)
Серия: ревью скорости обработки + объяснение сбоев.
Предыдущие файлы:
- History/2026-08-20-sonnet-query-review-speed.md — исходный промпт ревью
- History/2026-08-20-sonnet-query-review-speed-followup.md — вопросы по спорным пунктам
- Настоящий файл — ответы Sonnet + решения
---
## В1. Фильтр `page.lines or page.curves` перед `extract_tables()` (extractor.py)
### В1.1 — Какие таблицы extract_tables() найдёт, но фильтр отбросит?
**Sonnet: допущение (не факт).**
`extract_tables()` со стратегией по умолчанию ищет только векторные линии (strategy="lines").
- Таблицы на цветном фоне (только `page.rects`, без линий) — фильтр `lines or curves` их пропустит → **дыра**.
Нужно `or page.rects`.
- Whitespace-таблицы (только пробелы/выравнивание) — не найдёт вообще без strategy="text".
- Растровые таблицы в embedded images — не найдёт вообще.
**Решение:** фильтр = `page.lines or page.curves or page.rects`. Обязательно проверить фактом (В1.2).
### В1.2 — Как проверить, что фильтр не потеряет таблицы?
**Sonnet:**
```python
with pdfplumber.open(fname) as pdf:
for i, page in enumerate(pdf.pages):
t_all = page.extract_tables()
has_lines = bool(page.lines or page.curves or page.rects)
t_filtered = page.extract_tables() if has_lines else []
if len(t_all) != len(t_filtered):
print(f"p{i}: потеряно {len(t_all)-len(t_filtered)} таблиц")
```
Прогнать на TMP-спецификациях и 0144-03-2023_отчет об оценке.pdf.
**Решение:** сделать замер до включения фильтра в код. ТАБЛИЦЫ КРИТИЧНЫ (прецедент PyMuPDF 40 vs 94).
### В1.3 — Насколько page.lines/curves дешевле extract_tables()?
**Sonnet: допущение.**
`page.lines` — `@cached_property` (уже разобран при первом обращении к странице). Стоимость ≈ 0.
Дорогой в `extract_tables()` — `TableFinder`: строит граф пересечений линий. На скан-PDF всё равно инициализируется.
Замер:
```python
t0 = time.perf_counter(); _ = page.lines; t1 = time.perf_counter()
t2 = time.perf_counter(); _ = page.extract_tables(); t3 = time.perf_counter()
print(f"lines={t1-t0:.4f}s extract_tables={t3-t2:.4f}s")
```
### В1.4 — find_tables() как guard?
**Sonnet:** `find_tables()` и `extract_tables()` — одна стоимость (`extract_tables()` вызывает `finder.find_tables()` внутри). Guard бесполезен.
---
## В2. `_decode_name`: попытка decode("utf-8") перед decode("cp866")
### В2.1 — Риск ложного срабатывания UTF-8 на CP866-байтах?
**Sonnet:**
CP866 кириллические заглавные = 0xC0–0xDF. В UTF-8 0xC0, 0xC1 — overlong (невалидны), 0xC2–0xDF — валидное начало двухбайта, требует продолжения 0x80–0xBF. CP866 строчные = 0xA0–0xBF — попадают в диапазон UTF-8 continuation bytes.
**Реальная коллизия:** «Т» (0xD2) + «г» (0xA3) = 0xD2 0xA3 = валидный UTF-8 (U+04A3 Ң). Риск для имён «Тг…», «Рп…» и т.п.
**Защита:** после `decode("utf-8")` проверить, что все символы — в U+0400–U+04FF (кириллица) или ASCII. Если есть символы вне диапазона — ложное срабатывание → фоллбэк cp866.
### В2.2 — Подтверждение порядка
**Sonnet: факт.**
- Info-ZIP UTF-8 без флага: zipfile(cp437) → `encode("cp437")` → исходные UTF-8 байты → `decode("utf-8")` → корректно.
- CP866 1С: zipfile(cp437) → `encode("cp437")` → CP866 байты → `decode("utf-8")` → почти всегда UnicodeDecodeError (0xC0/0xC1 overlong, 0xE0–0xFF без продолжения) → фоллбэк `decode("cp866")` → корректно.
**Решение:** фикс принимается, но с валидацией диапазона кириллицы/ASCII после utf-8.
---
## В3. ProcessPoolExecutor по страницам/файлам
### В3.1 — Риск памяти при fork в managed-поде
**Sonnet: допущение.**
fork → COW. pdfplumber в дочернем создаёт новые объекты (запись → COW). 500МБ session files в родителе — COW, читаются, не копируются. Пик: pdfplumber на 14.8МБ PDF ~150–300МБ на воркер. 2 воркера + родитель ≈ 1–1.5ГБ пик. При 4Gi — умеренный риск.
`spawn` (не fork): дочерние стартуют чисто, память сессии не копируется, overhead ~0.5с/процесс.
### В3.2 — Безопасный план распараллеливания
**Sonnet:**
- Единица параллелизма: **файл** (не страница), воркер получает `(fname, content_bytes)` через pickle.
- content_bytes по pipe: 15МБ ≈ 0.1с overhead.
- Порядок: `{i: executor.submit(pdf_to_markdown, content)}`, collect `{i: future.result()}`.
- progress_cb из основного потока после future.result().
### В3.3 — Выигрыш на CPU=2
**Sonnet:** на 2 ядрах при ОДНОМ PDF — нет выигрыша (один процесс на одно ядро). Выигрыш только при нескольких PDF: 3 файла × 25с → ~30с вместо 75с.
**Рекомендация Sonnet:** сначала В1 (фильтр) + В2 (decode) — бесплатно и безопасно. ProcessPool — потом, если замер покажет, что узкое место реально там.
**Решение:** ОТЛОЖЕНО. Сначала В1+В2, замерить.
---
## В4. Разброс 70–470с на Spartan10Manual.pdf (14.8 МБ)
### В4.1 — Как измерить, что дороже: extract_text или extract_tables
**Sonnet:**
```python
with pdfplumber.open(io.BytesIO(content)) as pdf:
for i, page in enumerate(pdf.pages):
t0 = time.perf_counter()
page.extract_text()
t1 = time.perf_counter()
page.extract_tables()
t2 = time.perf_counter()
print(f"p{i:3d}: text={t1-t0:.3f}s tables={t2-t1:.3f}s")
```
### В4.2 — Причина разброса
**Sonnet:**
- Факт: pdfplumber не кэширует между запросами — каждый вызов парсит заново.
- Допущение: CPU throttling при limit=2 в managed-поде (нагрузка → 470с, пусто → 70с). Проверить `kubectl top pod` во время обработки.
- Допущение: битмапы скана разного размера (цветной vs ч/б) → разное время декомпрессии (JPEG2000/JBIG2). Проверить замером по страницам.
### В4.3 — Скрытый повторный парсинг в extract_text?
**Sonnet: факт.** `extract_text()` читает `self.chars` (@cached_property), `extract_tables()` — `self.edges` (@cached_property). Оба используют уже разобранные структуры. Повторного чтения PDF в рамках одного `with pdfplumber.open()` нет.
---
## Итоговый план (по приоритету)
1. **В2** — `_decode_name`: utf-8 + валидация диапазона (кириллица U+0400–U+04FF/ASCII) → фоллбэк cp866. Фикс Info-ZIP-зипов (кракозябры).
2. **В1** — фильтр `page.lines or page.curves or page.rects` перед `extract_tables()` + замер числа таблиц до/после на реальных файлах.
3. Замер ускорения на Spartan10Manual.pdf.
4. ProcessPool — после фактов, если узкое место там (отложено).
5. Диагностика разброса 70–470с: `kubectl top pod` во время обработки + замер по страницам.
## Статус реализации
- Пока НЕ реализовано (ждёт «делай»). Код не менялся в рамках этого Q&A.
+36
View File
@@ -0,0 +1,36 @@
# 2026-08-20 — Лимиты загрузки + предупреждение в таблице (v0.0.50)
## Проблема
Юзер грузил всё подряд (сотни МБ, включая >200МБ) → HTTP 413 / SSE-обрыв.
Требование: понятная политика лимитов, но файлы сверх лимита НЕ молча скрывать —
показывать в таблице, чтобы юзер не повторял загрузку в недоумении.
## Решение (фронт, index.html)
Лимиты:
- 1 файл > 100 МБ → «не учитывается»
- сумма учитываемых > 1 ГБ → следующий файл тоже «не учитывается»
Логика:
- `MAX_FILE_BYTES = 100МБ`, `MAX_SESSION_BYTES = 1ГБ` (константы).
- `addFileWithDedup`: файл сверх лимита добавляется в `sf` (виден в таблице),
но имя кладётся в `overNames` (Set). Учитываемость считаем по не-overNames.
- `rr()`: строки с `overNames` получают класс `row-over` (красный фон/текст),
статус «🔥 не учитывается»; счётчик: «N учитываются + M свыше лимита · СУММА».
- `uploadFiles()`: строит `toSend` ТОЛЬКО из учитываемых, `sendIdx` маппит
idx-отправленного → idx в `sf` (прогресс/таймеры/start/done пишут в верную строку).
Если все переборные → «Нет файлов для обфускации (все превышают лимит)».
- Шапка: добавлена строка «Ограничения: файл не более 100 МБ, суммарно не более
1 ГБ. Файлы сверх лимита помечаются красным и не участвуют в обфускации.»
- CSS: `.row-over td { background: rgba(220,38,38,.10) !important; color:#c0392b; }`.
- `rm`/`resetAll` — чистят `overNames`.
Бэк НЕ менялся (MAX_CONTENT_LENGTH=200МБ, MAX_SESSION_BYTES=500МБ остались как защита
на сервере). Внимание: бэк-лимит 200МБ/запрос и 500МБ/сессия всё ещё ниже фронтовых
1ГБ — для файлов 100МБ-200МБ фронт пустит, но одиночный запрос >200МБ упадёт 413.
Это можно согласовать позже, если нужно.
## Проверка
- node --check секции <script> index.html — OK.
- py_compile app.py — OK.
- Лог-тест лимитов через node: 120МБ→перебор, 13 учитываемых ~995МБ, >1ГБ→перебор — верно.
- VERSION поднята до 0.0.50.
+37
View File
@@ -0,0 +1,37 @@
# 2026-08-20 — В1+В2 из ревью Sonnet (v0.0.54)
## В2 — фикс кракозябр имён zip (extractor.py `_decode_name`)
Добавлена попытка decode("utf-8") ПЕРЕД decode("cp866"), с валидацией диапазона:
- результат UTF-8 принимается, только если все символы — ASCII или кириллица (U+0400–U+04FF)
(отсекает случайную коллизию CP866→UTF-8, напр. «Т»+«г» = U+04A3);
- при ошибке или выходе из диапазона — фоллбэк decode("cp866") (реальные 1С).
Проверено:
- CP866-зип (zip_subfolders_cp866.zip) → имена корректны (1_Металлургия.pdf…)
- UTF-8 с флагом (zip_subfolders_utf8.zip) → корректны
- test_zip 28/28 OK
## В1 — фильтр перед extract_tables() (extractor.py `pdf_to_markdown`)
Добавлен: `if page.lines or page.curves or page.rects:` перед `extract_tables()`.
Задумывалось как ускорение сканов (extract_tables на страницах без линий впустую).
### ⚠️ ФАКТ замера (опровергает гипотезу Sonnet)
На Spartan10Manual.pdf (14.8 МБ, 619 стр):
- extract_text суммарно 64.4с (104мс/стр) — УЗКОЕ МЕСТО
- extract_tables суммарно 0.2с (0мс/стр) — ничтожно
- на 272 страницах без линий extract_tables суммарно 0.0с — почти мгновенно
ВЫВОД: фильтр В1 НЕ даёт ускорения (extract_tables и так дешёвый). Реальное узкое
место — extract_text (pdfminer). Фильтр оставлен как безопасная защита (таблицы
не теряет: на 0144-03-2023_отчет об оценке.pdf 94=94 таблицы, потеряно страниц 0),
но эффект ускорения ≈ 0.
### Реальный путь ускорения (отложено)
Узкое место — extract_text (104мс/стр × 619 стр = 64с). Ускорение только через:
- распараллеливание extract_text по страницам/файлам (ProcessPool) — отложено,
требует замера рисков (fork/память, CPU=2);
- или смену извлечения текста без потери таблиц (риск: таблицы критичны).
## Тесты
Все 106 тестов OK (test_zip 28, test_extractor 15, test_builder 20, test_replacer 10,
test_scanner 20, test_upload 13). VERSION 0.0.54.
@@ -0,0 +1,124 @@
# ПЛАН: перенос паттерна «ВМ-буфер + pull» в drhider
_Дата: 2026-08-21. Основа: `contracts/loadtest/History/2026-08-21-pattern-vm-buffer-pull.md`._
_Статус: РЕАЛИЗОВАНО 2026-08-21 (v0.0.58), см. `2026-08-21-vm-upload-implemented.md`._
---
## Цель
drhider (Managed Flask, Штурвал, `drhider.pythonk8s.dev.nubes.ru`) получает файлы через
`POST /api/upload` (multipart). Входной шлюз managed-кластера обрывает тела >~64КБ
(HTTP 000 ~10с). Решение — загружать файл на ВМ напрямую, Flask тянет сам (egress).
## Директива (обязательно)
**ВСЕ файловые операции загрузки в Flask (из браузера или вообще снаружи) — ТОЛЬКО через ВМ.**
Никаких прямых `POST /api/upload` с телом файла во Flask.
## Среда (уточнено 2026-08-21)
- `iot-naeel` — СОБСТВЕННЫЙ кластер, параметры/аннотации правились через kubectl. Пока
тестируем/смотрим поведение на нём (лимит 64КБ там может не воспроизводиться).
- В дальнейшем деплой — на ОБЫЧНЫЙ managed-кластер (лимит 64КБ будет реальным).
- ВМ — ОДНА (`5.172.178.213`, `contracts.kube5s.ru`), та же, что для loadtest. Location
`/drhider-upload/` добавляется в существующий nginx рядом с `/lt-serve/`.
- Вывод: VM-путь строим СЕЙЧАС, независимо от поведения iot-naeel — прод всё равно на обычном.
## Текущий поток
`uploadFiles()` (index.html ~413): по одному файлу в цикле → `FormData` → `POST /api/upload`.
Бэк: `MAX_CONTENT_LENGTH=200МБ`, сессия 500МБ. HTTP-клиент — **httpx** (не requests).
## Целевой поток
```
Браузер ──(PUT, файл целиком)──▶ ВМ nginx (без лимита 64КБ)
│ └─ получил URL + токен
│
└──▶ Flask POST /api/upload_refs (JSON: [{name,size,url}], <64КБ)
│
▼
Flask egress GET с ВМ ──▶ читает файл ──▶ add_file() ──▶ дальше как сейчас
```
## Изменения (4 места)
### 1. ВМ `5.172.178.213`, `nginx-contracts.conf` — приём загрузки
Новый location (рядом с `/lt-serve/`), приём через PUT + статическая отдача:
```nginx
location /drhider-upload/ {
alias /var/www/drhider-upload/;
dav_methods PUT;
create_full_put_path on;
client_max_body_size 1024m;
# CORS: браузер с drhider.pythonk8s.dev.nubes.ru шлёт PUT (не simple → preflight)
add_header Access-Control-Allow-Origin https://drhider.pythonk8s.dev.nubes.ru always;
add_header Access-Control-Allow-Methods 'PUT, GET, OPTIONS, DELETE' always;
add_header Access-Control-Allow-Headers 'Content-Type' always;
if ($request_method = OPTIONS) { return 204; }
}
```
- Каталог `/var/www/drhider-upload/` (владелец `www-data`), НЕ `/home/naeel` (403).
- Порядок: backup → правка → `nginx -t` → `systemctl reload nginx`.
### 2. Flask — новый endpoint pull (`site/routes/api_bp.py`)
```python
@api_bp.route("/upload_refs", methods=["POST"])
def upload_refs():
data = request.get_json(silent=True) or {}
sid = data.get("session") or create_session()
refs = data.get("files") or []
if not refs:
return jsonify({"ok": False, "error": "No files"}), 400
added = 0
with httpx.Client(timeout=120, follow_redirects=True) as client:
for ref in refs:
name, url = ref.get("name"), ref.get("url")
if not name or not url:
continue
content = b"".join(client.stream("GET", url).iter_bytes()) # egress, по частям
if not add_file(sid, name, content):
return jsonify({"ok": False, "error": "Session not found"}), 404
added += 1
return jsonify({"ok": True, "session": sid, "count": file_count(sid)})
```
- Импорт `httpx` (уже в requirements.txt) и `file_count` из session.
- `stream(...).iter_bytes()` — НЕ `content` целиком (OOM при 100МБ+).
- Обработка ошибок egress (таймаут/403/404) → вернуть 502 с именем файла.
### 3. Фронт (`site/templates/index.html`, `uploadFiles()`)
Разбить «Фазу 1» на два шага:
- **Шаг 1a:** каждый файл → `fetch(<ВМ>/drhider-upload/<token>/<name>, {method:'PUT', body:file})`,
собирать `[{name, size, url}]`. Прогресс — `xhr.upload.onprogress` заменить на `fetch` +
`ReadableStream` (или оставить XHR, но на PUT к ВМ).
- **Шаг 1b:** один `POST /api/upload_refs` с JSON `{session, files:[...]}` (<64КБ).
- Токен: `crypto.randomUUID()`. URL ВМ — константа/`data-атрибут`.
- Показывать прогресс «загрузка на ВМ» отдельно от «загрузка в Flask».
### 4. `requirements.txt` — без изменений (httpx уже есть).
## Безопасность и жизненный цикл
- Токен в пути URL — только `crypto.randomUUID()`, не переиспользуется.
- Flask после `add_file` делает `DELETE` на URL ВМ (или ВМ чистит по TTL — cron/systemd-timer
`find /var/www/drhider-upload -mmin +30 -delete`).
- ВМ отдаёт файл только по валидному URL с токеном (нет открытого листинга: `autoindex off`).
## Грабли (из pattern-дока + drhider)
- CORS: PUT — не simple-метод → браузер шлёт OPTIONS preflight; nginx обязан отвечать 204.
- `site/` конфликтует со stdlib `site.py` (для loadtest; у drhider — Штурвал, без gunicorn).
- egress через `httpx` с `stream`, timeout 120; большие ОТВЕТЫ проходят (проверено 50МБ).
- OOM: не читать файл в память целиком; сессия 500МБ уже ограничивает.
- Локальные curl на Krupski идут через прокси `172.17.192.1:10808` → для теста ВМ `--noproxy '*'`.
## Порядок работ + проверка
1. ВМ nginx (location + каталог + CORS) → проверить `curl --noproxy '*' -X PUT`.
2. Flask `/api/upload_refs` → проверить `python3 -c "import py_compile; py_compile.compile(...)"`.
3. Фронт `uploadFiles()` → `node -c` нет (это EJS/HTML+JS внутри шаблона) — ручная проверка.
4. Сквозной тест: файл 10МБ → ВМ → Flask → обфускация → ZIP.
5. Commit + push (правило: после правки + bump версии `VERSION` в `site/app.py`).
## Решения (закрыты)
- Кластер: сейчас `iot-naeel` (тест), прод — обычный managed. VM-путь обязателен.
- ВМ: одна, `contracts.kube5s.ru` (`5.172.178.213`), общая с loadtest — добавляем location.
+75
View File
@@ -0,0 +1,75 @@
# ПЛАН: универсальный файловый сервис на ВМ (API)
_Дата: 2026-08-21. Статус: РЕАЛИЗОВАНО И ЗАДЕПЛОЕНО (v1.0.0), см. `README.md` сервиса `/home/naeel/nubes/vmfiles/` и `2026-08-21-vm-upload-implemented.md`._
## Цель
Один внутренний (НЕ публичный) сервис на ВМ `5.172.178.213` (`contracts.kube5s.ru`)
для приёма/временного хранения/выдачи файлов. Потребители: **drhider**, **contracts**,
**SQS IoT**. Заменяет сырой nginx-dav `/drhider-upload/` на задокументированный,
версионированный API, чтобы любому сервису было ясно, как им пользоваться.
## Потребители и режимы
- **drhider** — браузер (PUT файла) + сервер (pull). Нужен CORS для браузера.
- **contracts** — сервер-к-серверу, много файлов из локали.
- **SQS IoT** — сервер-к-серверу.
## Технология
**FastAPI + uvicorn** (Python уже есть на ВМ).
Почему FastAPI: автогенерация OpenAPI/Swagger (`/docs`) → «как юзать» видно из коробки;
нативный streaming для больших тел.
## API v1 (контракт)
| Метод | Путь | Вход | Ответ |
|---|---|---|---|
| `POST` | `/api/v1/files` | тело=файл (raw или multipart `file`), header `X-App-Key` | `201 {"id","url","size","expires_at"}` |
| `GET` | `/api/v1/files/{id}` | header `X-App-Key` | байты файла |
| `DELETE` | `/api/v1/files/{id}` | header `X-App-Key` | `204` |
| `GET` | `/api/v1/health` | — | `{"ok":true,"version":...}` |
| `GET` | `/api/v1/docs` | — | Swagger UI (авто) |
Единые ошибки: `{"error": "...", "code": "unauthorized|not_found|too_large|expired|quota_exceeded"}`.
## Авторизация
Заголовок `X-App-Key`, по одному секрету на приложение. Ключи — в конфиге на ВМ
(файл `apps.yml`/`.env`): `drhider`, `contracts`, `sqs-iot`. Без валидного ключа — `401`.
## Хранилище и метаданные
- Файлы: `/var/lib/vmfiles/` (или `/var/www/vmfiles/`, владелец — пользователь сервиса).
- Метаданные: SQLite (`id, app, name, size, created_at, expires_at`).
- id — серверный UUID (или клиентский токен — см. «открытые вопросы»).
## Лимиты и TTL
- `max_file_size` (по умолчанию 1024m) и `quota` на приложение.
- TTL по умолчанию 30 мин (на приложение можно переопределить).
- Чистка — встроенная фоновая задача сервиса (вместо текущего cron-`find`).
## Деплой на ВМ
1. Код сервиса — в отдельной папке на ВМ (или отдельный репозиторий).
2. systemd-юнит `vmfiles.service` → `uvicorn app:app --host 127.0.0.1 --port 8769`.
3. nginx: новый `location /api/v1/ { proxy_pass http://127.0.0.1:8769; ... }`
+ `proxy_request_buffering off` (стримить большие тела) + `client_max_body_size 1024m`.
CORS — отдать сервису (FastAPI), не nginx.
4. Проверка: `nginx -t` → reload.
## Миграция drhider
- Пока сервис поднимается — `/drhider-upload/` оставить как есть (не ломать текущее).
- После готовности — перевести `uploadFiles()` и `/api/upload_refs` на `/api/v1/files`,
убрать dav-location и cron-`find`.
## Порядок работ
1. Скелет FastAPI + health + `/docs`.
2. `POST/GET/DELETE /files` + SQLite-метаданные + TTL-чистка.
3. `X-App-Key` авторизация + квоты.
4. systemd + nginx `proxy_pass`.
5. Тесты: curl (raw + multipart), большие файлы, expiry, quota.
6. Перевести drhider на новый API, выпилить старый dav.
7. Документ-спека (`README`/`API.md`) + примеры для contracts/SQS.
## Открытые вопросы (до «делай»)
1. Язык: FastAPI (рекомендую) или Flask?
2. id файла: сервер генерирует (POST) или клиент приносит токен (PUT)? Влияет на браузерный поток drhider.
3. Загрузка: только raw-тело или поддерживать multipart тоже?
4. Где живёт код сервиса — отдельный репозиторий на gitea или папка на ВМ?
5. Ключи приложений — придумать сейчас или сервис сгенерирует при первом старте?
@@ -0,0 +1,43 @@
# ВМ-загрузка в drhider — реализовано (v0.0.58, 2026-08-21)
_Реализация паттерна «ВМ-буфер + pull» по плану `2026-08-21-pattern-vm-buffer-pull-plan.md`._
_Снимок до изменений: ветка `save-pre-vm-upload-2026-08-21`._
## Что сделано
1. **Flask** (`site/routes/api_bp.py`) — новый `POST /api/upload_refs`:
принимает JSON `{session, files:[{name,size,url}]}`, тянет каждый файл с ВМ
ИСХОДЯЩИМ GET (httpx, stream по частям), кладёт в сессию `add_file()`,
после успешного pull делает DELETE с ВМ. Ошибки egress → 502.
2. **Фронт** (`site/templates/index.html`, `uploadFiles()`) — Фаза 1 переделана:
- 1a: каждый файл → `PUT` на ВМ `https://contracts.kube5s.ru/drhider-upload/<токен>_<k>`
(XHR, прогресс по-прежнему в строке), собирает refs.
- 1b: один маленький `POST /api/upload_refs` (<64КБ).
- Токен — `crypto.randomUUID()`. Константа `VM_UPLOAD_URL`.
3. **ВМ** (`5.172.178.213`) — nginx `nginx-contracts.conf`:
- `location /drhider-upload/` → `alias /var/www/drhider-upload/`,
`dav_methods PUT DELETE`, `create_full_put_path on`, `client_max_body_size 1024m`,
CORS-заголовки для `drhider.pythonk8s.dev.nubes.ru`, OPTIONS → 204.
- Каталог `/var/www/drhider-upload/` (www-data).
- Backup: `/tmp/nginx-contracts.conf.bak.20260821`.
- TTL-чистка в root cron: `*/5 * * * * find /var/www/drhider-upload -type f -mmin +30 -delete`.
4. **Версия** — `site/app.py`: 0.0.57 → 0.0.58.
## Отклонение от плана
- URL файла на ВМ — БЕЗ имени: `.../drhider-upload/<токен>_<k>` (имя — только в метаданных).
Иначе кириллица/пробелы в имени ломали бы URL.
## Тесты (2026-08-21)
- ВМ: PUT 10МБ → 201 (1.0с); GET → 200, 10485760B; OPTIONS preflight → 204 с CORS;
DELETE → 204; GET после DELETE → 404.
- Сквозной: PUT на ВМ → `POST /api/upload_refs` → 200, файл в сессии (10485760B),
файл с ВМ удалён (404).
- `py_compile` api_bp/app — OK; `node --check` JS из index.html — OK.
## Замечания
- ГРАБЛИ: на Krupski `dd` алиасится на `docker compose down` — для генерации файлов
использовать `head -c N /dev/urandom > file`.
- Локальные curl/httpx к contracts.kube5s.ru — через прокси (`172.17.192.1:10808`):
для тестов `--noproxy '*'` / `NO_PROXY=contracts.kube5s.ru`.
- Прод-безопасность (позже): токены без TTL-времени в самом URL, авторизация на ВМ
(сейчас ключ — случайный UUID, файл живёт ≤30 мин по cron).
+106
View File
@@ -0,0 +1,106 @@
# ПЛАН: тест-режим «только загрузка» (для Флэша)
_Дата: 2026-08-23. Цель: при нажатии «Обфусцировать» — только загрузить файлы
(браузер→ВМ→Flask), НЕ запускать обработку (SSE/LLM/обфускацию)._
## Суть
Добавить флаг тест-режима в фронтенд. Когда он включён, `uploadFiles()` проходит
Фазу 1 полностью (PUT на ВМ + `POST /api/upload_refs` — pull в сессию), а потом
**останавливается до Фазы 2** (не открывает EventSource, не запускает обработку).
Плюс опциональный отладочный endpoint на бэке — проверить, что файлы реально
легли в сессию с корректными размерами.
---
## Изменение 1 — флаг (site/templates/index.html)
В начало скрипта, рядом с `VM_UPLOAD_URL` (строка ~201), добавить:
```js
// Тест-режим: открыть страницу с ?upload-only=1 → только загрузка, без обработки
const TEST_UPLOAD_ONLY = new URLSearchParams(location.search).get('upload-only') === '1';
```
## Изменение 2 — ранний выход после загрузки (site/templates/index.html)
В `uploadFiles()`, сразу ПОСЛЕ блока «Шаг 1b» (try/catch `POST /api/upload_refs`)
и ПЕРЕД комментарием `// Фаза 2: обработка (SSE — прогресс по каждому файлу)`.
При этом в шаге 1b зафиксировать число загруженных файлов. Сейчас там:
```js
const data = await resp.json();
if (!data.ok) throw new Error(data.error || 'HTTP ' + resp.status);
currentSid = data.session;
```
Запомнить count:
```js
const data = await resp.json();
if (!data.ok) throw new Error(data.error || 'HTTP ' + resp.status);
currentSid = data.session;
uploadedCount = data.count || refs.length; // сколько реально легло в сессию
```
(объявить `let uploadedCount = 0;` рядом с `const refs = [];` в начале Фазы 1).
Сразу после `catch` шага 1b вставить:
```js
if (TEST_UPLOAD_ONLY) {
st.className = 'status done';
st.textContent = '✅ Загружено ' + uploadedCount + ' файлов (тест: обработка пропущена). Сессия: ' + currentSid;
ub.disabled = false;
return;
}
```
То есть: показываем итог, снова включаем кнопку, выходим из функции. Фаза 2 не выполняется.
## Изменение 3 (опционально, рекомендуется) — debug-endpoint (site/routes/api_bp.py)
Чтобы проверить размеры файлов в сессии (не повредились ли при pull), добавить:
```python
@api_bp.route("/session_files/<sid>", methods=["GET"])
def session_files(sid):
"""Отладка: список файлов сессии с размерами."""
files = get_files(sid)
if files is None:
return jsonify({"ok": False, "error": "Session not found"}), 404
return jsonify({
"ok": True, "count": len(files),
"files": [{"name": n, "size": len(b)} for n, b in files],
})
```
`get_files` уже импортирован в `api_bp.py`. Endpoint временный — убрать после теста.
---
## Как тестировать
1. Поднять сервис (локально `cd site && python app.py` или redeploy на Штурвале).
2. Открыть `https://drhider.pythonk8s.dev.nubes.ru/?upload-only=1` (или `http://127.0.0.1:5000/?upload-only=1`).
3. Выбрать файлы (в т.ч. большой >1МБ), нажать «Обфусцировать».
4. Ожидать: прогресс «Загрузка на ВМ…» по каждому файлу → «Передача ссылок…» →
«✅ Загружено N файлов (тест: обработка пропущена)». SSE НЕ открывается.
5. (Если сделан п.3) в консоли/curl: `curl /api/session_files/<sid>` → сверить `size` с оригиналом.
6. Проверить на ВМ: после pull файлы удалены (`ls /var/www/drhider-upload/` пусто).
## Как вернуть обратно (после теста)
- Флаг `TEST_UPLOAD_ONLY` без `?upload-only=1` даёт обычное поведение — **ничего выпиливать не надо**,
режим включается только query-параметром.
- Debug-endpoint (п.3) — убрать, когда тест завершён (или оставить под комментарием «отладка»).
## Замечания
- НЕ трогать бэк-логику pull (`/api/upload_refs`) — она уже работает (v0.0.58).
- НЕ менять Фазу 2 (SSE) — только добавить ранний `return` до неё.
- После правки: `python3 -c "import py_compile; py_compile.compile('site/routes/api_bp.py', doraise=True)"`,
JS проверить `node --check` (извлечь блок `<script>` из index.html).
- Версию `VERSION` в `site/app.py` НЕ поднимать, пока это тестовый режим — либо поднять, если деплоим.
+135
View File
@@ -0,0 +1,135 @@
# Проблема загрузки файлов: диагностика и решение
**Дата:** 2026-07-16
**Версия приложения:** v0.0.31
**Кластер:** `iot-naeel` (4 ноды, Cilium Geneve, kube-vip, Штурвал)
---
## Проблема 1: 51-секундная задержка (MTU)
### Суть
При передаче POST из внешней сети — пауза 51с на любом объёме данных. Внутри кластера — мгновенно.
### Причина
Underlay MTU = 1450. Cilium Geneve добавляет 50 байт. Дефолтный MTU пода = 1500.
Cross-node пакет = 1500+50 = 1550 → фрагментация → дроп → ядро ждёт повторной передачи 51с.
### Решение
`cilium-config` → `mtu: 1400`. 1400+50 = 1450 = underlay.
Задокументировано 2026-07-13, применено 2026-07-14.
---
## Проблема 2: ERR_CONNECTION_RESET после фикса MTU
### Суть
curl с ВМ работает. Браузер — `ERR_CONNECTION_RESET` или `⏳ 100%`.
После `rollout restart` ingress — работает 5-10 минут, потом снова RST.
### Ложные следы (опровергнуты)
| Гипотеза | Почему неверна |
|----------|---------------|
| kube-vip ARP flapping | `kube-vip.io/vipHost: control-plane` — VIP жёстко на одной ноде |
| hostPort на 2 из 4 нод | Трафик только на control-plane, не на все 4 |
| upstream keepalive stale | `upstream-keepalive-connections: 0` не помог |
| conntrack | ETPolicy Cluster не при чём — трафик на одну ноду |
| MTU | Решено в проблеме 1 |
| HTTP/2 | `ERR_HTTP2_PROTOCOL_ERROR` → отключили → `ERR_CONNECTION_RESET` — симптом тот же |
### Истинная причина
**`keep-alive: 10`** — Штурвал ставил nginx `keep-alive: 10` (вместо дефолта 75).
Сценарий:
1. Браузер грузит страницу — TCP открыт
2. Пользователь выбирает файлы (>10с) — nginx закрывает idle соединение
3. Браузер не замечает FIN (буферизация ОС)
4. POST 63KB летит в закрытый сокет → TCP RST
### Решение (применено в платформе Штурвала 2026-07-15)
Через `ShturvalServiceConfig` (не kubectl — ArgoCD откатывает):
```yaml
controller:
config:
keep-alive: "75"
proxy-body-size: "1024m"
client-body-timeout: "60"
client-header-timeout: "30"
hostPort:
enabled: true
replicaCount: 2
service:
type: LoadBalancer
```
Также в коде — `fetch('/health')` при загрузке страницы (прогрев upstream).
### v0.0.30 — .doc в диалоге выбора файлов
Добавлен `accept=".doc,.docx,.pdf,.txt,.zip"` в `<input type="file">`.
`doc_to_markdown()` переписан: вместо `soffice` (LibreOffice на ВМ) — HTTP-вызов `liberta.containerk8s.dev.nubes.ru/convert` через `httpx`.
### v0.0.31 — .doc обрабатывается обфускацией
`.doc` теперь не просто конвертируется, а проходит полную обфускацию (замена сущностей, mapping.csv). В архиве — `.md` с заменённым текстом, не исходный `.doc`.
### Результаты тестов (v0.0.29, 2026-07-15)
| Тест | Результат |
|------|-----------|
| 10× POST 63KB (свежие страницы) | **10/10 OK:200** |
| UI: 5 файлов + SSE + ZIP + CSV | ✅ полный цикл |
| 5× POST через 75с (keep-alive окно) | **5/5 OK:200** |
| 5MB PDF через curl | ✅ 200 OK, 0.62с |
| **ИТОГО** | **25/25 успешно, 0 RST** |
---
## Итоговая конфигурация кластера
### Ingress (через платформу Штурвала, несбрасываемо ArgoCD)
| Параметр | Дефолт nginx | Было (Штурвал) | Сейчас |
|----------|-------------|----------------|--------|
| `keep-alive` | 75 | **10** | **75** |
| `proxy-body-size` | 8m | 8m | **1024m** |
| `client-body-timeout` | 60 | **10** | **60** |
| `client-header-timeout` | 60 | **10** | **30** |
| `use-http2` | true | true | **false** |
| `upstream-keepalive-connections` | 0 | 50 | 0 |
| `error-log-level` | info | info | **debug** (вернуть info) |
### Cilium (через kubectl, не сбрасывается)
| Параметр | Дефолт | Сейчас |
|----------|--------|--------|
| `mtu` | 1500 | **1400** |
### Приложение (drhider v0.0.29)
```python
# site/app.py
VERSION = "0.0.29"
app.config["MAX_CONTENT_LENGTH"] = 200 * 1024 * 1024 # 200 MB
```
```javascript
// site/templates/index.html — warmup при загрузке
window.addEventListener('load', () => {
sf = []; fi.value = ''; rr();
fetch('/health').catch(() => {}); // прогрев upstream
});
```
---
## Документация
| Файл | Что |
|------|-----|
| `docs/ARCHITECTURE.md` | Архитектура + требования к Flask |
| `docs/MIGRATION-GUIDE.md` | Инструкция переноса UI на Flask |
| `History/2026-07-14-browser-rst-debug.md` | Полная история диагностики RST |
| `History/2026-07-15-rst-root-cause.md` | Финальный диагноз |
| `History/2026-07-15-cluster-dump.md` | Дамп кластера |
| `STATE-2026-07-15.md` | Состояние проекта на 2026-07-15 |
+20 -33
View File
@@ -51,48 +51,31 @@ git add -A && git commit -m "подробное описание" && git push
### `drhider/` — пакет ядра обфускации
| Файл | Назначение | Строк |
|---|---|---|
| `__init__.py` | re-export: `obfuscate_files`, `LLMClient`, `TwoPassObfuscator` | 10 |
| `config.py` | Константы: regex-паттерны (ENTITY_PATTERNS, COMPANY_PATTERN, PERSON_PATTERN), словари имён/городов/улиц | 80 |
| `checksum.py` | Контрольные суммы: ИНН10, ИНН12, ОГРН | 60 |
| `random_utils.py` | Утилиты: `random_digits`, `random_letters` | 20 |
| `llm_client.py` | LLMClient — HTTP-клиент к LLM API (aillm.ru) | 50 |
| `extractor.py` | Извлечение текста из .docx/.pdf/.txt + `expand_zips` + `convert_pdfs_to_docx` | 180 |
| `scanner.py` | Проход 1: `scan_regex` (regex) + `scan_llm_ner` (LLM NER) | 110 |
| `replacer.py` | Проход 2: `apply_replacements`, `replace_in_docx`, `replace_in_text` | 100 |
| `builder.py` | Сборка: `build_zip`, `build_mapping_csv` | 65 |
| `obfuscator.py` | `TwoPassObfuscator` — оркестратор + `obfuscate_files()` | 100 |
### `drhider/generators/` — генераторы фиктивных значений
Каждый файл — один генератор (10–40 строк). Интерфейс: `generate_xxx(original: str) -> str`.
| Файл | Что генерирует |
| Файл | Назначение |
|---|---|
| `phone.py` | +7 (XXX) XXX-XX-XX |
| `email.py` | user@fake-domain |
| `inn.py` | ИНН 10 и 12 знаков (с контрольной суммой) |
| `ogrn.py` | ОГРН 13 знаков |
| `kpp.py` | КПП 9 знаков |
| `bik.py` | БИК 9 знаков (04XXXXXXX) |
| `accounts.py` | Расчётный счёт (40702...) + корр. счёт (30101...) |
| `passport.py` | Паспорт (XX XX XXXXXX) |
| `company.py` | ООО/ЗАО/АО «СлучайноеНазвание» |
| `person.py` | Фамилия И.О. |
| `address.py` | Город, ул. Улица, д. N |
| `__init__.py` | re-export: `obfuscate_files`, `LLMClient`, `TwoPassObfuscator` |
| `config.py` | Константы: regex-паттерны (ENTITY_PATTERNS, COMPANY_PATTERN, PERSON_PATTERN), словари имён/городов/улиц |
| `llm_client.py` | LLMClient — HTTP-клиент к LLM API (aillm.ru) |
| `extractor.py` | Извлечение текста из .docx/.pdf/.doc/.txt + `expand_zips` |
| `scanner.py` | Проход 1: `scan_regex` (regex) + `scan_llm_ner` (LLM NER) + генерация гибридных токенов |
| `replacer.py` | Проход 2: `apply_replacements`, `replace_in_docx` |
| `builder.py` | Сборка: `build_zip`, `build_mapping_csv` |
| `obfuscator.py` | `TwoPassObfuscator` — оркестратор + `obfuscate_files()` |
`__init__.py` содержит `ENTITY_GENERATORS` — маппинг `entity_type → генератор`.
Фиктивные значения — **гибридные токены** (шаблон + номер), генерируются в `scanner.py`
через `TYPE_POOLS` + `_next_token()`. Примеры: `Иванов_0001`, `ООО_Технология_0034`, `ул_Ленина_0015`.
Отдельного пакета генераторов (`generators/`) нет.
### `site/` — Flask-приложение
| Файл | Назначение |
|---|---|
| `app.py` | `create_app()` — создание Flask, регистрация blueprint'ов, VERSION |
| `session.py` | Управление сессиями: файлы, результат, CSV, очистка по таймауту |
| `routes/__init__.py` | `register_routes(app)` |
| `routes/main_bp.py` | `GET /` — HTML-интерфейс |
| `routes/health_bp.py` | `GET /health` — `{"ok": true, "version": "..."}` |
| `routes/api_bp.py` | `POST /api/drhider` — multipart files → ZIP |
| `routes/api_bp.py` | API загрузки/обработки/скачивания (см. раздел API) |
| `templates/index.html` | UI (выбор файлов, прогресс, скачивание) |
| `static/favicon.svg` | Иконка |
@@ -116,8 +99,12 @@ git add -A && git commit -m "подробное описание" && git push
| Метод | Путь | Что делает |
|---|---|---|
| `GET` | `/` | HTML-интерфейс |
| `GET` | `/health` | `{"ok": true, "version": "2.0.0"}` |
| `POST` | `/api/drhider` | multipart/form-data (поле `files`) → `application/zip` |
| `GET` | `/health` | `{"ok": true, "version": "..."}` |
| `POST` | `/api/upload` | Загрузка файлов в сессию → `{ok, session, count}` |
| `GET` | `/api/process_stream/<sid>` | SSE: обработка файлов сессии, прогресс пофайлово |
| `POST` | `/api/process/<sid>` | Обработка файлов сессии (legacy) |
| `GET` | `/api/download/<sid>` | Скачать ZIP с обфусцированными файлами |
| `GET` | `/api/csv/<sid>` | Скачать mapping.csv отдельно |
---
+107
View File
@@ -0,0 +1,107 @@
# DrHider — состояние на 2026-07-15
## Текущая версия: v0.0.29 (задеплоена)
## Что работает
- Загрузка файлов (.docx, .pdf, .txt, .zip)
- Обфускация через LLM (два прохода: regex + LLM NER)
- SSE-прогресс обработки
- Скачивание ZIP + CSV (имена из Content-Disposition, не хардкод)
- Выбор файлов добавляет (не стирает), с дедупликацией
- При F5/закрытии — XHR и EventSource обрываются
## Что НЕ работает (баг вернулся)
**ERR_CONNECTION_RESET / Таймаут 30с при загрузке из браузера.**
Причина: kube-vip ARP flapping + `externalTrafficPolicy: Cluster` + 4 ingress пода.
Браузерное keep-alive соединение попадает на ноду без conntrack → RST.
### Что пробовали
| Фикс | Результат |
|------|-----------|
| `keep-alive: 10 → 75` | Помогло, но временно |
| JS: `fetch('/health')` при загрузке (warmup) | Помогло, но временно |
| `upstream-keepalive-connections: 0` | Не помогло |
| Перезапуск ingress | Помогает на 5-10 мин, потом снова |
### Что НЕ пробовали (главный кандидат)
**`externalTrafficPolicy: Local`** — убирает лишний хоп kube-proxy, conntrack создаётся на той же ноде где ingress под. Это должно добить проблему навсегда.
Но затрагивает ВСЕ сервисы на этом ingress, не только drhider.
## Текущая конфигурация кластера
### Ingress ConfigMap
```
keep-alive: "75" # было 10 — Штурвал
client-body-timeout: "10" # было 10 — надо 60
client-header-timeout: "10" # было 10 — надо 30
use-http2: "false" # можно вернуть true
proxy-body-size: "1024m"
upstream-keepalive-connections: "0"
error-log-level: "debug" # вернуть info
```
### Cilium
```
mtu: 1400 # было 1500 — Geneve overhead
```
### Ingress поды (4 шт — что-то изменило кол-во)
```
control-plane: 172.16.0.73 (3h13m)
v8zq4: 172.16.3.149 (3h13m)
bhbvs: 172.16.2.115 (29m) ← новые
6f74n: 172.16.1.227 (29m) ← новые
```
### Drhider под
```
Неймспейс: 20a75175-a58c-49cb-b8fa-e86367b1a8dc
Deployment: pythonk8s (1 реплика)
```
## Ключевые файлы
| Файл | Что |
|------|-----|
| `site/templates/index.html` | Весь фронтенд (HTML/CSS/JS) |
| `site/routes/api_bp.py` | API: upload, process_stream (SSE), download, csv |
| `site/routes/health_bp.py` | `/health` — liveness probe |
| `site/app.py` | Точка входа Flask, VERSION, MAX_CONTENT_LENGTH, no_cache |
| `site/session.py` | In-memory сессии |
| `drhider/*.py` | Бизнес-логика обфускации |
| `docs/ARCHITECTURE.md` | Архитектура, требования Flask, имена файлов |
| `docs/MIGRATION-GUIDE.md` | Инструкция миграции UI на Flask |
| `PROBLEM-AND-SOLUTION.md` | MTU fix, конфигурация для DevOps |
| `History/2026-07-14-browser-rst-debug.md` | Полная история диагностики RST |
## Как деплоить
```bash
# После правок кода:
cd /home/naeel/nubes/drhider
# Bump VERSION в site/app.py
# Bump версию в index.html (два места: заголовок .ver и в JS если есть)
git add -A && git commit -m "..." && git push
ssh ... naeel@5.172.178.213 "kubectl rollout restart deploy -n 20a75175... pythonk8s"
```
## Что делать дальше
1. **Срочно:** перезапустить ingress чтобы пользователь мог работать сейчас:
```bash
kubectl rollout restart deploy -n ingress shturval-ingress-controller-controller
```
2. **Навсегда:** `externalTrafficPolicy: Local` — обсудить с DevOps
3. **Докупить:** обновить PROBLEM-AND-SOLUTION.md после применения Local
+34
View File
@@ -0,0 +1,34 @@
тип_данных,оригинал,замена
text,"""115404"," г. Москва, ул. 1-я Стекольная, д.7с2"",Красноярск_0001 "
text,"""АО """"****XXX****003****""""""","Альянс_0001 "
text,"""АО """"XXX003""""""","Стандарт_0001 "
text,"""ЗАО """"****XXX****001****""""""","Сервис_0001 "
text,"""ЗАО """"XXX001""""""","Прогресс_0001 "
text,"""ООО """"НУБЕС""""""","Меридиан_0001 "
phone,+7 (495) 789-41-35,"+7_000_000_0001 "
text,01300_2,"Договор_0004 "
text,044525225,"БИК_0001 "
text,FILE: допник-1-XXX002-01200_3.doc,ID_0001
email,info@nubes.ru,"email_0001 "
company,ЗАО «ХХХ001»,"Сервис_0001 "
inn_ul,ИНН 9706005293,"ИНН_0001 "
text,Исполнителем Заказчику Сторонами,"Кузнецов_0002 "
kpp,КПП 772401001,"КПП_0001 "
ks,Кор/сч 30101810400000000225,"КС_0001 "
ogrn,ОГРН 1207700098759,"ОГРН_0001 "
company,ООО «НУБЕС»,"Проект_0001 "
company,ПАО Сбербанк,"Меридиан_0001 "
rs,Р/с 40702810738000174030,"РС_0001 "
text,Степаненко В.С.,"Михайлов_0001 "
text,Степаненко Василия Сергеевича,"Фёдоров_0001 "
text,г. Москва,"Екатеринбург_0001 "
text,г. Москва ул. 1я Стекольная д7 стр. 5,"Нижний_Новгород_0001 "
text,г. Москва ул. Бутлерова д. 7,Красноярск_0001
text,договор-XXX001-03700.docx,"ID_0001 "
text,спецификация-XXX003-01300_2.docx,"ID_0001 "
text,№ 01300_2,"Договор_0003 "
text,№ 03700_1,Договор_0001
text,№****1,"Договор_0001 "
text,№01200_3,Договор_0001
text,№01300_2,Договор_0001
text,№1,Договор_0002
1 тип_данных оригинал замена
2 text "115404 г. Москва, ул. 1-я Стекольная, д.7с2",Красноярск_0001
3 text "АО ""****XXX****003****""" Альянс_0001
4 text "АО ""XXX003""" Стандарт_0001
5 text "ЗАО ""****XXX****001****""" Сервис_0001
6 text "ЗАО ""XXX001""" Прогресс_0001
7 text "ООО ""НУБЕС""" Меридиан_0001
8 phone +7 (495) 789-41-35 +7_000_000_0001
9 text 01300_2 Договор_0004
10 text 044525225 БИК_0001
11 text FILE: допник-1-XXX002-01200_3.doc ID_0001
12 email info@nubes.ru email_0001
13 company ЗАО «ХХХ001» Сервис_0001
14 inn_ul ИНН 9706005293 ИНН_0001
15 text Исполнителем Заказчику Сторонами Кузнецов_0002
16 kpp КПП 772401001 КПП_0001
17 ks Кор/сч 30101810400000000225 КС_0001
18 ogrn ОГРН 1207700098759 ОГРН_0001
19 company ООО «НУБЕС» Проект_0001
20 company ПАО Сбербанк Меридиан_0001
21 rs Р/с 40702810738000174030 РС_0001
22 text Степаненко В.С. Михайлов_0001
23 text Степаненко Василия Сергеевича Фёдоров_0001
24 text г. Москва Екатеринбург_0001
25 text г. Москва ул. 1я Стекольная д7 стр. 5 Нижний_Новгород_0001
26 text г. Москва ул. Бутлерова д. 7 Красноярск_0001
27 text договор-XXX001-03700.docx ID_0001
28 text спецификация-XXX003-01300_2.docx ID_0001
29 text № 01300_2 Договор_0003
30 text № 03700_1 Договор_0001
31 text №****1 Договор_0001
32 text №01200_3 Договор_0001
33 text №01300_2 Договор_0001
34 text №1 Договор_0002
+286
View File
@@ -0,0 +1,286 @@
**************Договор******** ****************на оказание технологических услуг******
Договор_0001
**Сервис_0001** именуемое в дальнейшем "Заказчик", в лице Генерального директора ФИО, действующего на основании Устава, и **ООО **"**НУБЕС**", именуемое в дальнейшем "Исполнитель", в лице Генерального директора Николаев_0001, действующего на основании Устава, с другой стороны, вместе именуемые "Стороны", заключили настоящий Договор о нижеследующем:
# ТЕРМИНЫ И ОПРЕДЕЛЕНИЯ
**Услуги **– оказываемые Исполнителем Заказчику технологические услуги в области компьютерных и телекоммуникационных технологий и информационной безопасности, размещение серверов на технической площадке (colocation), организация канала связи, обеспечение бесперебойного электропитания и охлаждения, предоставление готовой технической инфраструктуры, профессиональное техническое обслуживание оборудования в режиме 24/7/365, подключение к сети Интернет по высокоскоростным каналам связи, оказываемые Исполнителем Заказчику. Услуги подразделяются на Инсталляционные, Абонентские (ежемесячные), Тарифицируемые и разовые (дополнительные). Полный перечень и описание оказываемых Услуг приводятся в Спецификациях, утверждаемых Сторонами в виде Приложения №1 к Договору, являющегося его неотъемлемой частью (далее – «Спецификация»).
**Инсталляционные услуги** – Услуги по установке, настройке и включению в сетевую инфраструктуру Исполнителя оборудования и/или программного обеспечения, необходимого для предоставления Заказчику Абонентских и Тарифицируемых Услуг.
**Абонентские услуги** – Услуги, указанные в Спецификации, предоставляемые ежемесячно в течение всего срока действия Договора.
**Тарифицируемые услуги** – Услуги, оказываемые в дополнение к Абонентским, количество объектов обслуживания для которых непостоянно. Ежемесячная стоимость услуги определяется в конце каждого отчетного периода, в соответствии с ценой услуги за один объект обслуживания указанной в Спецификации.
**Разовые (дополнительные) услуги** – Услуги, которые предоставляются на основании письменного запроса Заказчика, оформленного в соответствии Регламентом взаимодействия Сторон в процессе оказания Услуг, утверждаемом Сторонами в виде Приложения №3 к настоящему Договору (далее – «**Регламент взаимодействия Сторон в процессе оказания Услуг**»).
**Этап оказания Услуг** - очередной этап исполнения обязательств по Договору, Отчетный период или период предоставления Услуг, предусмотренных Договором.
**Информационная система (далее – «ИС»)** – совокупность прикладного программного обеспечения и информационного наполнения, обладающая полезными функциями, необходимыми Заказчику. ИС может являться собственностью Заказчика или предоставляться ему в пользование Исполнителем на основании отдельно заключенного Сторонами соглашения.
**Облачные ресурсы – **виртуальные ресурсы ПАК (виртуальные серверы, резервное копирование данных), а также типовое программное обеспечение и специальное программное обеспечение, необходимое для выполнения конкретных функций. Состав и конфигурация предоставляемого ресурса и функционала ПАК Исполнителя приведены в Спецификации.
**Программно-аппаратный комплекс (далее – «ПАК»)** – совокупность оборудования и программного обеспечения, необходимого для оказания предусмотренных Договором Услуг.
**Технологическая площадка** – специализированное помещение, в котором размещается серверное и сетевое оборудование Исполнителя и предоставляются Услуги.
**Конфиденциальная информация -** означает всю информацию, не являющуюся общедоступной, предоставляемую одной Стороной другой Стороне в связи с настоящим Договором, за исключением той информации, которую Стороны определяют, как не конфиденциальная и которая не является конфиденциальной в соответствии с действующим законодательством РФ.
**Отчетный период -** означает календарный месяц.
**Рабочий день** означает день, официально считающийся в текущем году рабочим на территории Российской Федерации при пятидневной рабочей неделе.
**Уровень обслуживания** – совокупность параметров качества оказываемых Услуг, описывается в Соглашении об уровне обслуживания, утверждаемом Сторонами в виде Приложения № 2 к Договору (далее – «**Соглашение об уровне обслуживания**») и Приложении № 4 к Договору (далее – «**Технические условия**»), являющихся неотъемлемой частью Договора.
**УПД **- универсальный передаточный документ, подтверждающий выполнение работ и/или оказание Услуг Исполнителем.** **
**Криптовалютный майнинг** - процесс использования вычислительных мощностей для решения алгоритмических задач, направленных на создание новых блоков в блокчейне и получение вознаграждения в виде криптовалюты.
# ПРЕДМЕТ ДОГОВОРА
Исполнитель обязуется оказывать Заказчику технологические Услуги, указанные в Спецификации, а Заказчик обязуется принимать и оплачивать Услуги в объеме и в сроки согласно условиям настоящего Договора и Приложений к нему.
Место оказания услуг – технологическая площадка, Российская Федерация, г. Москва, 1-я Стекольная 7с5, г. Москва, 1-я Стекольная 7с6.
Период оказания Услуг по настоящему Договору, подлежащий оплате Заказчиком, начинается с даты подписания Сторонами Акта начала оказания Услуг или с даты начала Услуг, согласованной Сторонами в Спецификации, и заканчивается в момент истечения срока действия, прекращения или расторжения настоящего Договора, если иное не установлено настоящим Договором или письменно не согласованно Сторонами.
Технические требования к оказанию Услуг указаны в Соглашении об уровне обслуживания и Технических условиях.
# ПРАВА И ОБЯЗАННОСТИ СТОРОН
## Степанов_0001 определять количество специалистов и график их работы, объем вычислительных ресурсов и программного обеспечения, а также других ресурсов, необходимых для оказания Услуг. В случае необходимости Исполнитель имеет право привлекать для исполнения обязательств по настоящему Договору третьи стороны, при этом Исполнитель в полном объеме несет ответственность за действия привлеченных Исполнителем третьих лиц перед Заказчиком.
На основании Универсального передаточного документа (далее – «УПД»), подписываемого обеими Сторонами и счета на оплату, потребовать дополнительной оплаты затраченных ресурсов, если по вине или инициативе Заказчика в ходе исполнения настоящего Договора объем фактически оказанных Услуг превысил объем Услуг, предусмотренный условиями настоящего Договора.
Изменять в одностороннем порядке не чаще одного раза в 6 (шесть) месяцев цены на предоставляемые им Услуги с обязательным уведомлением Заказчика в письменной форме в срок не менее чем за тридцать календарных дней до вступления в силу новых цен. Отсутствие в течение вышеуказанного срока письменного отказа от новых цен со стороны Заказчика считается его согласием с указанными изменениями. Об изменении цен по Договору Стороны подписывают дополнительное соглашение. Заказчик обязуется в течение 15 рабочих дней с даты получения уведомления Исполнителя и Дополнительного соглашения об изменении цены по Договору подписать такое Дополнительное соглашение, либо направить Исполнителю письменный мотивированный отказ от его подписания. В случае отказа Заказчика от подписания Дополнительного соглашения на условиях, предлагаемых Исполнителем, а также в случае, если в указанный срок Заказчик не направит Исполнителю письменный мотивированный отказ от подписания Дополнительного соглашения, Исполнитель вправе расторгнуть настоящий Договор в порядке, предусмотренном п.15.11 настоящего Договора.
При оказании Услуг осуществлять взаимодействие с представителями Заказчика и доступ к оборудованию Заказчика, размещаемому на Технологической площадке для эксплуатационно-технического обслуживания в соответствии с Регламентом взаимодействия сторон в процессе оказания Услуг (Приложение №3 к настоящему Договору).
Запрашивать у Заказчика информацию (материалы, документы и сведения технического и организационного характера), необходимую для качественного и бесперебойного оказания Услуг.
По истечении 10 (десяти) календарных дней с момента приостановления оказания услуг или расторжения договора безвозвратно удалить все элементы информационной системы Заказчика, предоставляемые Исполнителем согласно Спецификации. Уведомление о приостановлении оказания услуг направляется в сроки, указанные в разделе 8 настоящего Договора. Уведомление о предстоящем расторжении договора в одностороннем порядке направляется в порядке, указанном в п.15.3. настоящего Договора.
В случае нарушения условий п. 9.2.11 Договора, а также при выявлении недостоверной информации, предоставленной Заказчиком Исполнитель вправе в одностороннем порядке расторгнуть Договор без возмещения Заказчику каких-либо затрат. Исполнитель не несет ответственности за убытки Заказчика, возникшие в связи с таким расторжением.
## Егоров_0001 у Исполнителя в дополнение к предусмотренным в Спецификациях иные Инсталляционные, Абонентские (ежемесячные), Тарифицируемые и разовые (дополнительные) Услуги.
Отказаться от ранее заказанных Тарифицируемых Услуг, письменно уведомив о своем намерении Исполнителя не позднее, чем за пять рабочих дней до даты прекращения указанных услуг. При этом Заказчик обязан оплатить Исполнителю стоимость фактически оказанных услуг.
Запрашивать у Исполнителя информацию о ходе выполнения Услуг.
Требовать сверки взаиморасчетов по Договору.
Осуществлять иные права по настоящему Договору.
Контролировать процесс оказания Исполнителем Услуг по настоящему Договору, не вмешиваясь в производственную и хозяйственную деятельность Исполнителя.
## Соколов_0001 Заказчику Услуги в объеме и в сроки, предусмотренные настоящим Договором и Приложениями к нему.
Принять в целях размещения на Технологической площадке Исполнителя оборудование Заказчика по Акту приема-передачи оборудования, подписываемому уполномоченными представителями Сторон в порядке, предусмотренном Регламентом взаимодействия Сторон в процессе оказания Услуг, и обеспечить условия его размещения и сохранности в соответствии с условиями Договора и Соглашения об уровне обслуживания.
Обеспечить работоспособность оборудования и программного обеспечения ПАК Исполнителя на Технологической площадке Исполнителя в соответствии с гарантиями и сертификатами их производителей на основании Соглашения об уровне обслуживания.
Вести учет потребления и оплаты Заказчиком Услуг, оказываемых в соответствии со Спецификацией.
Рассматривать запросы Заказчика на Услуги, не предусмотренные Спецификацией к настоящему Договору, и предоставлять Заказчику в письменной форме технико-экономическое предложение на оказание запрашиваемых услуг или обоснованный отказ в связи с невозможностью оказания запрашиваемых услуг.
В случае подачи Заказчиком запроса на вынос оборудования с Технологической площадки Исполнителя, передать по Акту приема-передачи оборудования, подписываемому уполномоченными представителями Сторон, принадлежащее Заказчику оборудование в порядке, предусмотренном Регламентом взаимодействия Сторон в процессе оказания Услуг.
По окончании монтажа оборудования Заказчика на Технологической площадке провести подключение оборудования к сети электропитания; принять оборудование Заказчика в эксплуатацию. По факту передачи оборудования Заказчика в эксплуатацию уполномоченные представители Сторон подписывают Акт начала оказания Услуг.
В случае предоставления Заказчиком Исполнителю учетной информации для получения доступа к услугам обеспечить конфиденциальность данной информации в соответствии со ст. 12 настоящего Договора.
В случае возникновения аварий и их последствий на Технологической площадке обе Стороны составляют Акт, устанавливающий причины, виновника возникновения аварий, размеры и порядок возмещения ущерба.
Обеспечивать представителям Заказчика возможность круглосуточного доступа к размещенному оборудованию Заказчика в соответствии с условиями настоящего Договора и Приложений к нему.
## Попов_0001 принимать и оплачивать Услуги Исполнителя в размере и в сроки, установленные настоящим Договором.
Предоставлять в согласованные сроки Исполнителю по его требованию все необходимые для оказания им Услуг материалы, документы и сведения технического и организационного характера.
За свой счет доставить оборудование Заказчика в предварительно согласованные Сторонами сроки на Технологическую площадку Исполнителя, передать Исполнителю оборудование в порядке, предусмотренном Регламентом взаимодействия Сторон в процессе оказания Услуг, по Акту приема-передачи оборудования, подписываемому уполномоченными представителями Сторон, и обеспечить необходимые условия проверки его комплектации и работоспособности Исполнителем.
Информировать Исполнителя о вносимых в документацию по обслуживанию оборудования дополнениях и изменениях не позднее 1 (одного) рабочего дня со дня внесения таких дополнений и изменений.
Соблюдать пропускной режим, правила посещения Технологической площадки, правила пожарной безопасности и техники безопасности, требования Госсанэпидемнадзора, а также отраслевых правил и норм, действующих в сфере деятельности Заказчика, в отношении Технологической площадки и предоставленного Исполнителем оборудования.
Немедленно извещать Исполнителя о всяком повреждении, аварии, угрозе повреждения или аварии, или ином событии, которые могут нанести ущерб находящемуся на Технологической площадке оборудованию, и своевременно принимать все возможные меры по предотвращению дальнейшего разрушения или повреждения оборудования, находящегося на Технологической площадке, а также самой Технологической площадки.
Не производить прокладок скрытых и открытых проводок и коммуникаций, перепланировок и переоборудования Технологической площадки, вызываемых потребностями Заказчика, без предварительного письменного разрешения Исполнителя и собственника здания. При наличии необходимости у Заказчика прокладки проводок и коммуникаций, перепланировок и переоборудования Технологической площадки и при наличии письменного разрешения Исполнителя и собственника здания, соответствующие работы выполняются за счет Заказчика. Затраты на проведение вышеуказанных работ не подлежат возмещению Исполнителем или собственником здания.
В случае обнаружения Исполнителем самовольных перестроек, нарушения целостности стен, перегородок или перекрытий, переделок или прокладок сетей, проводок и иных коммуникаций на Технологической площадке, таковые должны быть ликвидированы Заказчиком, а Технологическая площадка приведена в прежний вид за его счет в срок, определяемый односторонним предписанием Исполнителя.
Производить монтаж/демонтаж и подключение к сети или отключение от сети электропитания Технологической площадки Исполнителя любого оборудования Заказчика на Технологической площадке Исполнителя строго в соответствии с Регламентом взаимодействия Сторон в процессе оказания Услуг.
Не производить подключение любого оборудования к оборудованию Исполнителя и иному оборудованию, не принадлежащему Заказчику, без предварительного письменного разрешения Исполнителя.
В случае предоставления Исполнителем на основании отдельно заключаемого Сторонами соглашения принадлежащих Исполнителю оборудования, программного обеспечения и/или технических средств в пользование Заказчику использовать их исключительно в соответствии с условиями такого соглашения и только для получения Услуг Исполнителя.
После окончания срока, на который были предоставлены оборудование и/или технические средства, прекратить их использование и возвратить Исполнителю предоставленное им оборудование и/или технические средства в том состоянии, в котором Заказчик их получил с учетом нормального износа, а также осуществить иные действия, предусмотренные соглашением Сторон. После окончания срока, на который было предоставлено в пользование программное обеспечение Исполнителя, осуществить действия, предусмотренные соглашением Сторон, в отношении такого программного обеспечения.
В случае неисполнения или ненадлежащего исполнения Заказчиком обязанностей, предусмотренных настоящим Договором и Приложениями к нему, по вывозу оборудования Заказчика с Технологической площадки, компенсировать расходы Исполнителя, связанные с демонтажем и хранением оборудования.
Обеспечивать Исполнителю возможность доступа к размещенному оборудованию Заказчика в соответствии с Регламентом взаимодействия Сторон в процессе оказания Услуг.
В случае предоставления Исполнителем доступа к учетной информации обеспечить ее конфиденциальность в соответствии со ст. 12 настоящего Договора.
В случае осуществления монтажа оборудования на Технологической площадке Заказчиком, по окончании монтажных работ сдать Исполнителю оборудование в эксплуатацию в порядке, предусмотренном Регламентом взаимодействия Сторон в процессе оказания Услуг. По факту передачи оборудования Заказчика в эксплуатацию уполномоченные представители Сторон подписывают Акт начала оказания Услуг.
Осуществлять вынос (внос) с (на) Технологической площадки Исполнителя оборудования, принадлежащего Заказчику, в порядке, предусмотренном Регламентом взаимодействия Сторон в процессе оказания Услуг, по Акту приема-передачи оборудования, подписываемому уполномоченными представителями Сторон.
Оказывать содействие в работе представителей Исполнителя включая участие специалистов и ответственных лиц Заказчика и/или его представителей в переговорах с представителями Исполнителя, согласование, утверждение и приемку результатов Услуг.
Возместить Исполнителю все убытки, понесенные в результате использования услуг в целях, запрещенных настоящим Договором, включая перерасход энергоресурсов, износ оборудования и затраты на устранение иных последствий.
# СОСТАВ, ПОРЯДОК И СРОКИ ОКАЗАНИЯ УСЛУГ
Этапы оказания Услуг по Договору:
Первый этап – оказание Инсталляционных услуг. Продолжительность оказания Инсталляционных услуг устанавливается в Спецификации.
Второй этап - оказание Абонентских и Тарифицируемых услуг.
Дата начала оказания Инсталляционных услуг определяется в Спецификации, но не ранее получения всей информации и документов от Заказчика, необходимых Исполнителю для начала предоставления Инсталляционных услуг по настоящему Договору.
Исполнитель имеет право изменить (соразмерно продлить) срок предоставления Инсталляционных услуг в случае, если Заказчик не предоставил в согласованные сроки, указанные в настоящем Договоре документы и информацию. В этом случае Исполнитель направляет Заказчику письменное уведомление об изменении (соразмерном продлении) сроков предоставления Инсталляционных услуг.
Планируемая дата начала оказания Абонентских услуг по Договору определяется в Спецификации. Фактическая дата начала оказания Абонентских услуг по Договору определяется датой подписания Акта начала оказания Услуг обеими Сторонами (по форме Приложения №5 к настоящему Договору). Заказчик признает и соглашается, что в случае несвоевременного оказания Исполнителем Инсталляционных услуг, которое произошло в связи с неисполнением, ненадлежащим исполнением обязательств Заказчика по Договору или по инициативе Заказчика, датой начала оказания Исполнителем Абонентских и Тарифицируемых услуг по настоящему Договору является планируемая дата начала оказания соответствующих Услуг, указанная в Спецификации. В этом случае Исполнитель выставляет счета Заказчику, начисляет платежи за соответствующие Услуги, а Заказчик обязуется оплачивать стоимость Услуг, с планируемой даты начала оказания Услуг. Исполнитель признает и соглашается с тем, что в случае готовности Заказчика к началу оказания услуг ранее планируемой даты начала оказания Услуг, указанной в Спецификации, Стороны вправе подписать Акт начала оказания услуг. В этом случае начисление платежей по Договору производится с даты подписания данного Акта.
Если Заказчик планирует ввоз оборудования последовательно, то этап оказания Абонентских и Тарифицируемых услуг по Договору начинается с даты подписания первого Акта начала оказания услуг. Незаполненные оборудованием стойки оплачиваются Заказчиком, исходя из стоимости резерва места под стандартную стойку на территории Технологической площадки, указанной в Спецификации.
Перечень и объем Услуг указывается в Спецификации.
Схема размещения оборудования и технические требования указываются в Технических условиях (Приложение №4 к настоящему Договору).
# СТОИМОСТЬ УСЛУГ
Стоимость Инсталляционных, Абонентских (ежемесячных) и Тарифицируемых услуг указывается в Спецификации.
Стоимость Услуг рассчитывается в соответствии со Спецификацией и запросами на оказание Тарифицируемых Услуг, поступившими от Заказчика в порядке, предусмотренном Регламентом взаимодействия Сторон в процессе оказания Услуг (Приложение №3 к настоящему Договору).
Стоимость фактически оказанных Услуг рассчитывается Исполнителем исключительно на основании зафиксированных Исполнителем данных потребления Услуг, включая разовые (дополнительные) и Тарифицируемые услуги.
# ПОРЯДОК И СРОКИ ОПЛАТЫ
Все расчеты по настоящему Договору осуществляются в безналичной форме в рублях Российской Федерации. Датой оплаты считается дата зачисления денежных средств на расчетный счет Исполнителя. Факт оплаты Заказчик подтверждает копией платежного поручения с отметкой банка, высланной в адрес Исполнителя по факсу, электронным или иным способом.
Расчеты между Сторонами по платежам, обусловленным доплатой или возвратом денежных средств, производятся на основании двустороннего Акта сверки расчетов и счета, выставляемого в срок не позднее 5 (пяти) рабочих дней с момента подписания Сторонами Акта сверки расчетов.
Все платежи по настоящему Договору производятся Заказчиком в адрес Исполнителя по реквизитам, указанным в разделе 18 настоящего Договора, на основании выставляемых Исполнителем счетов не позднее 10 (десяти) дней с момента их выставления. Все расходы, связанные с переводом денежных средств на расчетный счет Исполнителя, принимает на себя Заказчик.
Счета на оплату направляются по электронной почте в адрес Заказчика. Доставка оригинала УПД производится путем отправки курьерской или почтовой корреспонденции.
Оплата за Инсталляционные услуги производится Заказчиком единовременно постоплатой в размере 100% (ста процентов) от стоимости этих Услуг на основании счета, выставляемого Исполнителем одновременно с УПД.
Оплата за Абонентские услуги производится Заказчиком ежемесячно постоплатой в размере 100% (ста процентов) от стоимости Абонентских услуг на основании счета, выставляемого Исполнителем одновременно с УПД.
Оплата Тарифицируемых услуг производится Заказчиком ежемесячно после окончания Отчетного периода на основании счета, выставляемого Исполнителем одновременно с УПД.
В случае оказания Исполнителем разовых (дополнительных) услуг Заказчику их оплата производится на основании отдельного счета, выставленного на основании УПД. В данном УПД Исполнитель указывает перечень оказанных Услуг. Услуги предоставляются на основании письменного запроса Заказчика, оформленного в соответствии Регламентом взаимодействия Сторон в процессе оказания Услуг.
В случае изменения стоимости Услуг согласно условиям настоящего Договора, ранее внесенная Заказчиком оплата за эти Услуги не пересчитывается по новой стоимости. В этом случае Услуги считаются оплаченными, и Исполнитель обязуется их предоставить в соответствии с условиями настоящего Договора.
Стоимость Услуг, оказанных Исполнителем за неполный Отчетный период, рассчитывается Исполнителем в соответствии со Спецификацией пропорционально времени оказания Услуг.
# ПОРЯДОК ВЗАИМОДЕЙСТВИЯ СТОРОН
Порядок взаимодействия Сторон в процессе оказания Услуг, порядок и формы обмена информацией и документами между Сторонами определяется Регламентом взаимодействия Сторон в процессе оказания Услуг (Приложение №3 к настоящему Договору) и условиями настоящего Договора.
# ПОРЯДОК СДАЧИ И ПРИЕМКИ УСЛУГ
Сдача и приемка Абонентских и Тарифицируемых услуг производится ежемесячно на основании УПД.
Сдача и приемка Инсталляционных услуг производится на основании УПД, подписываемого обеими Сторонами по окончании этапа инсталляции.
Сдача и приемка разовых (дополнительных) услуг производится на основании УПД, подписываемого обеими Сторонами по окончании этапа оказания этих Услуг.
По завершении каждого месяца оказания Услуг, по окончании этапа инсталляции, а также в иных случаях, предусмотренных Договором, в течение пяти рабочих дней Исполнитель направляет Заказчику для ознакомления и подписания два экземпляра, соответствующего УПД, датированные последним днем Этапа оказания Услуг.
Заказчик в течение 5 (пяти) рабочих дней со дня выставления УПД обязан направить Исполнителю один экземпляр подписанного им УПД или письменный мотивированный отказ от приемки данного Этапа Услуг.
В случае предъявления Заказчиком мотивированного отказа от приемки Услуг, Сторонами в течение 10 (десяти) календарных дней составляется двусторонний протокол с перечнем необходимых доработок и сроков их выполнения. Указанные доработки производятся силами и за счет Исполнителя. Повторная сдача-приемка Услуг производится Заказчиком в порядке, установленном настоящим Договором.
В случае неисполнения Заказчиком условий, указанных в п.7.5 настоящего Договора, Услуги считаются оказанными в полном объеме и с надлежащим качеством, принятыми Заказчиком без претензий и подлежащими оплате, а УПД им подписанным.
# ОСОБЫЕ УСЛОВИЯ
## Исполнитель вправе приостановить оказание Услуг в следующих случаях:
Просрочки Заказчиком платежей за оказанные Услуги на срок свыше десяти рабочих дней с момента выставления счета и УПД или в случае поступления оплаты не в полном объеме - до момента полной оплаты;
При обнаружении неисправности, несоответствия оборудования Заказчика заявленным параметрам или при возникновении форс-мажорных обстоятельств, препятствующих качественному обслуживанию оборудования Заказчика до момента устранения соответствующих обстоятельств;
При возникновении форс-мажорных обстоятельств, препятствующих качественному предоставлению Услуг Заказчику до момента устранения соответствующих обстоятельств;
Если использование Заказчиком Услуг может нанести ущерб Исполнителю и/или вызвать сбой технических и программных средств Исполнителя и третьих лиц;
Заказчик использует Услуги Исполнителя, либо Заказчик использует Технологическую площадку Исполнителя для каких-либо незаконных целей, в том числе, но не ограничиваясь этим, нарушает права на интеллектуальную собственность, исключительные права, патентные, авторские, смежные или иные охраняемые законом права Исполнителя и третьих лиц;
Заказчик эксплуатирует предоставленное оборудование с нарушением правил технической эксплуатации;
Наличия со стороны Заказчика любых противоправных действий, включая, но не ограничиваясь этим, получение, копирование, использование, отправление, опубликование, передача, воспроизведение, распространение любой охраняемой законодательством РФ информации, данных и сведений любым способом, а также любые действия, в том числе использование, копирование и распространение, в отношении программного обеспечения и/или других охраняемых объектов интеллектуальной собственности, материалов, информации, сведений, в том числе полученных посредством Услуг, если Заказчиком не было получено предварительное письменное согласие правообладателя на указанные действия, и которые нарушают авторские, исключительные или любые другие права правообладателя;
Распространения Заказчиком и/или публикации любой информации, которая противоречит требованиям действующего законодательства Российской Федерации, положениям лицензий Министерства связи РФ (Министерства информационных технологий и связи в РФ), нормам международного права, и ущемляет права третьих лиц;
Опубликования, копирования или распространения Заказчиком любой информации или программного обеспечения, которое содержит в себе коды компьютерных вирусов или других компонентов, приравненных к ним;
Если Исполнитель получает соответствующее законное указание о прекращении оказания Услуг со стороны какого-либо государственного регулирующего или иного компетентного органа до момента отмены указания;
Если Исполнитель получает указание со стороны ответственного представителя Заказчика о приостановлении оказания Услуг. Услуги приостанавливаются до момента, обозначенного в указании, либо до его отмены ответственным представителем Заказчика.
## В случае возникновения событий, указанных в п.8.1.1, 8.1.2, 8.1.4, 8.1.11 настоящего Договора, Исполнитель письменно уведомляет Заказчика не позднее, чем за 5 (пять) Рабочих дней до приостановления Услуг с обоснованием причин приостановления оказания Услуг;
В случае возникновения событий, указанных в п. 8.1.2., 8.1.5, 8.1.7, 8.1.8 настоящего Договора, Исполнитель одновременно с приостановкой оказания Услуг Заказчику письменно уведомляет Заказчика о необходимости устранения соответствующих нарушений. Заказчик обязуется в течение 24 часов после получения письменного уведомления Исполнителя устранить эти нарушения, если иной срок не указан в приложениях к настоящему договору. Исполнитель возобновляет оказание Услуг Заказчику только после устранения Заказчиком нарушений.
В случае возникновения событий, указанных в п.п.8.1.10-8.1.11 настоящего Договора, Исполнитель в течение 5 (пяти) рабочих дней с даты приостановления оказания Услуг письменно уведомляет об этом Заказчика с обоснованием причин приостановления оказания Услуг и приложением подтверждающих это документов.
## Время приостановления оказания Услуг по основаниям, указанным в п.8.1 Договора, не считается перерывом в оказании Услуг и не может рассматриваться как нарушение Исполнителем своих обязательств, предусмотренных настоящим Договором и Приложениями к нему.
## Приостановление оказания Услуг и/или отключение программных или аппаратных средств Заказчика осуществляется до выполнения Заказчиком требований Исполнителя по устранению нарушений, указанных в п. 8.1. Приостановление оказания Услуг не отменяет выполнения Заказчиком всех своих обязательств по настоящему Договору, включая обязательство по ежемесячным платежам.
## Заказчик обязуется компенсировать все документально обоснованные расходы и затраты, понесенные Исполнителем в результате приостановления и возобновления оказания Услуг, указанных в п.8.1.
8.6. В случае приостановления оказания услуг или расторжения договора наличие и хранение резервных копии информационной системы (базы данных, программного обеспечения и пр. элементов) осуществляется силами и за счет Заказчика. Заказчик самостоятельно определяет перечень, обеспечивает сбор и перенос файлов для резервного хранения в течение 10 (десяти) календарных дней с момента получения уведомления о приостановлении услуг или расторжения договора.
# ОТВЕТСТВЕННОСТЬ СТОРОН
## Общие положения
За невыполнение или ненадлежащее выполнение своих обязательств по настоящему Договору Стороны несут ответственность в соответствии с действующим законодательством РФ, настоящим Договором и Приложениями к настоящему Договору.
Каждая из Сторон является единственно ответственной за соблюдение лицензионного законодательства в отношении используемых ее сотрудниками аппаратных и программных средств.
Ни одна из Сторон ни при каких обстоятельствах не несет ответственности перед другой Стороной за «косвенные» убытки (упущенную выгоду), которые включают, но не ограничиваются, потерю дохода, прибыли, ожидаемой экономии, деловой активности или репутации.
Заказчик и Исполнитель отвечают друг перед другом за правильность, достоверность и полноту передаваемых ими друг другу технических требований, документов, сведений и иной информации. В случае использования одной из Сторон недостоверной или неполной информации, полученной от другой Стороны, Сторона, получившая информацию, не несет ответственности за последствия, вызванные ее действиями на основании предоставленной недостоверной информации.
В случае просрочки в оплате Услуг Исполнитель имеет право потребовать от Заказчика уплаты пени в размере 0,1% (ноль целых одной десятой) процента от неоплаченной суммы за каждый день просрочки, но не более 10% (десяти) процентов от просроченного платежа.
В случае просрочки оказания любых Услуг, предусмотренных настоящим Договором, Заказчик имеет право потребовать от Исполнителя уплаты пени в размере 0,1% (ноль целых одной десятой) процента от не выполненных в срок Услуг за каждый день просрочки, но не более 10% (десяти) процентов от не выполненных в срок Услуг.
Максимальный размер ответственности Исполнителя за ненадлежащее оказание Услуг по настоящему Договору устанавливается в размере суммы, фактически уплаченной Заказчиком за оказание соответствующих Услуг по Договору в течение Отчетного периода, в котором данное ненадлежащее исполнение допущено. Конкретная сумма, подлежащая уплате Заказчику, в случае нарушения условий предоставления Услуг Исполнителем определяется согласно схеме компенсации, указанной в пункте 2.3 Соглашения об уровне обслуживания (Приложение №2 к настоящему Договору).
Заказчик имеет право на компенсацию в размере и порядке, установленном в пункте 2.3 Соглашения об уровне обслуживания.
Уплата штрафных санкций не освобождает Стороны от выполнения обязательств по настоящему Договору.
Каждая из Сторон Договора обязуется в течение срока действия настоящего Договора, а также в течение года с момента его расторжения/прекращения, воздерживаться от осуществления действий, направленных на переманивание работников другой Стороны.
## Семёнов_0001 несет ответственность перед третьими лицами в случае предъявления любых претензий и исков такими третьими лицами, заключившими договоры с Заказчиком на оказание услуг, которые частично или полностью оказываются Заказчиком с помощью Услуг Исполнителя.
Заказчик несет ответственность за соблюдение прав Исполнителя и третьих лиц на интеллектуальную собственность, исключительных прав, патентных, авторских, смежных или иных охраняемых законом прав. В случае, если к Исполнителю по вине Заказчика будут предъявлены со стороны третьих лиц какие-либо претензии, вытекающие из нарушения Заказчиком прав на интеллектуальную собственность, исключительных прав, патентных, авторских, смежных или иных охраняемых законом прав, Заказчик обязуется принять на себя эти претензии и возместить Исполнителю все убытки и расходы, понесенные Исполнителем в связи с нарушением таких прав, и за свой счет и на свой риск незамедлительно принять меры к урегулированию заявленных претензий.
Заказчик полностью ответственен за сохранность своих учетных данных и за убытки или иной ущерб, которые могут возникнуть по причине несанкционированного использования этой информации. По факту утери, несанкционированного доступа к учетным данным или возможности возникновения такой ситуации Заказчик имеет право направить запрос Исполнителю на смену учетных данных. В этом случае Исполнитель по согласованию с Заказчиком осуществляет блокировку доступа к учетным данным Заказчика. Срок подобной блокировки согласовывается Сторонами дополнительно. При этом Исполнитель не несет ответственности за извещение любых третьих лиц о временной приостановке доступа Заказчика к его информационной системе и за возможные последствия, возникшие в результате отсутствия такого извещения.
Заказчик несет ответственность за поддержание в актуальном состоянии списка его представителей, имеющих право обращаться в службу поддержки Исполнителя с запросами на изменение параметров Услуг.
Заказчик несет ответственность за убытки, причиненные Исполнителю в результате повреждения и/или утраты оборудования Исполнителя и третьих лиц.
Заказчик несет ответственность за поддержание в актуальном состоянии списка его представителей, допущенных на Технологическую площадку и к размещенному на ней оборудованию Заказчика.
Заказчик несет ответственность за соответствие параметров своего размещаемого оборудования эксплуатационным требованиям и нормам, принятым на Технологической площадке Исполнителя. Любые изменения к требованиям и нормам на Технологической площадке Исполнителя должны быть сообщены Заказчику письменно не позднее, чем за тридцать календарных дней до их введения в действие.
Заказчик обязуется возместить Исполнителю убытки, причиненные свойствами переданного оборудования, если Исполнитель, размещая оборудование Заказчика, не был письменно проинформирован об этих свойствах.
На основании дополнительного письменного запроса Исполнитель предоставляет сотрудникам Заказчика, чьи фамилия, имя, отчество, должность и иные сведения, предоставленные Исполнителю по его требованию, указаны Заказчиком в запросе, допуск для обеспечения круглосуточного контролируемого физического доступа на Технологическую площадку в соответствии с Регламентом взаимодействия Сторон в процессе оказания услуг (Приложение №3 к настоящему Договору). Заказчик несет ответственность за безопасность и надлежащее использование пропусков на проход представителей Заказчика на территорию Технологической площадки. Пропуск не может быть передан третьим лицам. Исполнитель не несет ответственность за убытки, понесенные Заказчиком в результате потери пропусков и разглашения иных сведений по вине Заказчика.
Заказчик несет ответственность за убытки, причиненные Исполнителю в результате повреждения и/или утраты оборудования Исполнителя и третьих лиц, разместивших свое оборудование на Технологической площадке, которые произошли в результате действий или бездействий представителей Заказчика и/или привлекаемой Заказчиком третьей стороны).
Заказчик гарантирует, что не является участником деятельности, связанной с криптовалютным майнингом (включая, но не ограничиваясь, майнингом биткойнов, эфириума и иных криптовалют), а также не планирует использовать предоставляемые Исполнителем услуги, оборудование, вычислительные мощности, энергоресурсы или инфраструктуру центра обработки данных для осуществления майнинга или иных операций, связанных с генерацией криптовалют.
Заказчик берет на себя обязательство:
До заключения настоящего Договора письменно уведомить Исполнителя о любых обстоятельствах, прямо или косвенно связанных с криптовалютным майнингом, если такие обстоятельства могут повлиять на исполнение обязательств по Договору.
Незамедлительно информировать Исполнителя о возникновении в течение срока действия Договора намерения использовать услуги в целях, указанных в п. 9.2.11 настоящего Договора.
## Кузнецов_0001 не контролирует содержание информации, хранимой, публикуемой или распространяемой Заказчиком с использованием предоставленных Услуг, и не несет никакой ответственности за точность, качество и содержание такой информации.
Исполнитель не несет ответственность за нарушение Заказчиком прав третьих лиц, включая, но не ограничиваясь этим, нарушение Заказчиком прав на интеллектуальную собственность, исключительных прав, патентных, авторских, смежных или иных охраняемых законом прав, в том числе за нарушение, совершенное с использованием Услуг, предоставляемых Исполнителем.
Исполнитель не несет ответственность за сбои в телекоммуникационных сетях, не находящихся под контролем Исполнителя в соответствии с Соглашением об уровне обслуживания.
Исполнитель не несет ответственность за последствия изменений в размещаемом оборудовании и установленном на нем программном обеспечении, не согласованных с Исполнителем, и не гарантирует работоспособность ПАК Заказчика в случае внесения таких изменений Заказчиком.
Исполнитель не является ответчиком или соответчиком по любым обязательствам и расходам, связанным с нарушением положений настоящего Договора Заказчиком или третьими лицами, использующими учетные данные Заказчика, а также связанным с использованием Заказчиком Интернет посредством Услуг Исполнителя; или связанным с помещением или передачей любого сообщения, информации, программного обеспечения или других материалов в сети Интернет Заказчиком или другими лицами, использующими его учетные данные.
Исполнитель несет ответственность перед Заказчиком в размере, оговоренном в п.2.3. Соглашения об уровне обслуживания.
Исполнитель несет ответственность перед Заказчиком в размере документально подтвержденного ущерба, причиненного Заказчику в результате поломки, утраты или повреждения размещаемого оборудования Заказчика, принятого в эксплуатацию по условиям настоящего Договора, в соответствии с п.9.4 настоящего Договора, а также невозможности работы оборудования ПАК Заказчика по вине Исполнителя в результате:
Нарушения режимов электропитания;
Нарушения допустимых параметров в помещении;
Повреждений, нанесенных персоналом Исполнителя или представителями третьей стороны, привлекаемой Исполнителем для оказания Услуг по настоящему Договору в результате халатных и/или умышленных действий;
Повреждений, нанесенных иными лицами, не являющимися представителями Заказчика и/или привлекаемой Заказчиком третьей стороны, допущенными на Технологическую площадку с получением доступа от Исполнителя, или проникших на Технологическую площадку в результате халатности Исполнителя и/или несоблюдении им Регламента взаимодействия Сторон в процессе оказания Услуг (Приложение №3 к настоящему Договору).
Исполнитель не несет ответственность за работоспособность оборудования Заказчика, не принятого в эксплуатацию в соответствии с условиями настоящего Договора.
Исполнитель не несет ответственность за утрату или повреждение оборудования Заказчика, в случаях, когда утрата или повреждение произошли из-за свойств оборудования Заказчика, о которых Исполнитель, размещая его на Технологической площадке, не был письменно (под роспись) предупрежден Заказчиком.
Исполнитель несет ответственность в соответствии с п.9.4 настоящего Договора за повреждение оборудования Заказчика, произошедшее как в результате халатных и/или умышленных действий сотрудников Исполнителя и/или уполномоченных им третьих лиц, так и в результате нарушения Исполнителем технических условий и правил технической эксплуатации данного оборудования.
## Ответственность сторон при утрате и повреждении оборудования
Исполнитель несет ответственность при:
утрате оборудования Заказчика в размере его рублевой балансовой стоимости, приведенной в Акте приема-передачи оборудования (Приложение №5 к настоящему Договору), с учетом норм амортизации (износа), зафиксированных в Акте убытия ОС;
повреждении оборудования – в размере документально подтвержденной стоимости восстановительного ремонта поврежденного оборудования, включая заменяемые в процессе ремонта поврежденные составляющие оборудования до состояния, соответствующего первоначальному. Стоимость подлежащих замене составляющих определяется на основании балансовой стоимости таких составляющих, указанных в Акте приема-передачи оборудования (Приложение №5 к настоящему Договору).
Любая из Сторон в случае утраты или повреждения оборудования обязана немедленно (в течение суток с момента наступления такого случая) письменно известить об этом другую Сторону.
В случае отсутствия спора Сторон о причинах утраты оборудования Исполнитель обязан в течение 15 (пятнадцати) рабочих дней с момента получения требования от Заказчика перечислить Заказчику рублевую балансовую стоимость оборудования, которая указана в Акте приема-передачи оборудования, с учетом норм амортизации (износа), зафиксированных в Акте убытия ОС, на счет Заказчика.
В случае отсутствия спора Сторон о причинах повреждения оборудования Заказчика и признания Исполнителем своей ответственности за повреждение оборудования Заказчика сумма восстановительного ремонта определяется путем переговоров между Исполнителем и Заказчиком, результат которых оформляется в виде Акта о повреждении (утрате) оборудования Заказчика (форма акта в Приложении №5 к настоящему Договору), в котором фиксируется факт повреждения оборудования, время и причины повреждения оборудования, а также согласованная Сторонами стоимость его восстановительного ремонта. Исполнитель обязан в течение пяти рабочих дней с даты подписания обеими Сторонами Акта о повреждении (утрате) оборудования Заказчика перечислить Заказчику рублевую стоимость восстановительного ремонта оборудования, указанную в данном Акте, на счет Заказчика. В случае спора Сторон о стоимости восстановительного ремонта и отказа одной из Сторон от подписания Акта о повреждении (утрате) оборудования Стороны решают спор в порядке, предусмотренном условиями пп.9.4.5-9.4.7 настоящего Договора.
В случае спора Сторон о причинах утраты или повреждения оборудования, а также о стоимости восстановительного ремонта, Заказчик имеет право проведения полной технической экспертизы технического состояния поврежденного оборудования. При этом техническая экспертиза должна быть проведена либо изготовителем данного оборудования, либо иной организацией, уполномоченной на осуществление данных действий изготовителем поврежденного оборудования, с участием привлеченной Исполнителем организации, компетентной и уполномоченной на проведение технической экспертизы такого оборудования. Этим же Актом технической экспертизы технического состояния поврежденного оборудования должна быть установлена стоимость его восстановительного ремонта, если таковой может быть произведен, и рублевая балансовая стоимость, приведенная в Акте приема-передачи оборудования, с учетом норм амортизации (износа), зафиксированных в Акте убытия ОС.
В случае отказа Исполнителя от проведения технической экспертизы в течение 10 (десяти) рабочих дней с момента повреждения оборудования, Заказчик имеет право провести техническую экспертизу в одностороннем порядке. В данном случае Исполнитель обязан не препятствовать доступу специалистов, осуществляющих техническую экспертизу, к поврежденному оборудованию.
Если по результатам технической экспертизы виновным за утрату или повреждение оборудования Заказчика будет признан Исполнитель, Заказчик обязан в течение 5 (пяти) рабочих дней с даты получения Акта технической экспертизы передать один экземпляр оригинала данного Акта Исполнителю. В случае согласия Исполнителя с результатами технической экспертизы он обязан в течение 5 (пяти) рабочих дней с даты получения им оригинала Акта технической экспертизы перечислить Заказчику рублевую стоимость:
восстановительного ремонта оборудования, указанную в Акте технической экспертизы, в случае если такой ремонт может быть произведен, либо
рублевую балансовую стоимость поврежденного оборудования, указанную в Акте приема-передачи оборудования, с учетом норм амортизации (износа), зафиксированных в Акте убытия ОС, в случае, если проведение восстановительного ремонта не представляется возможным, на счет Заказчика в соответствии с реквизитами, указанными в разделе 18 настоящего Договора. При этом Исполнитель обязан компенсировать Заказчику все документально подтвержденные затраты на проведение технической экспертизы.
В случае несогласия Исполнителя с результатами технической экспертизы он обязан в течение пяти рабочих дней с даты получения Акта технической экспертизы письменно известить об этом Заказчика, давая основание Заказчику перенести данный спор на рассмотрение в суд.
При судебном разбирательстве Заказчик вправе потребовать от Исполнителя не только стоимость поврежденного оборудования, но и компенсацию документально подтвержденных расходов по проведению технической экспертизы.
Заказчик несет ответственность за нарушение работоспособности, утрату или повреждение оборудования, размещенного на Технологической площадке Исполнителя и не принадлежащего Заказчику, нанесенных представителями Заказчика и/или, привлекаемой Заказчиком третьей стороной, допущенными на Технологическую площадку с получением доступа от Исполнителя по запросу Заказчика в соответствии с Регламентом взаимодействия Сторон в процессе оказания Услуг.
За утрату или частичное повреждение размещаемого на Технологической площадке оборудования ПАК Заказчика после того, как наступила обязанность Заказчика вывезти оборудование, Исполнитель несет ответственность лишь при наличии с его стороны умысла или грубой неосторожности.
Ответственность за порчу или утрату предоставленных Исполнителем в пользование Заказчику оборудования и технических средств несет Заказчик, кроме случаев, если будет доказана вина Исполнителя или привлекаемой им в процессе оказания Услуг третьей стороны. В случае возникновения спора между Заказчиком и Исполнителем о причинах утраты, повреждения оборудования и технических средств, а также стоимости восстановительного ремонта, Стороны вправе провести техническую экспертизу в порядке, предусмотренном пп. 9.4.5-9.4.7 настоящего Договора. Инициатором проведения экспертизы является Исполнитель. В случае признания вины Заказчика в повреждении или утрате оборудования или технических средств, Заказчик обязуется компенсировать Исполнителю документально подтвержденные затраты на проведение технической экспертизы в течение 5 (пяти) календарных дней с момента предоставления Исполнителем копий документов, подтверждающих понесенные на экспертизу затраты.
В случае несогласия Заказчика с результатами технической экспертизы он обязан в течение пяти рабочих дней с даты получения Акта технической экспертизы письменно известить об этом Исполнителя, давая основание Исполнителю перенести данный спор на рассмотрение в суд.
# ФОРС-МАЖОРНЫЕ ОБСТОЯТЕЛЬСТВА
В случае возникновения форс-мажорных обстоятельств: стихийных бедствий, землетрясений, наводнений, пожаров, и других обстоятельств непреодолимой силы, Исполнитель и Заказчик освобождаются от ответственности за неисполнение или ненадлежащее исполнение обязательств по настоящему Договору. В течение трех календарных дней с момента наступления таких обстоятельств Сторона, пострадавшая от их влияния, письменно уведомляет другую Сторону о произошедших событиях.
Сторона, понесшая в связи с обстоятельствами непреодолимой силы убытки из-за неисполнения или приостановления другой Стороной исполнения своих обязательств, может потребовать от Стороны, ставшей объектом действия непреодолимой силы, документ, подтверждающий масштабы произошедших событий, а также об их влиянии на деятельность пострадавшей Стороны.
В случае возникновения форс-мажорных обстоятельств исполнение обязательств по настоящему Договору отодвигается на срок действия таких обстоятельств.
В случае возникновения обстоятельств непреодолимой силы, продолжающихся более трех месяцев, Заказчик или Исполнитель вправе расторгнуть настоящий Договор после взаиморасчетов по фактически выполненным обязательствам.
# РАЗРЕШЕНИЕ СПОРОВ
Споры и разногласия, возникающие по настоящему Договору или в связи с ним, решаются Сторонами прежде всего путем переговоров или заключением Дополнительных соглашений. Срок ответа на претензию – 10 (десять) рабочих дней с момента получения.
Все споры, разногласия или требования, возникающие из настоящего Договора или в связи с ним, в том числе касающиеся его исполнения, нарушения, прекращения или недействительности, подлежат разрешению в Арбитражном суде г. Москвы в соответствии с законодательством РФ.
# КОНФИДЕНЦИАЛЬНОСТЬ
Каждая из Сторон сохраняет в тайне всю Конфиденциальную информацию, раскрытую ей другой Стороной. Эта конфиденциальность распространяется, в том числе на любую конфиденциальную информацию, полученную Сторонами друг от друга устно, в письменном, электронном виде, а также в любой иной форме, в процессе оказания Услуг по настоящему Договору, и содержащую сведения об организационной структуре, документах и материалах по исполнению настоящего Договора, коммерческих аспектах настоящего Договора, о рыночных и технических планах развития и другие сведения, являющиеся исключительно Конфиденциальной информацией Сторон.
Стороны будут ответственны за несанкционированное разглашение и использование Конфиденциальной информации. Каждая Сторона обязуется соблюдать в отношении Конфиденциальной информации столь же высокую степень осторожности, какую она соблюдает в отношении своей собственной Конфиденциальной информации аналогичной степени важности.
Нарушением данного пункта не являются следующие случаи:
разглашение информации, на которое получено письменное разрешение другой Стороны;
разглашение информации по законному требованию компетентных государственных органов в соответствии с действующим законодательством РФ либо по решению компетентного суда;
разглашение информации регулирующему органу, эксперту или арбитру, назначенному в соответствии с положениями настоящего Договора, в той степени, в которой такое разглашение обязательно по закону;
разглашение информации финансовым или профессиональным советникам разглашающей Стороны при условии, что они берут на себя письменное обязательство по сохранению конфиденциальности данной информации.
Стороны обязуются использовать предоставленную друг другу Конфиденциальную информацию исключительно в интересах выполнения обязательств по настоящему Договору, а не во вред другой Стороне.
Конфиденциальность информации сохраняется обеими Сторонами после окончания срока действия или расторжения настоящего Договора в течение двадцати четырех календарных месяцев.
# ПРАВА НА ИНТЕЛЛЕКТУАЛЬНУЮ СОБСТВЕННОСТЬ
Ничто в настоящем Договоре не может быть интерпретировано как предоставление каких-либо прав на интеллектуальную собственность или как уступку прав на интеллектуальную собственность одной Стороны другой Стороне.
Каждая Сторона самостоятельно несет ответственность за любые убытки, ущерб, или расходы, понесенные ею в связи с нарушением прав третьих лиц на интеллектуальную собственность в ходе исполнения настоящего Договора.
# ПРОЧИЕ УСЛОВИЯ
Подписывая настоящий Договор, Заказчик подтверждает, что у него есть доступ в интернет и возможность отправлять и принимать электронную почту.
Передача любой из Сторон прав и обязанностей по настоящему Договору возможна только с предварительного письменного согласия другой Стороны, полученного не менее, чем за 30 (тридцать) календарных дней до даты передачи.
Исполнитель имеет право соответствующим образом изменить стоимость Услуг по настоящему Договору, в случае если будут приняты и вступят в силу изменения в законодательстве Российской Федерации: а) устанавливающие дополнительные налоги, сборы и прочие обязательные платежи/отчисления в отношении Услуг, оказываемых Исполнителем по настоящему Договору; б) устанавливающие обязанность Исполнителя по приобретению дополнительного оборудования, лицензий, разрешений, сертификатов, необходимых для оказания Услуг по настоящему Договору.
В случае если после заключения Договора будет официально опубликован (введен в действие) нормативный правовой акт, изменяющий ставку НДС и/или вводящий иные налоги и сборы, влияющие на стоимость Услуг, цена Договора подлежит пересчету с учетом вступивших в силу изменений.
Исполнитель, руководствуясь ст. 431.2 ГК РФ, заверяет Заказчика на момент подписания настоящего Договора и гарантирует в налоговых периодах, в течении которых совершаются операции по настоящему Договору, что уплачивает все налоги и сборы в соответствии с действующим законодательством, а также ведет и своевременно подает в налоговые и иные государственные органы налоговую, статистическую и иную отчетность.
Исполнитель обязуется по запросу Заказчика, составленному на основании требования / запроса налогового или иного государственного органа (с приложением копии такого требования/запроса или выдержки из такого требования/запроса), предоставить  надлежащим образом заверенные копии документов, относящихся к исполнению настоящего Договора, в срок, не превышающий 5 (пять) рабочих дней с момента получения соответствующего запроса от Заказчика, либо в течение иного согласованного Заказчиком и Исполнителем срока.
# СРОК ДЕЙСТВИЯ, ПОРЯДОК ИЗМЕНЕНИЯ И РАСТОРЖЕНИЯ ДОГОВОРА
Договор вступает в силу с момента его подписания Сторонами. Начальный период действия настоящего Договора составляет 12 (двенадцать) календарных месяцев с даты его подписания Сторонами.
Каждая из Сторон может прекратить действие настоящего Договора по истечении установленного Начального периода, представив об этом письменное уведомление не позднее, чем за 30 (тридцать) календарных дней до даты прекращения срока действия настоящего Договора. При отсутствии такового уведомления срок действия Договора ежегодно автоматически продлевается на следующие 12 (двенадцать) календарных месяцев. При этом действие Договора может быть прекращено по истечении каждого последующего годового периода письменным уведомлением не позднее, чем за 30 (тридцать) календарных дней до даты завершения годового периода.
Любая из Сторон имеет право в одностороннем внесудебном порядке досрочно расторгнуть Договор, но не ранее, чем спустя тридцать календарных дней после письменного уведомления другой Стороны о своих намерениях. С момента получения письменного уведомления другой Стороной и до момента расторжения Договора никакие изменения, уменьшающие или увеличивающие совокупную величину ежемесячных платежей, в Договор внесены быть не могут.
Если одна из Сторон допускает существенное нарушение настоящего Договора, которое не устраняется в течение 30 (тридцати) календарных дней после письменного требования об этом другой Стороны, то другая Сторона может немедленно расторгнуть Договор в одностороннем внесудебном порядке, направив нарушившей Стороне соответствующее письменное уведомление.
Исполнитель имеет право в одностороннем внесудебном порядке расторгнуть или изменить Договор, не неся за это никакой ответственности, если, по не зависящим от него причинам, Исполнитель не получит какой-либо лицензии или разрешения, которые необходимы ему для оказания Услуг. Или, если они будут отозваны или иным образом отменены/изменены так, что это повлияет на способность Исполнителя оказывать Услуги.
В случае просрочки платежей Заказчиком более чем на 30 (тридцать) рабочих дней, Исполнитель вправе в одностороннем внесудебном порядке расторгнуть Договор с направлением Заказчику письменного уведомления о расторжении не менее, чем за 10 (десять) рабочих дней до даты расторжения. Договор считается расторгнутым с даты, указанной в уведомлении о расторжении.
Любые положения настоящего Договора могут быть изменены, отменены или прекращены по инициативе любой из Сторон. Изменения, отмена или прекращение действия положений Договора должны быть оформлены в письменном виде как Дополнительное соглашение к Договору, подписаны обеими Сторонами, и являются неотъемлемой частью настоящего Договора.
Изменение требований к количеству и качеству предоставляемых Услуг, критериев и порядка оценки предоставляемых Услуг в обязательном порядке оформляются изменением существующих или разработкой новых Спецификаций.
Изменение действующих Спецификаций или разработка новых происходит путем подписания Сторонами Дополнительных соглашений к Договору, после чего действие старых Спецификаций прекращается, и может быть инициирована любой из Сторон. Составление и оформление Спецификаций осуществляет Исполнитель.
По окончании срока действия настоящего Договора отключение оборудования Заказчика от сетевой инфраструктуры Исполнителя и/или вывоз его с Технологической площадки осуществляется Сторонами в следующем порядке:
Исполнитель не позднее двух календарных дней производит отключение оборудования Заказчика от сетевой инфраструктуры Исполнителя;
демонтаж и вывоз оборудования Заказчика с Технологической площадки Исполнителя осуществляется Заказчиком самостоятельно в течение десяти календарных дней с даты окончания срока действия Договора, либо в иной согласованный Сторонами срок, оформленный в виде Дополнительного соглашения;
отключение от электропитания и демонтаж стойки Заказчика по окончании срока действия настоящего Договора осуществляет Исполнитель, стоимость соответствующих услуг Исполнителя определяется, исходя из стоимости работ по деинсталляции инфраструктуры стойко-места, указанной в Спецификации. Заказчик обязуется подписать Дополнительное Соглашение на эти услуги и оплатить такие услуги Исполнителя в течение 5 (пять) рабочих дней с даты подписания Сторонами УПД и получения счета Исполнителя, но не позднее срока действия Договора;
если Заказчик не оплатил стоимость Услуг Исполнителя по отключению от электропитания и демонтажу оборудования, или по истечении срока размещения оборудование Заказчика не было принято Заказчиком и не было вывезено Заказчиком в согласованный Сторонами срок, Исполнитель за свой счет складирует данное оборудование на условиях ответственного хранения на своей территории. В этом случае оборудование передается Заказчику только после оплаты Заказчиком расходов Исполнителя по отключению, демонтажу, складированию, хранению и приему-передаче оборудования. При этом стоимость Услуг Исполнителя по хранению оборудования определяется Дополнительном соглашении, исходя из расчета стоимости аренды склада на Технологической площадке Исполнителя. Заказчик обязуется оплатить такие услуги Исполнителя до момента передачи оборудования Заказчику. В момент возврата оборудования Кузнецов_0002 подписывается Акт приема-передачи оборудования с хранения в двух экземплярах.
Никакое изменение, исправление, добавление или отмена любых положений настоящего Договора не вступает в силу, пока не будет согласовано в письменной форме уполномоченными представителями Исполнителя и Заказчика.
Исполнитель вправе расторгнуть настоящий Договор в одностороннем порядке в случае отказа или уклонения Заказчика от подписания дополнительного соглашения об изменении цен по Договору (п.2.1.3) на условиях Исполнителя, направив Заказчику письменное уведомление не менее, чем за 10 (десять) рабочих дней до даты предполагаемого расторжения. В этом случае оплата оказанных Исполнителем услуг до даты расторжения осуществляется Заказчиком на прежних условиях.
# ЗАКЛЮЧЕНИЕ ДОГОВОРА
Договор и все Приложения, являющиеся его неотъемлемой частью, представляют собой полный Договор между Сторонами относительно изложенного в нем содержания.
Договор и все Приложения к нему полностью исчерпывают договоренность между Сторонами по вопросу оказания Услуг и отменяют все другие соглашения и заявления, сделанные в устной или письменной форме до момента подписания настоящего Договора.
Каждая Сторона признает, что ознакомилась с настоящим Договором, и что она надлежащим образом уполномочена подписать Договор, и согласна с его сроками и условиями.
Стороны договорились, что переписка по электронной почте между уполномоченными лицами, указанными в Приложении № 3 к Договору, может рассматриваться ими в качестве доказательства, в случае возникновения спора.
Документы, переданные по электронной почте, имеют юридическую силу оригинала и действуют до момента обмена Сторонами оригиналами документов.
В целях оптимизации документооборота между Сторонами, а также повышения уровня сохранения и защиты передаваемых документов и информации, содержащейся в них, Стороны договорились о внедрении с даты подписания Договора системы электронного документооборота с использованием электронной подписи и передачей документов и информации через оператора электронного документа оборота (далее - «Оператор»).
Стороны договорились осуществлять электронный документооборот следующими документами:
# Счетами на оплату;
# Актами сверки взаимных расчетов;
# Протоколами разногласий к акту сверки взаимных расчетов;
# Актами зачета взаимных требований;
# Заявлениями о проведении зачета;
# Электронными счетами фактурами (формат УПД(СЧФ);
# Универсальными передаточными документами (формат УПД(СЧФДОП);
# Актами об оказании услуг (акты оказанных услуг);
# Актами выполненных работ (акты о передаче результатов работ);
# Товарными накладными по форме ТОРГ-12;
# Товарными накладными;
# Приложениями к Договору;
Документы считаются надлежаще полученными другой Стороной в случае:
направления документов на бумажном носителе – в момент фактической доставки (выдачи) корреспонденции представителю получателя или проставления штемпеля почтового отделения получателя о прибытии корреспонденции в его адрес, если обязанности по получению им корреспонденции не были исполнены;
направления документов по электронной почте уполномоченным лицам, указанным в Приложении № 3 к Договору – в рабочий день отправления сообщения с приложением документов, содержащих все надлежащие реквизиты, позволяющие установить, что документ исходит от стороны по договору и подписан уполномоченным лицом;
направления документов через согласованного Сторонами оператора путем использования системы электронного документооборота (ЭДО) – в день направления документа и информации Оператору направившей его Стороны. Порядок использования Сторонами ЭДО определяется дополнительным соглашением к Договору.
Договор заключен в двух экземплярах, обладающих равной юридической силой, по одному экземпляру для каждой Стороны.
# ПЕРЕЧЕНЬ ПРИЛОЖЕНИЙ К ДОГОВОРУ
# Договор включает следующие приложения, являющиеся его неотъемлемой частью:
# Приложение №1. Спецификация и стоимость Услуг.
# Приложение №2. Соглашение об уровне обслуживания.
# Приложение №3. Регламент взаимодействия Сторон в процессе оказания Услуг.
# Приложение №4. Технические условия.
# Приложение №5. Акт начала оказания услуг
# ЮРИДИЧЕСКИЕ АДРЕСА И РЕКВИЗИТЫ СТОРОН
| г. Москва | 01.02.2026г. |
|---|---|
| Заказчик Меридиан_0001 Юридический адрес: Генеральный директор _______________ /ФИО/ М.П. | Исполнитель Альянс_0001 Юридический адрес: 115404, г. Москва, Вн. Тер. Г. Муниципальный округ Бирюлево Восточное, ул. 1-я Стекольная, д.7 стр. 2 Почтовый адрес: 115404, г. Москва, ул. 1-я Стекольная, д.7с2 ИНН_0001, КПП_0001 ОГРН_0001 РС_0001 Прогресс_0001, г. Москва КС_0001 БИК: 044525225 e-mail email_0001 Web-site www.nubes.ru Тел. +7_000_000_0001 Генеральный директор _______________ /В. С. Степаненко/ М.П. |
|---|---|
Binary file not shown.
+57
View File
@@ -0,0 +1,57 @@
**Дополнительное Соглашение Договор_0001**
к Договору Договор_0003 от 20.12.2025
г. Москва «27» февраля 2026
**Альянс_0001** именуемое в дальнейшем "Заказчик", в лице Директора ФИО, действующего на основании Устава, и **ООО **"**НУБЕС**", именуемое в дальнейшем "Исполнитель", в лице Генерального директора Фёдоров_0001, действующего на основании Устава, с другой стороны, заключили настоящее Дополнительное соглашение Договор_0002 (далее - Соглашение) к Договору Договор_0005 от 29.12.2025 (далее - Договор) о нижеследующем:
**1. ПРЕДМЕТ ДОПОЛНИТЕЛЬНОГО СОГЛАШЕНИЯ**
Руководствуясь п. 15.7, п.15.9 Договора, Стороны договорились о нижеследующем:
Изменить Спецификацию и стоимость Услуг (Приложение Договор_0002 к Договору). Утвердить новую Спецификацию и стоимость Услуг в соответствии с Приложением Договор_0002 к настоящему Соглашению.
Датой начала оказания Услуг по Договору считать даты, указанные в Приложении № 1 к Соглашению.
**ПРОЧИЕ УСЛОВИЯ**
2.1. Во всём остальном, что не урегулировано настоящим Соглашением, действуют положения Договора.
**СРОК ДЕЙСТВИЯ**
Настоящее Соглашение подписано в двух экземплярах, обладающих равной юридической силой, по одному для каждой Стороны, вступает в силу с момента его подписания обеими Сторонами и является неотъемлемой частью Договора.
**4.** **ПЕРЕЧЕНЬ ПРИЛОЖЕНИЙ К СОГЛАШЕНИЮ**
Приложение № 1. Спецификация и стоимость услуг
**ЮРИДИЧЕСКИЕ АДРЕСА И РЕКВИЗИТЫ СТОРОН**
**Приложение Договор_0001**
к Дополнительному Соглашению Договор_0002 от «27» февраля 2026
к Договору Договор_0004 от 20.12.2025. на оказание технологических услуг
**Спецификация и стоимость услуг**
### Состав и стоимость Услуг
Разовые услуги
Общая стоимость Инсталляционных услуг составляет **8 283,80 руб. **(восемь тысяч двести восемьдесят три рубля, 80 копеек) с учетом НДС 22%.
Абонентские услуги
Общая стоимость Ежемесячных услуг составляет **137 880,96 руб**. (сто тридцать семь тысяч восемьсот восемьдесят рублей, 96 копеек) с учетом НДС 22%.
| Заказчик Сфера_0001 Юридический адрес: Почтовый адрес: ИНН , КПП ОГРН Р/с Кор/сч БИК: Тел. , e-mail Директор _______________ФИО М.П. | Исполнитель Проект_0001 Юридический адрес: Новосибирск_0001 Почтовый адрес: Красноярск_0001 ИНН_0001, КПП_0001 ОГРН_0001 РС_0001 Меридиан_0001, г. Москва КС_0001 БИК: БИК_0001 e-mail email_0001 Web-site www.nubes.ru Тел. +7_000_000_0001 Генеральный директор _______________ / Волков_0001/ М.П. |
|---|---|
| № п/п | Наименование услуг | Цена, руб. с НДС | Объем услуг | Сумма, руб. с НДС | Дата начала оказания услуг |
|---|---|---|---|---|---|
| 1 | Организация L2 канала без резерва, в составе: Скорость порта: 1 Гбит/с - 1 шт. Период тарификации - месяц | 8 283,80 | 1 | 8 283,80 | 26.03.2026 |
| Итого инсталляционный платеж в руб с учетом НДС | Итого инсталляционный платеж в руб с учетом НДС | Итого инсталляционный платеж в руб с учетом НДС | Итого инсталляционный платеж в руб с учетом НДС | 8 283,80 | - |
| № п/п | Наименование услуг | Цена, руб. с НДС | Объем услуг | Сумма, руб. с НДС | Дата начала оказания услуг | Дата окончания оказания услуг |
|---|---|---|---|---|---|---|
| 1 | Аренда порта без резерва, в составе: Скорость порта: 1 Гбит/с - 1 шт. Период тарификации - месяц | 1 525,00 | 1 | 1 525,00 | 26.03.2026 | |
| 2 | Аренда публичной подсети (Длина префикса: /30 - 1 IPv4 адрес) | 589,66 | 1 | 589,66 | 26.03.2026 | |
| 3 | Интернет без защиты от DDoS, в составе: Полоса Интернет: тариф от 5 до 100 Мбит/с - 100 Mбит/с Период тарификации - месяц | 7 015,00 | 1 | 7 015,00 | 26.03.2026 | |
| 4 | Next Generation Cloud (AMD 4.0 ГГц), в составе: Количество vCPU - 62 шт. Период тарификации - месяц Объем vRAM - 480 ГБайт Период тарификации - месяц Дисковое пространство: SATA - 720 ГБайт Период тарификации - месяц Дисковое пространство: Fast SSD - 6838 ГБайт Период тарификации - месяц | 123 823,00 | 1 | 123 823,00 | 26.03.2026 | |
| 5 | Next Generation Cloud (AMD 4.0 ГГц), в составе: Количество vCPU - 10 шт. Объем vRAM - 10 ГБайт Дисковое пространство: Fast SSD - 100 ГБайт | 4 928,30 | 1 | 4 928,30 | 29.01.2026 | |
| Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | 137 880,96 | - | |
| Заказчик Директор Сфера_0001 _______________/ФИО/ М.П. | Исполнитель Генеральный директор Комплекс_0001 _______________ /Волков_0001/ М.П. |
|---|---|
@@ -0,0 +1,26 @@
# Общие коды поступающих: Сеченовский Университет ↔ ФИЯР МГУ
**Сравнение списков (магистратура, 45.04.02 Лингвистика):**
- **Сеченов:** очная, бюджет (10 мест) — источник priem.sechenov.ru, сбор 04.08.2026
- **МГУ ФИЯР:** очная, бюджет/КЦП (32 места), ОП «Лингвистика» — источник cpk.msu.ru, сбор 04.08.2026
**Совпадений: 16**
| Код | Сеченов (очная, бюджет) | ФИЯР МГУ (очная, КЦП) |
|---|---|---|
| 977854 | №68, 0 б. — Подал заявление | №11 — На рассмотрении |
| 1133145 | №69, 0 б. — Подал заявление | №56 — На рассмотрении |
| 1627565 | №30, 10 б. — Отозвано поступающим | №109 — На рассмотрении |
| 1716861 | №64, 0 б. — Подал заявление | №124 — На рассмотрении |
| 1776417 | №24, 10 б. — Подал заявление | №133 — На рассмотрении |
| 1886500 | №25, 10 б. — Подал заявление | №153 — На рассмотрении |
| 1948299 | №46, 0 б. — Подал заявление | №164 — На рассмотрении |
| 1959962 | №12, 10 б. — Подал заявление | №169 — На рассмотрении |
| 1991322 | №21, 10 б. — Подал заявление | №183 — Конкурсная группа исключена |
| 2050887 | №42, 0 б. — Подал заявление | №206 — На рассмотрении |
| 2089584 | №55, 0 б. — Подал заявление | №221 — На рассмотрении |
| 2141189 | №56, 0 б. — Подал заявление | №249 — На рассмотрении |
| 2264475 | №27, 10 б. — Отозвано поступающим | №288 — На рассмотрении |
| 2301501 | №45, 0 б. — Подал заявление | №300 — На рассмотрении |
| 2325881 | №26, 10 б. — Подал заявление | №310 — На рассмотрении |
| 2393823 | №60, 0 б. — Подал заявление | №348 — На рассмотрении |
@@ -0,0 +1,28 @@
# Общие коды: Сеченов + МГУ ФИЯР + МГЛУ (магистратура, 45.04.02 Лингвистика, очная, бюджет)
# ФИЛЬТР: в индивидуальных достижениях ТОЛЬКО красный диплом
**Сравниваемые списки (сбор 04.08.2026):**
1. **Сеченов** — очная, бюджет (10 мест), priem.sechenov.ru
2. **МГУ ФИЯР** — очная, бюджет/КЦП (32 места), cpk.msu.ru
3. **МГЛУ** — очная, бюджет, общий конкурс (3 группы: 70+84+10 мест), linguanet.ru
**Критерий:**
- код есть во всех 3 вузах;
- есть баллы за красный диплом (Сеченов/МГУ — 10 б., МГЛУ — 5 б.);
- ИД состоит ТОЛЬКО из красного диплома (нет НП/ПП/сертификатов).
## Результат: 3 человека
| Код | Сеченов | МГУ ФИЯР | МГЛУ |
|---|---|---|---|
| 1627565 | №30 · 10 б. (только красн.) · **Отозвано** | №109 · На рассмотрении | Гр.1 №217, Гр.2 №260 · 5 б. (только Отл) |
| 1776417 | №24 · 10 б. (только красн.) · Подал заявление | №133 · На рассмотрении | Гр.1 №152, Гр.2 №180 · 5 б. (только Отл) |
| 1886500 | №25 · 10 б. (только красн.) · Подал заявление | №153 · На рассмотрении | Гр.1 №191, Гр.2 №229 · 5 б. (только Отл) |
## Отсеяны (есть и другие достижения, кроме красного диплома)
| Код | Достижения в МГЛУ |
|---|---|
| 1959962 | Отл 5 + ПП 3 + НП 4 (10) |
| 1991322 | Отл 5 + НП 4 (9) |
| 2264475 | Отл 5 + ПП 3 (8) |
| 2325881 | Отл 5 + НП 4 (9) |
@@ -0,0 +1,52 @@
Приложение № 1
к Договору на оказание технологических услуг № 03700_1 от 01.02.2026 г.
**Спецификация и стоимость услуг**
### Состав и стоимость Услуг
Абонентские услуги с 01.04.2026 – 24.04.2026
Общая стоимость Ежемесячных услуг составляет **769 279,53 руб**. (семьсот шестьдесят девять тысяч двести семьдесят девять рублей, 53 копейки) с учетом НДС 22%.
Абонентские услуги с 25.04.2026
Общая стоимость Ежемесячных услуг составляет **877 563,21 руб**. (восемьсот семьдесят семь тысяч пятьсот шестьдесят три рубля, 21 копейка) с учетом НДС 22%.
| г. Москва | 01.02.2026г. |
|---|---|
| № п/п | Наименование услуг | Цена, руб. с НДС | Объем услуг | Сумма, руб. с НДС | Планируемая дата начала оказания услуг |
|---|---|---|---|---|---|
| 1 | Аренда стойко-места, в составе: Номинальная мощность – 10 кВт на каждую стойку 2H1Q010 2H1K080 2H1I030 Размер стойки: 600х1200 - 3 шт. | 213 905,44 | 3 | 641 716,32 | 01.04.2026 |
| 2 | Аренда PDU: 3Ф 32А вертикальный | 2 511,17 | 3 | 7 533,51 | 01.04.2026 |
| 3 | Аренда серверного шкафа: 42Ux600x1200 | 2 440,00 | 3 | 7 320,00 | 01.04.2026 |
| 4 | Аренда порта без резерва, в составе: Скорость порта: 1 Гбит/с - 3 шт. | 4 575,00 | 1 | 4 575,00 | 01.04.2026 |
| 5 | Предоставление 1 ОВ в кабеле ВОЛС: НУБЕС-М9 От Красноярск_0001 До Нижний_Новгород_0001 | 18 300,00 | 4 | 73 200,00 | 01.04.2026 |
| 6 | Аренда публичной подсети: Длина префикса: /29 - 5 IPv4 адресов | 884,50 | 1 | 884,50 | 01.04.2026 |
| 7 | Next Generation Cloud: AMD 2.8 ГГц, в составе: Количество vCPU: гарантированная доля 80% - 32 шт. Объем vRAM - 96 ГБайт Дисковое пространство: SSD - 2000 ГБайт | 34 050,20 | 1 | 34 050,20 | 01.04.2026 |
| Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | 769 279,53 | - |
| № п/п | Наименование услуг | Цена, руб. с НДС | Объем услуг | Сумма, руб. с НДС | Планируемая дата начала оказания услуг |
|---|---|---|---|---|---|
| 1 | Аренда стойко-места, в составе: Номинальная мощность – 10 кВт на каждую стойку 2H1Q010 2H1K080 2H1I030 Размер стойки: 600х1200 - 3 шт. | 250 000,00 | 3 | 750 000,00 | 25.04.2026 |
| 2 | Аренда PDU: 3Ф 32А вертикальный | 2 511,17 | 3 | 7 533,51 | 01.04.2026 |
| 3 | Аренда серверного шкафа: 42Ux600x1200 | 2 440,00 | 3 | 7 320,00 | 01.04.2026 |
| 4 | Аренда порта без резерва, в составе: Скорость порта: 1 Гбит/с - 3 шт. | 4 575,00 | 1 | 4 575,00 | 01.04.2026 |
| 5 | Предоставление 1 ОВ в кабеле ВОЛС: НУБЕС-М9 От Красноярск_0001 До Нижний_Новгород_0001 | 18 300,00 | 4 | 73 200,00 | 01.04.2026 |
| 6 | Аренда публичной подсети: Длина префикса: /29 - 5 IPv4 адресов | 884,50 | 1 | 884,50 | 01.04.2026 |
| 7 | Next Generation Cloud: AMD 2.8 ГГц, в составе: Количество vCPU: гарантированная доля 80% - 32 шт. Объем vRAM - 96 ГБайт Дисковое пространство: SSD - 2000 ГБайт | 34 050,20 | 1 | 34 050,20 | 01.04.2026 |
| Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | 877 563,21 | - |
| Заказчик Генеральный директор Прогресс_0001 ________________ /ФИО/ М.П. | Исполнитель Генеральный директор Вектор_0001 _______________ /В.С. Степаненко/ М.П. |
|---|---|
@@ -0,0 +1,29 @@
Приложение № 1
к Договору на оказание технологических услуг
Договор_0001 от 20.12.2025 г.
**Спецификация и стоимость услуг**
### Состав и стоимость Услуг
Абонентские услуги
Общая стоимость Абонентских услуг составляет **4 928,30 руб**. (четыре тысячи девятьсот двадцать восемь рублей, тридцать копеек) с учетом НДС 22% в месяц.
| Екатеринбург_0001 | 20.12.2025г. |
|---|---|
| № п/п | Наименование Абонентских услуг | Цена, руб. с НДС | Объем услуг | Сумма, руб. с НДС | Планируемая дата начала оказания услуг |
|---|---|---|---|---|---|
| 1 | Next Generation Cloud (AMD 4.0 ГГц), в составе: Количество vCPU - 10 шт. Период тарификации - месяц Объем vRAM - 10 ГБайт Период тарификации - месяц Дисковое пространство: Fast SSD - 100 ГБайт Период тарификации - месяц | 4 928,30 | 1 | 4 928,30 | 29.01.2026 |
| Итого абонентский платеж в руб. с учетом НДС | Итого абонентский платеж в руб. с учетом НДС | Итого абонентский платеж в руб. с учетом НДС | Итого абонентский платеж в руб. с учетом НДС | 4 928,30 | - |
| Заказчик Директор Стандарт_0001 ________________ ФИО | Исполнитель Генеральный директор Комплекс_0001 _______________ Попов_0001 М.П. |
|---|---|
@@ -0,0 +1,28 @@
Приложение № 1
к Договору Договор_0001 от 01.02.2026 г.
**Спецификация и стоимость услуг**
### Состав и стоимость Услуг
Абонентские услуги
Общая стоимость Ежемесячных услуг составляет **68 700,00 руб**. (шестьдесят восемь тысяч семьсот рублей(я), 0 копеек(a)) с учетом НДС 22% 12 388,52 (Двенадцать тысяч триста восемьдесят восемь рублей) 52 копейки.
| г. Москва | 01.02.2026г. |
|---|---|
| № п/п | Наименование услуг | Цена, руб. с НДС | Объем услуг | Сумма, руб. с НДС | Планируемая дата начала оказания услуг |
|---|---|---|---|---|---|
| 1 | Облачный диск Valo Cloud, в составе: Число пользователей - 300 шт. Период тарификации - месяц Дисковое пространство: Объем хранения - 6000 ГБайт Период тарификации - месяц | 68 700,00 | 1 | 68 700,00 | 01.02.2026 |
| Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | Итого абонентский платеж в руб с учетом НДС | 68 700,00 | - |
| Заказчик Генеральный директор Сервис_0001 ________________ ФИО. М.П. | Исполнитель Генеральный директор Меридиан_0001 _______________ Михайлов_0001 М.П. |
|---|---|
@@ -0,0 +1,768 @@
# Список поступающих в магистратуру — 45.04.02 Лингвистика (очная, бюджет) — МГЛУ
**Вуз:** ФГБОУ ВО «Московский государственный лингвистический университет» (МГЛУ, ИНЯЗ им. Мориса Тореза)
**Программа:** 45.04.02 Лингвистика — Совокупность программ магистратуры (общий конкурс, бюджет)
**Источник:** https://linguanet.ru/docs-m-spd/index1-Obschij_konkurs.html
**Состояние конкурса:** 09:05, 04.08.2026
**Дата сбора:** 04.08.2026
**Сокращения (МГЛУ):** Отл — красный диплом (5 баллов), НП — научные публикации (4), ПП — профпереподготовка (3), C1 — сертификат МГЛУ C1/C2 (3), B1 — сертификат B1/B2 (2).
⚠️ В МГЛУ красный диплом даёт **5 баллов** (не 10 как в Сеченове/МГУ).
---
## Группа № 1 — план набора 70 (всего 516 заявлений)
### С баллами за ИД (1–287):
| № | Код | Баллы ИД | Красн. диплом |
|---|---|---|---|
| 1 | 1986467 | 10 | да |
| 2 | 1959147 | 10 | да |
| 3 | 2024343 | 10 | да |
| 4 | 1959962 | 10 | да |
| 5 | 2056910 | 10 | да |
| 6 | 1702073 | 10 | да |
| 7 | 2174578 | 10 | да |
| 8 | 1847066 | 10 | да |
| 9 | 1913104 | 10 | да |
| 10 | 2313240 | 10 | да |
| 11 | 2324217 | 10 | да |
| 12 | 2031355 | 10 | да |
| 13 | 2382564 | 10 | да |
| 14 | 2411444 | 10 | да |
| 15 | 2414763 | 10 | да |
| 16 | 1823399 | 10 | да |
| 17 | 2401322 | 10 | да |
| 18 | 1997008 | 10 | да |
| 19 | 2472375 | 10 | да |
| 20 | 2141416 | 10 | да |
| 21 | 2248449 | 10 | да |
| 22 | 1958063 | 9 | да |
| 23 | 1835968 | 9 | да |
| 24 | 1774105 | 9 | да |
| 25 | 1789118 | 9 | да |
| 26 | 1095275 | 9 | да |
| 27 | 1437957 | 9 | да |
| 28 | 1616960 | 9 | да |
| 29 | 2076818 | 9 | да |
| 30 | 1576853 | 9 | да |
| 31 | 2077939 | 9 | да |
| 32 | 2080125 | 9 | да |
| 33 | 2107902 | 9 | да |
| 34 | 2119877 | 9 | да |
| 35 | 998586 | 9 | да |
| 36 | 1889465 | 9 | да |
| 37 | 2123140 | 9 | да |
| 38 | 2089307 | 9 | да |
| 39 | 1845740 | 9 | да |
| 40 | 2031997 | 9 | да |
| 41 | 2162631 | 9 | да |
| 42 | 2164619 | 9 | да |
| 43 | 1704642 | 9 | да |
| 44 | 2214463 | 9 | да |
| 45 | 1004127 | 9 | да |
| 46 | 2295696 | 9 | да |
| 47 | 2288017 | 9 | да |
| 48 | 2273113 | 9 | да |
| 49 | 2291592 | 9 | да |
| 50 | 2325881 | 9 | да |
| 51 | 2335274 | 9 | да |
| 52 | 1766069 | 9 | да |
| 53 | 2402216 | 9 | да |
| 54 | 2410202 | 9 | да |
| 55 | 2074734 | 9 | да |
| 56 | 2192813 | 9 | да |
| 57 | 1915024 | 9 | да |
| 58 | 1945848 | 9 | да |
| 59 | 2339934 | 9 | да |
| 60 | 2383207 | 9 | да |
| 61 | 1991322 | 9 | да |
| 62 | 2387462 | 9 | да |
| 63 | 1118922 | 9 | да |
| 64 | 2456186 | 9 | да |
| 65 | 2168651 | 9 | да |
| 66 | 995301 | 9 | да |
| 67 | 1982175 | 9 | да |
| 68 | 1909632 | 8 | да |
| 69 | 2005491 | 8 | да |
| 70 | 2039325 | 8 | да |
| 71 | 1559432 | 8 | да |
| 72 | 1915479 | 8 | да |
| 73 | 1996546 | 8 | да |
| 74 | 2143156 | 8 | да |
| 75 | 2082429 | 8 | да |
| 76 | 1982019 | 8 | да |
| 77 | 2126325 | 8 | да |
| 78 | 1913125 | 8 | да |
| 79 | 2139779 | 8 | да |
| 80 | 2267253 | 8 | да |
| 81 | 2088979 | 8 | да |
| 82 | 2328827 | 8 | да |
| 83 | 2336592 | 8 | да |
| 84 | 2364503 | 8 | да |
| 85 | 2355605 | 8 | да |
| 86 | 2391090 | 8 | да |
| 87 | 2389281 | 8 | да |
| 88 | 2412594 | 8 | да |
| 89 | 2378792 | 8 | да |
| 90 | 1836123 | 8 | да |
| 91 | 2446436 | 8 | да |
| 92 | 2237388 | 8 | да |
| 93 | 2264475 | 8 | да |
| 94 | 2441574 | 8 | да |
| 95 | 1880193 | 7 | нет |
| 96 | 2027304 | 7 | нет |
| 97 | 2042566 | 7 | нет |
| 98 | 2080012 | 7 | нет |
| 99 | 2180084 | 7 | нет |
| 100 | 2302603 | 7 | да |
| 101 | 2326851 | 7 | нет |
| 102 | 2155682 | 7 | нет |
| 103 | 979472 | 5 | да |
| 104 | 1442993 | 5 | да |
| 105 | 1001106 | 5 | да |
| 106 | 986186 | 5 | да |
| 107 | 1034682 | 5 | да |
| 108 | 1838133 | 5 | да |
| 109 | 1936595 | 5 | да |
| 110 | 1949507 | 5 | да |
| 111 | 1904904 | 5 | да |
| 112 | 1986531 | 5 | да |
| 113 | 1851597 | 5 | да |
| 114 | 1869331 | 5 | да |
| 115 | 1888417 | 5 | да |
| 116 | 1914857 | 5 | да |
| 117 | 1963875 | 5 | да |
| 118 | 1441246 | 5 | да |
| 119 | 1810928 | 5 | да |
| 120 | 1997164 | 5 | да |
| 121 | 1822429 | 5 | да |
| 122 | 1836383 | 5 | да |
| 123 | 1717606 | 5 | да |
| 124 | 2034959 | 5 | да |
| 125 | 1617118 | 5 | да |
| 126 | 1620936 | 5 | да |
| 127 | 1024009 | 5 | да |
| 128 | 1694781 | 5 | да |
| 129 | 1837921 | 5 | да |
| 130 | 1107671 | 5 | да |
| 131 | 1125546 | 5 | да |
| 132 | 1143562 | 5 | да |
| 133 | 1150912 | 5 | да |
| 134 | 2048595 | 5 | да |
| 135 | 1399761 | 5 | да |
| 136 | 2052391 | 5 | да |
| 137 | 1212050 | 5 | да |
| 138 | 1213995 | 5 | да |
| 139 | 977994 | 5 | да |
| 140 | 2076678 | 5 | да |
| 141 | 2087434 | 5 | да |
| 142 | 1893542 | 5 | да |
| 143 | 2116731 | 5 | да |
| 144 | 2042372 | 5 | да |
| 145 | 2119519 | 5 | да |
| 146 | 2120813 | 5 | да |
| 147 | 2123752 | 5 | да |
| 148 | 2142486 | 5 | да |
| 149 | 1913494 | 5 | да |
| 150 | 2030313 | 5 | да |
| 151 | 2140980 | 5 | да |
| 152 | 1776417 | 5 | да |
| 153 | 1939980 | 5 | да |
| 154 | 2081148 | 5 | да |
| 155 | 1949949 | 5 | да |
| 156 | 1983095 | 5 | да |
| 157 | 1962936 | 5 | да |
| 158 | 1960851 | 5 | да |
| 159 | 2152242 | 5 | да |
| 160 | 2163899 | 5 | да |
| 161 | 2174025 | 5 | да |
| 162 | 2056572 | 5 | да |
| 163 | 2180565 | 5 | да |
| 164 | 2184997 | 5 | да |
| 165 | 2191587 | 5 | да |
| 166 | 2212942 | 5 | да |
| 167 | 2230684 | 5 | да |
| 168 | 2235964 | 5 | да |
| 169 | 2243999 | 5 | да |
| 170 | 2246749 | 5 | да |
| 171 | 2018248 | 5 | да |
| 172 | 2249840 | 5 | да |
| 173 | 2248692 | 5 | да |
| 174 | 2261004 | 5 | да |
| 175 | 2260430 | 5 | да |
| 176 | 2280952 | 5 | да |
| 177 | 2288443 | 5 | да |
| 178 | 2291492 | 5 | да |
| 179 | 2292262 | 5 | да |
| 180 | 2264949 | 5 | да |
| 181 | 2306832 | 5 | да |
| 182 | 2088500 | 5 | да |
| 183 | 2307460 | 5 | да |
| 184 | 2080519 | 5 | да |
| 185 | 2290462 | 5 | да |
| 186 | 2087110 | 5 | да |
| 187 | 2302567 | 5 | да |
| 188 | 2302990 | 5 | да |
| 189 | 2315301 | 5 | да |
| 190 | 2317162 | 5 | да |
| 191 | 1886500 | 5 | да |
| 192 | 2326262 | 5 | да |
| 193 | 2328307 | 5 | да |
| 194 | 2336096 | 5 | да |
| 195 | 2340184 | 5 | да |
| 196 | 2342957 | 5 | да |
| 197 | 2046968 | 5 | да |
| 198 | 1832379 | 5 | да |
| 199 | 2359345 | 5 | да |
| 200 | 2364721 | 5 | да |
| 201 | 2358206 | 5 | да |
| 202 | 2368355 | 5 | да |
| 203 | 2243140 | 5 | да |
| 204 | 1157385 | 5 | да |
| 205 | 2228228 | 5 | да |
| 206 | 2279237 | 5 | да |
| 207 | 2311610 | 5 | да |
| 208 | 2379080 | 5 | да |
| 209 | 2390957 | 5 | да |
| 210 | 2416494 | 5 | да |
| 211 | 2412561 | 5 | да |
| 212 | 2424441 | 5 | да |
| 213 | 2423184 | 5 | да |
| 214 | 2426935 | 5 | да |
| 215 | 2429983 | 5 | да |
| 216 | 2431315 | 5 | да |
| 217 | 1627565 | 5 | да |
| 218 | 1982130 | 5 | да |
| 219 | 2311028 | 5 | да |
| 220 | 1413451 | 5 | да |
| 221 | 1259749 | 5 | да |
| 222 | 1856018 | 5 | да |
| 223 | 1995839 | 5 | да |
| 224 | 2430886 | 5 | да |
| 225 | 2454163 | 5 | да |
| 226 | 2451149 | 5 | да |
| 227 | 2044400 | 5 | да |
| 228 | 2467138 | 5 | да |
| 229 | 2409963 | 5 | да |
| 230 | 2481204 | 5 | да |
| 231 | 2443829 | 5 | да |
| 232 | 2094575 | 5 | да |
| 233 | 2460565 | 5 | да |
| 234 | 2473053 | 5 | да |
| 235 | 2482981 | 5 | да |
| 236 | 1727527 | 5 | да |
| 237 | 984275 | 4 | нет |
| 238 | 1042961 | 4 | нет |
| 239 | 967887 | 4 | нет |
| 240 | 2027364 | 4 | нет |
| 241 | 2046390 | 4 | нет |
| 242 | 2078499 | 4 | нет |
| 243 | 1586164 | 4 | нет |
| 244 | 2087614 | 4 | нет |
| 245 | 2091787 | 4 | нет |
| 246 | 2002539 | 4 | нет |
| 247 | 2119174 | 4 | нет |
| 248 | 2165574 | 4 | нет |
| 249 | 2157161 | 4 | нет |
| 250 | 2159197 | 4 | нет |
| 251 | 2162154 | 4 | нет |
| 252 | 2172150 | 4 | нет |
| 253 | 2260510 | 4 | нет |
| 254 | 2297163 | 4 | нет |
| 255 | 2326984 | 4 | нет |
| 256 | 2327618 | 4 | нет |
| 257 | 2364556 | 4 | нет |
| 258 | 2364886 | 4 | нет |
| 259 | 2342296 | 4 | нет |
| 260 | 2403900 | 4 | нет |
| 261 | 1518952 | 4 | нет |
| 262 | 2044126 | 4 | нет |
| 263 | 1653495 | 4 | нет |
| 264 | 2174316 | 4 | нет |
| 265 | 2083304 | 4 | нет |
| 266 | 1707844 | 4 | нет |
| 267 | 982755 | 4 | нет |
| 268 | 1912189 | 3 | нет |
| 269 | 1875984 | 3 | нет |
| 270 | 1953224 | 3 | нет |
| 271 | 1155080 | 3 | нет |
| 272 | 2161846 | 3 | нет |
| 273 | 2225168 | 3 | нет |
| 274 | 2318032 | 3 | нет |
| 275 | 2394778 | 3 | нет |
| 276 | 1999772 | 3 | нет |
| 277 | 2442561 | 3 | нет |
| 278 | 2114625 | 3 | нет |
| 279 | 2228150 | 3 | нет |
| 280 | 2446669 | 3 | нет |
| 281 | 2184477 | 3 | нет |
| 282 | 2051698 | 3 | нет |
| 283 | 2469732 | 3 | нет |
| 284 | 1771071 | 3 | нет |
| 285 | 1498066 | 3 | нет |
| 286 | 2413239 | 3 | нет |
| 287 | 1986882 | 2 | нет |
### Без баллов за ИД (288–516): коды
962072, 831462, 970736, 976905, 996186, 1001621, 1001823, 1149419, 1043832, 1249253, 1263773, 1214789, 1379777, 1382663, 975221, 1387070, 1382550, 1383188, 1469881, 1296020, 1404921, 1395626, 1531262, 1518283, 1586002, 1589947, 990989, 1612166, 1628478, 1634023, 1564861, 1636975, 1646707, 1488031, 1683100, 1726234, 1798623, 1784957, 1819767, 1855104, 1852215, 1850553, 1841889, 1113870, 1885763, 1888844, 1704692, 1912781, 1909526, 1909694, 1044306, 1128994, 1906989, 1948675, 1947291, 1961781, 1927639, 1938857, 1962596, 1952288, 1945527, 1250350, 1907054, 1996604, 1996151, 1905346, 1913738, 1172563, 1943880, 1941556, 1946268, 1963193, 1948299, 2021510, 1994581, 2040338, 2029772, 1997376, 1997663, 2017640, 2050887, 2053510, 1999724, 2075011, 2091588, 2090364, 2088242, 2097779, 2096725, 2101859, 2076988, 2109394, 1968369, 2118363, 1517135, 1949810, 2140385, 1904284, 2135074, 2142338, 1653425, 2128608, 2125143, 2158805, 2155905, 2184080, 2180331, 2229241, 2227299, 2041227, 2038349, 2216654, 2218797, 2205452, 2210270, 2206170, 2099847, 2220290, 2246361, 1106928, 2253499, 2251649, 2259523, 2253016, 2263216, 2261905, 2141463, 2274395, 2284817, 2312147, 2300585, 2300826, 2313844, 2301501, 2311597, 2308108, 2337050, 2341227, 2338038, 2305865, 2346097, 2359475, 2357620, 2354833, 1880437, 2361843, 2361298, 2361106, 2364901, 2368530, 2365951, 2365596, 2369054, 2253405, 2393823, 2057862, 2390444, 2390070, 2393476, 2406611, 2404248, 2410716, 2390008, 1068627, 2413556, 1712112, 2414184, 2413832, 2413302, 2415888, 2415708, 2022151, 2414438, 2415547, 2415674, 1642721, 2425201, 2424188, 2149691, 2418260, 2228795, 1025416, 2428067, 2427732, 2429262, 2430011, 2435582, 1982302, 2432284, 1604930, 2429871, 2440607, 1700966, 1107756, 2447013, 2268568, 1253147, 2447002, 2445875, 2353753, 2454288, 2455651, 2298038, 2098348, 2458373, 2457602, 1578899, 2460023, 2466673, 2469790, 2470986, 2471035, 2471252, 2471573, 2470892, 2471372, 2475715, 2475467, 2473847, 2473005, 2472914, 2092930, 2472512, 2477845, 2479330, 2480157, 2483536, 2268454, 2285333
---
## Группа № 2 — план набора 84 (всего 582 заявления)
### С баллами за ИД (1–334):
| № | Код | Баллы ИД | Красн. диплом |
|---|---|---|---|
| 1 | 1099265 | 10 | да |
| 2 | 2035991 | 10 | да |
| 3 | 1940385 | 10 | да |
| 4 | 2091212 | 10 | да |
| 5 | 2098842 | 10 | да |
| 6 | 2056910 | 10 | да |
| 7 | 2130131 | 10 | да |
| 8 | 1702073 | 10 | да |
| 9 | 2097304 | 10 | да |
| 10 | 2043984 | 10 | да |
| 11 | 1931155 | 10 | да |
| 12 | 2313240 | 10 | да |
| 13 | 2324217 | 10 | да |
| 14 | 2363456 | 10 | да |
| 15 | 2382564 | 10 | да |
| 16 | 2411444 | 10 | да |
| 17 | 2414763 | 10 | да |
| 18 | 2024783 | 10 | да |
| 19 | 2242683 | 10 | да |
| 20 | 2401322 | 10 | да |
| 21 | 1997008 | 10 | да |
| 22 | 2473210 | 10 | да |
| 23 | 2472375 | 10 | да |
| 24 | 2460203 | 10 | да |
| 25 | 2178290 | 10 | да |
| 26 | 2141416 | 10 | да |
| 27 | 2248449 | 10 | да |
| 28 | 1429314 | 10 | да |
| 29 | 2262589 | 9 | да |
| 30 | 1058493 | 9 | нет |
| 31 | 1845740 | 9 | да |
| 32 | 1958063 | 9 | да |
| 33 | 1835968 | 9 | да |
| 34 | 1955346 | 9 | да |
| 35 | 1946503 | 9 | да |
| 36 | 1616960 | 9 | да |
| 37 | 1024380 | 9 | да |
| 38 | 2048832 | 9 | да |
| 39 | 1576853 | 9 | да |
| 40 | 1527189 | 9 | да |
| 41 | 2031997 | 9 | да |
| 42 | 1730868 | 9 | да |
| 43 | 2122622 | 9 | да |
| 44 | 2126805 | 9 | да |
| 45 | 1889465 | 9 | да |
| 46 | 2128731 | 9 | да |
| 47 | 2123140 | 9 | да |
| 48 | 2156361 | 9 | да |
| 49 | 1704642 | 9 | да |
| 50 | 2291592 | 9 | да |
| 51 | 2324691 | 9 | да |
| 52 | 2325881 | 9 | да |
| 53 | 2155793 | 9 | да |
| 54 | 1157980 | 9 | да |
| 55 | 2334469 | 9 | да |
| 56 | 2382709 | 9 | да |
| 57 | 2410202 | 9 | да |
| 58 | 2410119 | 9 | да |
| 59 | 2192813 | 9 | да |
| 60 | 2431182 | 9 | да |
| 61 | 2170975 | 9 | да |
| 62 | 1991322 | 9 | да |
| 63 | 2387462 | 9 | да |
| 64 | 1118922 | 9 | да |
| 65 | 994814 | 9 | да |
| 66 | 2084396 | 9 | да |
| 67 | 995301 | 9 | да |
| 68 | 2366866 | 9 | да |
| 69 | 1984309 | 9 | да |
| 70 | 1836123 | 8 | да |
| 71 | 1909632 | 8 | да |
| 72 | 1994323 | 8 | да |
| 73 | 2032538 | 8 | да |
| 74 | 2039325 | 8 | да |
| 75 | 1167595 | 8 | да |
| 76 | 1559432 | 8 | да |
| 77 | 2076691 | 8 | да |
| 78 | 2079479 | 8 | да |
| 79 | 1996546 | 8 | да |
| 80 | 2128268 | 8 | да |
| 81 | 2143156 | 8 | да |
| 82 | 2025275 | 8 | да |
| 83 | 1982019 | 8 | да |
| 84 | 2126325 | 8 | да |
| 85 | 1957468 | 8 | да |
| 86 | 1949924 | 8 | да |
| 87 | 1913125 | 8 | да |
| 88 | 2139779 | 8 | да |
| 89 | 2267253 | 8 | да |
| 90 | 2328827 | 8 | да |
| 91 | 2336592 | 8 | да |
| 92 | 2314422 | 8 | да |
| 93 | 2357993 | 8 | да |
| 94 | 2391090 | 8 | да |
| 95 | 2391314 | 8 | да |
| 96 | 2383277 | 8 | да |
| 97 | 2401635 | 8 | да |
| 98 | 2412594 | 8 | да |
| 99 | 2378792 | 8 | да |
| 100 | 2424775 | 8 | да |
| 101 | 2455380 | 8 | да |
| 102 | 2446436 | 8 | да |
| 103 | 2440448 | 8 | да |
| 104 | 2465686 | 8 | да |
| 105 | 2237388 | 8 | да |
| 106 | 2264475 | 8 | да |
| 107 | 2090464 | 8 | да |
| 108 | 2441574 | 8 | да |
| 109 | 2027304 | 7 | нет |
| 110 | 2038594 | 7 | да |
| 111 | 2083159 | 7 | да |
| 112 | 2180084 | 7 | нет |
| 113 | 2302603 | 7 | да |
| 114 | 2332097 | 7 | да |
| 115 | 2099259 | 7 | нет |
| 116 | 2354499 | 7 | нет |
| 117 | 2155682 | 7 | нет |
| 118 | 1456837 | 6 | нет |
| 119 | 964590 | 5 | да |
| 120 | 996024 | 5 | да |
| 121 | 1442993 | 5 | да |
| 122 | 986186 | 5 | да |
| 123 | 1036917 | 5 | да |
| 124 | 1077041 | 5 | да |
| 125 | 1882077 | 5 | да |
| 126 | 1936595 | 5 | да |
| 127 | 971835 | 5 | да |
| 128 | 1986531 | 5 | да |
| 129 | 1983875 | 5 | да |
| 130 | 1869331 | 5 | да |
| 131 | 1626889 | 5 | да |
| 132 | 1892711 | 5 | да |
| 133 | 1441246 | 5 | да |
| 134 | 1895710 | 5 | да |
| 135 | 1807727 | 5 | да |
| 136 | 1953969 | 5 | да |
| 137 | 1989177 | 5 | да |
| 138 | 1677356 | 5 | да |
| 139 | 1998558 | 5 | да |
| 140 | 1717606 | 5 | да |
| 141 | 1620936 | 5 | да |
| 142 | 1024009 | 5 | да |
| 143 | 1694781 | 5 | да |
| 144 | 1900745 | 5 | да |
| 145 | 1837921 | 5 | да |
| 146 | 1104215 | 5 | да |
| 147 | 2039067 | 5 | да |
| 148 | 1125546 | 5 | да |
| 149 | 1143562 | 5 | да |
| 150 | 1974601 | 5 | да |
| 151 | 1150912 | 5 | да |
| 152 | 973940 | 5 | да |
| 153 | 1399761 | 5 | да |
| 154 | 1425418 | 5 | да |
| 155 | 1443903 | 5 | да |
| 156 | 1188400 | 5 | да |
| 157 | 2071004 | 5 | да |
| 158 | 1213995 | 5 | да |
| 159 | 977994 | 5 | да |
| 160 | 2076678 | 5 | да |
| 161 | 2080642 | 5 | да |
| 162 | 2087434 | 5 | да |
| 163 | 2089058 | 5 | да |
| 164 | 1893542 | 5 | да |
| 165 | 1897375 | 5 | да |
| 166 | 2116731 | 5 | да |
| 167 | 2042372 | 5 | да |
| 168 | 2119519 | 5 | да |
| 169 | 2120813 | 5 | да |
| 170 | 2129672 | 5 | да |
| 171 | 2133326 | 5 | да |
| 172 | 2038870 | 5 | да |
| 173 | 2142486 | 5 | да |
| 174 | 1913494 | 5 | да |
| 175 | 2036508 | 5 | да |
| 176 | 2037862 | 5 | да |
| 177 | 2140980 | 5 | да |
| 178 | 2139817 | 5 | да |
| 179 | 2157240 | 5 | да |
| 180 | 1776417 | 5 | да |
| 181 | 2115055 | 5 | да |
| 182 | 2050197 | 5 | да |
| 183 | 2048205 | 5 | да |
| 184 | 2023092 | 5 | да |
| 185 | 1949949 | 5 | да |
| 186 | 1987576 | 5 | да |
| 187 | 1983095 | 5 | да |
| 188 | 1960851 | 5 | да |
| 189 | 2159906 | 5 | да |
| 190 | 2160060 | 5 | да |
| 191 | 2163899 | 5 | да |
| 192 | 2174025 | 5 | да |
| 193 | 2179281 | 5 | да |
| 194 | 2180565 | 5 | да |
| 195 | 2184997 | 5 | да |
| 196 | 2052391 | 5 | да |
| 197 | 1939907 | 5 | да |
| 198 | 1867960 | 5 | да |
| 199 | 2200611 | 5 | да |
| 200 | 2204200 | 5 | да |
| 201 | 2212942 | 5 | да |
| 202 | 1704446 | 5 | да |
| 203 | 2230684 | 5 | да |
| 204 | 2235964 | 5 | да |
| 205 | 2243999 | 5 | да |
| 206 | 2246749 | 5 | да |
| 207 | 2244818 | 5 | да |
| 208 | 2018248 | 5 | да |
| 209 | 1828992 | 5 | да |
| 210 | 2254796 | 5 | да |
| 211 | 2249614 | 5 | да |
| 212 | 2249840 | 5 | да |
| 213 | 2248692 | 5 | да |
| 214 | 2261004 | 5 | да |
| 215 | 2260430 | 5 | да |
| 216 | 2290975 | 5 | да |
| 217 | 2291492 | 5 | да |
| 218 | 2292698 | 5 | да |
| 219 | 2292262 | 5 | да |
| 220 | 2264949 | 5 | да |
| 221 | 2265652 | 5 | да |
| 222 | 2266153 | 5 | да |
| 223 | 2306832 | 5 | да |
| 224 | 2307460 | 5 | да |
| 225 | 2290462 | 5 | да |
| 226 | 2087110 | 5 | да |
| 227 | 2302990 | 5 | да |
| 228 | 2317162 | 5 | да |
| 229 | 1886500 | 5 | да |
| 230 | 2328307 | 5 | да |
| 231 | 2340184 | 5 | да |
| 232 | 2341533 | 5 | да |
| 233 | 2319799 | 5 | да |
| 234 | 2046968 | 5 | да |
| 235 | 2364721 | 5 | да |
| 236 | 2365324 | 5 | да |
| 237 | 2368355 | 5 | да |
| 238 | 2379804 | 5 | да |
| 239 | 2243140 | 5 | да |
| 240 | 1988047 | 5 | да |
| 241 | 1157385 | 5 | да |
| 242 | 2228228 | 5 | да |
| 243 | 2279237 | 5 | да |
| 244 | 2311610 | 5 | да |
| 245 | 2379080 | 5 | да |
| 246 | 2382454 | 5 | да |
| 247 | 2352019 | 5 | да |
| 248 | 2412561 | 5 | да |
| 249 | 2421918 | 5 | да |
| 250 | 2413678 | 5 | да |
| 251 | 2423494 | 5 | да |
| 252 | 1944212 | 5 | да |
| 253 | 2426935 | 5 | да |
| 254 | 2427149 | 5 | да |
| 255 | 2429983 | 5 | да |
| 256 | 2431315 | 5 | да |
| 257 | 1115434 | 5 | да |
| 258 | 2430924 | 5 | да |
| 259 | 1677670 | 5 | да |
| 260 | 1627565 | 5 | да |
| 261 | 2305489 | 5 | да |
| 262 | 1982130 | 5 | да |
| 263 | 2397573 | 5 | да |
| 264 | 2424436 | 5 | да |
| 265 | 1413451 | 5 | да |
| 266 | 1259749 | 5 | да |
| 267 | 1856018 | 5 | да |
| 268 | 2444953 | 5 | да |
| 269 | 1004144 | 5 | да |
| 270 | 2430886 | 5 | да |
| 271 | 2448482 | 5 | да |
| 272 | 2359722 | 5 | да |
| 273 | 2451149 | 5 | да |
| 274 | 2044400 | 5 | да |
| 275 | 2467138 | 5 | да |
| 276 | 2415670 | 5 | да |
| 277 | 2443829 | 5 | да |
| 278 | 2213956 | 5 | да |
| 279 | 2094575 | 5 | да |
| 280 | 2471321 | 5 | да |
| 281 | 2460565 | 5 | да |
| 282 | 2358966 | 5 | да |
| 283 | 2161100 | 5 | да |
| 284 | 2473053 | 5 | да |
| 285 | 2384775 | 5 | да |
| 286 | 2481015 | 5 | да |
| 287 | 2482981 | 5 | да |
| 288 | 2078276 | 5 | да |
| 289 | 1397856 | 5 | да |
| 290 | 2439904 | 5 | да |
| 291 | 1971723 | 5 | нет |
| 292 | 984275 | 4 | нет |
| 293 | 1042961 | 4 | нет |
| 294 | 2027364 | 4 | нет |
| 295 | 1156445 | 4 | нет |
| 296 | 2099938 | 4 | нет |
| 297 | 2260510 | 4 | нет |
| 298 | 2314401 | 4 | нет |
| 299 | 2364556 | 4 | нет |
| 300 | 2364886 | 4 | нет |
| 301 | 1518952 | 4 | нет |
| 302 | 1653495 | 4 | нет |
| 303 | 2174316 | 4 | нет |
| 304 | 2083304 | 4 | нет |
| 305 | 1707844 | 4 | нет |
| 306 | 982755 | 4 | нет |
| 307 | 1096785 | 3 | нет |
| 308 | 1776385 | 3 | нет |
| 309 | 1912189 | 3 | нет |
| 310 | 1875984 | 3 | нет |
| 311 | 1953224 | 3 | нет |
| 312 | 1727985 | 3 | нет |
| 313 | 1155080 | 3 | нет |
| 314 | 1610977 | 3 | нет |
| 315 | 2155954 | 3 | нет |
| 316 | 2300411 | 3 | нет |
| 317 | 2318032 | 3 | нет |
| 318 | 2341353 | 3 | нет |
| 319 | 2354619 | 3 | нет |
| 320 | 986157 | 3 | нет |
| 321 | 2382499 | 3 | нет |
| 322 | 2096926 | 3 | нет |
| 323 | 2442561 | 3 | нет |
| 324 | 2442767 | 3 | нет |
| 325 | 1239009 | 3 | нет |
| 326 | 2228150 | 3 | нет |
| 327 | 2446669 | 3 | нет |
| 328 | 2184477 | 3 | нет |
| 329 | 2452023 | 3 | нет |
| 330 | 2468661 | 3 | нет |
| 331 | 2469732 | 3 | нет |
| 332 | 1771071 | 3 | нет |
| 333 | 1986882 | 2 | нет |
| 334 | 1000031 | 2 | нет |
### Без баллов за ИД (335–582): коды
950585, 831462, 996186, 994712, 983212, 1001823, 1002005, 1001621, 1020903, 1044161, 1073987, 1093256, 1204363, 1043832, 1212076, 1263773, 1248244, 1214789, 1248566, 1327620, 1350115, 1382663, 975221, 1333197, 1413349, 1383188, 1436691, 1208700, 1261606, 1286474, 1296020, 1404921, 1412342, 1395626, 1513725, 1520480, 1517135, 1586002, 1535246, 1604742, 1634023, 1548841, 1564861, 1392893, 1664614, 1686316, 1690918, 1726234, 1730753, 1798623, 1784957, 1350812, 1819767, 1850553, 1113870, 1885763, 1888844, 1704692, 1906437, 1906989, 1910688, 1908367, 1909526, 1044306, 1909694, 1947291, 1128994, 1957079, 1952557, 1948675, 1962596, 1952288, 1961781, 1927639, 1938857, 1976948, 1250350, 1990279, 1984381, 1907054, 1988584, 2001065, 1996604, 2006048, 1905346, 1908694, 1913738, 1939349, 1943880, 1946268, 1963193, 2021510, 2029772, 1997376, 1997663, 2017640, 2051253, 1999724, 2043423, 2067772, 1122614, 2096016, 2091588, 2088242, 2097779, 2099644, 2096725, 2101321, 2080595, 2076988, 2108120, 2112836, 1968369, 1023938, 1333734, 2141923, 2141559, 2140385, 2138717, 2142338, 2135074, 2128608, 2125143, 2160801, 2158805, 2155905, 2168582, 1283904, 2184080, 2180331, 2229241, 2228371, 2227299, 2204810, 2200586, 2038349, 2211251, 2228279, 2210270, 2206170, 2246361, 1106928, 2273908, 2251649, 2253016, 2259523, 2253499, 2135017, 2263216, 2261008, 2141463, 2274395, 2298063, 2285333, 2300826, 2312147, 2306735, 2308132, 1844624, 2301501, 2310068, 2311597, 2308108, 1847701, 2317575, 2336962, 2337050, 2213270, 2340190, 2338251, 2305865, 2343994, 2359475, 2357620, 2359900, 2359358, 2166910, 2354833, 1880437, 2354546, 2353753, 2352025, 2361843, 2361298, 2361106, 2360355, 2369971, 2369054, 2152879, 2253405, 2377226, 2380845, 2388970, 2394052, 2393823, 2057862, 2390444, 2393476, 2402102, 2404248, 1047712, 2410716, 2390008, 2414184, 2414964, 2423759, 2425201, 2424188, 1642721, 2149691, 2418547, 2418260, 2312698, 1651891, 2228795, 1025416, 2428067, 2429763, 2435582, 2432284, 2409752, 1700966, 2441850, 2447013, 1253147, 2447002, 2454288, 1577926, 2454594, 2458373, 2457602, 1578899, 2098348, 2469790, 2470986, 2471372, 1994871, 2475467, 2473847, 2092930, 2472512, 2478426, 2478852, 2480889, 2480997, 2479330, 2268454, 2218797
---
## Группа № 3 — план набора 10 (всего 175 заявлений)
### С баллами за ИД (1–92):
| № | Код | Баллы ИД | Красн. диплом |
|---|---|---|---|
| 1 | 2401322 | 10 | да |
| 2 | 2473210 | 10 | да |
| 3 | 1429314 | 10 | да |
| 4 | 1796221 | 9 | да |
| 5 | 1095275 | 9 | да |
| 6 | 2156361 | 9 | да |
| 7 | 2325881 | 9 | да |
| 8 | 2383207 | 9 | да |
| 9 | 2439843 | 9 | да |
| 10 | 2387462 | 9 | да |
| 11 | 1118922 | 9 | да |
| 12 | 2354233 | 9 | да |
| 13 | 2366866 | 9 | да |
| 14 | 1915479 | 8 | да |
| 15 | 1949924 | 8 | да |
| 16 | 2267253 | 8 | да |
| 17 | 2336592 | 8 | да |
| 18 | 2355605 | 8 | да |
| 19 | 2440448 | 8 | да |
| 20 | 2464109 | 8 | да |
| 21 | 2180084 | 7 | нет |
| 22 | 2332097 | 7 | да |
| 23 | 1100866 | 7 | нет |
| 24 | 996024 | 5 | да |
| 25 | 1442993 | 5 | да |
| 26 | 1949507 | 5 | да |
| 27 | 971835 | 5 | да |
| 28 | 1869331 | 5 | да |
| 29 | 1963875 | 5 | да |
| 30 | 1798829 | 5 | да |
| 31 | 1807727 | 5 | да |
| 32 | 1989177 | 5 | да |
| 33 | 2004449 | 5 | да |
| 34 | 2003576 | 5 | да |
| 35 | 1836383 | 5 | да |
| 36 | 1717606 | 5 | да |
| 37 | 1617118 | 5 | да |
| 38 | 1837921 | 5 | да |
| 39 | 2039067 | 5 | да |
| 40 | 1107671 | 5 | да |
| 41 | 2052391 | 5 | да |
| 42 | 1212050 | 5 | да |
| 43 | 1236137 | 5 | да |
| 44 | 2079183 | 5 | да |
| 45 | 2089058 | 5 | да |
| 46 | 2116731 | 5 | да |
| 47 | 1557263 | 5 | да |
| 48 | 2137940 | 5 | да |
| 49 | 1913494 | 5 | да |
| 50 | 2140980 | 5 | да |
| 51 | 1980921 | 5 | да |
| 52 | 2160060 | 5 | да |
| 53 | 2174025 | 5 | да |
| 54 | 2056572 | 5 | да |
| 55 | 2182065 | 5 | да |
| 56 | 2191587 | 5 | да |
| 57 | 1104215 | 5 | да |
| 58 | 2201662 | 5 | да |
| 59 | 2225554 | 5 | да |
| 60 | 2018248 | 5 | да |
| 61 | 2249840 | 5 | да |
| 62 | 2280952 | 5 | да |
| 63 | 2292262 | 5 | да |
| 64 | 2306832 | 5 | да |
| 65 | 2080519 | 5 | да |
| 66 | 2312178 | 5 | да |
| 67 | 2302990 | 5 | да |
| 68 | 2340184 | 5 | да |
| 69 | 1832379 | 5 | да |
| 70 | 2279237 | 5 | да |
| 71 | 2311610 | 5 | да |
| 72 | 2352019 | 5 | да |
| 73 | 2416494 | 5 | да |
| 74 | 2412561 | 5 | да |
| 75 | 2429983 | 5 | да |
| 76 | 2430924 | 5 | да |
| 77 | 1856018 | 5 | да |
| 78 | 1995839 | 5 | да |
| 79 | 2409963 | 5 | да |
| 80 | 2473053 | 5 | да |
| 81 | 2481015 | 5 | да |
| 82 | 984275 | 4 | нет |
| 83 | 2046390 | 4 | нет |
| 84 | 2091787 | 4 | нет |
| 85 | 2473469 | 4 | нет |
| 86 | 1707844 | 4 | нет |
| 87 | 1819648 | 3 | нет |
| 88 | 2354619 | 3 | нет |
| 89 | 2446669 | 3 | нет |
| 90 | 2469732 | 3 | нет |
| 91 | 1997102 | 3 | нет |
| 92 | 1498066 | 3 | нет |
### Без баллов за ИД (93–175): коды
831462, 1020903, 996186, 950585, 1249253, 1263773, 1248244, 1379777, 1382663, 1436691, 1441616, 1535246, 1562444, 1548841, 1564861, 1636975, 1686316, 1798623, 1790714, 1868653, 1855104, 1906437, 1044306, 1909694, 1947291, 1962596, 1990279, 1988584, 1996604, 1946268, 1948299, 2021510, 2029772, 1950460, 2064315, 2068813, 2096725, 2109394, 2101859, 1241714, 2142338, 2180331, 2204810, 2220290, 2206170, 2244842, 2273908, 2253499, 2253016, 2317575, 2336142, 2340828, 2340190, 2305865, 2357620, 2359900, 1880437, 2361298, 2369054, 2393332, 2335549, 2402102, 2404248, 2413556, 2414184, 2415888, 2394645, 2415708, 2425201, 2424188, 1642721, 2149691, 2418547, 1025416, 1253147, 2457602, 2458541, 1773680, 2471372, 2473847, 2478852, 2479330, 2483536
---
## Итого по МГЛУ (очная, лингвистика, бюджет)
- Группа №1: план 70, заявлений 516
- Группа №2: план 84, заявлений 582
- Группа №3: план 10, заявлений 175
- Всего заявлений (с учётом подачи в несколько групп): 1273
@@ -0,0 +1,386 @@
# Список поступающих в магистратуру — 45.04.02 Лингвистика (очная, бюджет/КЦП) — ФИЯР МГУ
**Вуз:** ФГБОУ ВО «Московский государственный университет имени М.В. Ломоносова»
**Факультет:** Факультет иностранных языков и регионоведения (ФИЯР)
**Программа:** 45.04.02 Лингвистика, образовательная программа «Лингвистика», очная форма
**Вид мест:** Основные места в рамках КЦП (бюджет)
**Количество мест:** 32
**Источник:** https://cpk.msu.ru/submitted/master/dep_20 (Центральная приёмная комиссия МГУ)
**Список сформирован:** 27.07.2026 15:13:56
**Дата сбора:** 04.08.2026
**Всего поступающих:** 372
| № | Код поступающего | Статус |
|---|---|---|
| 1 | 952451 | На рассмотрении |
| 2 | 958672 | На рассмотрении |
| 3 | 959391 | На рассмотрении |
| 4 | 964590 | На рассмотрении |
| 5 | 967526 | На рассмотрении |
| 6 | 967693 | Конкурсная группа исключена |
| 7 | 967887 | На рассмотрении |
| 8 | 969868 | На рассмотрении |
| 9 | 971836 | На рассмотрении |
| 10 | 972471 | На рассмотрении |
| 11 | 977854 | На рассмотрении |
| 12 | 979472 | Конкурсная группа исключена |
| 13 | 982755 | На рассмотрении |
| 14 | 983678 | На рассмотрении |
| 15 | 984275 | На рассмотрении |
| 16 | 985581 | Конкурсная группа исключена |
| 17 | 986186 | На рассмотрении |
| 18 | 995301 | На рассмотрении |
| 19 | 1001621 | На рассмотрении |
| 20 | 1004127 | Конкурсная группа исключена |
| 21 | 1020903 | На рассмотрении |
| 22 | 1022796 | На рассмотрении |
| 23 | 1024380 | На рассмотрении |
| 24 | 1024995 | На рассмотрении |
| 25 | 1025416 | На рассмотрении |
| 26 | 1031212 | На рассмотрении |
| 27 | 1031919 | На рассмотрении |
| 28 | 1033229 | На рассмотрении |
| 29 | 1034243 | На рассмотрении |
| 30 | 1036176 | На рассмотрении |
| 31 | 1036713 | Конкурсная группа исключена |
| 32 | 1040665 | Конкурсная группа исключена |
| 33 | 1042961 | Конкурсная группа исключена |
| 34 | 1044095 | На рассмотрении |
| 35 | 1044137 | На рассмотрении |
| 36 | 1044306 | На рассмотрении |
| 37 | 1060354 | На рассмотрении |
| 38 | 1073368 | На рассмотрении |
| 39 | 1077041 | Конкурсная группа исключена |
| 40 | 1077644 | На рассмотрении |
| 41 | 1087070 | На рассмотрении |
| 42 | 1094821 | На рассмотрении |
| 43 | 1095275 | На рассмотрении |
| 44 | 1097706 | Конкурсная группа исключена |
| 45 | 1104565 | На рассмотрении |
| 46 | 1106928 | На рассмотрении |
| 47 | 1107178 | На рассмотрении |
| 48 | 1113870 | На рассмотрении |
| 49 | 1114657 | На рассмотрении |
| 50 | 1115534 | На рассмотрении |
| 51 | 1118980 | На рассмотрении |
| 52 | 1119212 | На рассмотрении |
| 53 | 1120922 | На рассмотрении |
| 54 | 1127186 | На рассмотрении |
| 55 | 1130609 | На рассмотрении |
| 56 | 1133145 | На рассмотрении |
| 57 | 1146590 | На рассмотрении |
| 58 | 1147573 | На рассмотрении |
| 59 | 1147592 | На рассмотрении |
| 60 | 1150199 | На рассмотрении |
| 61 | 1155080 | На рассмотрении |
| 62 | 1157615 | На рассмотрении |
| 63 | 1166226 | На рассмотрении |
| 64 | 1167993 | Конкурсная группа исключена |
| 65 | 1169704 | На рассмотрении |
| 66 | 1248244 | На рассмотрении |
| 67 | 1248672 | На рассмотрении |
| 68 | 1249253 | На рассмотрении |
| 69 | 1256155 | На рассмотрении |
| 70 | 1263773 | На рассмотрении |
| 71 | 1283904 | На рассмотрении |
| 72 | 1324700 | На рассмотрении |
| 73 | 1331095 | На рассмотрении |
| 74 | 1333197 | На рассмотрении |
| 75 | 1338495 | На рассмотрении |
| 76 | 1367199 | На рассмотрении |
| 77 | 1386416 | На рассмотрении |
| 78 | 1398774 | Конкурсная группа исключена |
| 79 | 1399761 | На рассмотрении |
| 80 | 1411788 | На рассмотрении |
| 81 | 1421729 | На рассмотрении |
| 82 | 1426914 | На рассмотрении |
| 83 | 1428427 | На рассмотрении |
| 84 | 1436094 | На рассмотрении |
| 85 | 1437957 | На рассмотрении |
| 86 | 1461010 | На рассмотрении |
| 87 | 1462107 | На рассмотрении |
| 88 | 1469881 | На рассмотрении |
| 89 | 1477052 | На рассмотрении |
| 90 | 1483253 | На рассмотрении |
| 91 | 1484331 | На рассмотрении |
| 92 | 1495480 | На рассмотрении |
| 93 | 1535246 | На рассмотрении |
| 94 | 1542819 | На рассмотрении |
| 95 | 1548852 | На рассмотрении |
| 96 | 1550161 | На рассмотрении |
| 97 | 1559432 | На рассмотрении |
| 98 | 1571352 | На рассмотрении |
| 99 | 1577890 | На рассмотрении |
| 100 | 1578899 | На рассмотрении |
| 101 | 1585465 | На рассмотрении |
| 102 | 1604742 | На рассмотрении |
| 103 | 1604930 | На рассмотрении |
| 104 | 1605242 | На рассмотрении |
| 105 | 1605704 | На рассмотрении |
| 106 | 1608921 | На рассмотрении |
| 107 | 1616960 | На рассмотрении |
| 108 | 1623654 | На рассмотрении |
| 109 | 1627565 | На рассмотрении |
| 110 | 1648849 | На рассмотрении |
| 111 | 1654168 | На рассмотрении |
| 112 | 1676176 | На рассмотрении |
| 113 | 1694781 | На рассмотрении |
| 114 | 1695717 | На рассмотрении |
| 115 | 1696179 | На рассмотрении |
| 116 | 1698389 | На рассмотрении |
| 117 | 1699108 | На рассмотрении |
| 118 | 1702671 | Конкурсная группа исключена |
| 119 | 1704446 | На рассмотрении |
| 120 | 1704642 | На рассмотрении |
| 121 | 1704692 | Конкурсная группа исключена |
| 122 | 1707844 | На рассмотрении |
| 123 | 1710263 | На рассмотрении |
| 124 | 1716861 | На рассмотрении |
| 125 | 1717606 | На рассмотрении |
| 126 | 1726234 | На рассмотрении |
| 127 | 1733072 | На рассмотрении |
| 128 | 1744477 | На рассмотрении |
| 129 | 1756298 | На рассмотрении |
| 130 | 1765615 | На рассмотрении |
| 131 | 1769992 | На рассмотрении |
| 132 | 1776360 | На рассмотрении |
| 133 | 1776417 | На рассмотрении |
| 134 | 1780183 | На рассмотрении |
| 135 | 1780451 | На рассмотрении |
| 136 | 1786595 | На рассмотрении |
| 137 | 1795325 | На рассмотрении |
| 138 | 1798530 | На рассмотрении |
| 139 | 1798623 | На рассмотрении |
| 140 | 1807727 | На рассмотрении |
| 141 | 1809880 | На рассмотрении |
| 142 | 1812870 | На рассмотрении |
| 143 | 1836024 | Конкурсная группа исключена |
| 144 | 1839476 | На рассмотрении |
| 145 | 1840217 | На рассмотрении |
| 146 | 1845399 | На рассмотрении |
| 147 | 1845740 | На рассмотрении |
| 148 | 1849936 | На рассмотрении |
| 149 | 1850553 | На рассмотрении |
| 150 | 1856018 | На рассмотрении |
| 151 | 1860670 | На рассмотрении |
| 152 | 1869331 | На рассмотрении |
| 153 | 1886500 | На рассмотрении |
| 154 | 1900918 | На рассмотрении |
| 155 | 1906563 | На рассмотрении |
| 156 | 1907054 | На рассмотрении |
| 157 | 1910688 | На рассмотрении |
| 158 | 1911764 | На рассмотрении |
| 159 | 1913738 | На рассмотрении |
| 160 | 1915024 | На рассмотрении |
| 161 | 1931789 | На рассмотрении |
| 162 | 1939397 | На рассмотрении |
| 163 | 1946268 | На рассмотрении |
| 164 | 1948299 | На рассмотрении |
| 165 | 1948675 | На рассмотрении |
| 166 | 1949924 | На рассмотрении |
| 167 | 1951573 | На рассмотрении |
| 168 | 1951956 | На рассмотрении |
| 169 | 1959962 | На рассмотрении |
| 170 | 1960690 | На рассмотрении |
| 171 | 1961285 | На рассмотрении |
| 172 | 1962936 | На рассмотрении |
| 173 | 1963875 | На рассмотрении |
| 174 | 1965822 | На рассмотрении |
| 175 | 1966886 | На рассмотрении |
| 176 | 1969466 | На рассмотрении |
| 177 | 1970107 | На рассмотрении |
| 178 | 1982019 | На рассмотрении |
| 179 | 1982175 | Конкурсная группа исключена |
| 180 | 1982302 | На рассмотрении |
| 181 | 1988272 | На рассмотрении |
| 182 | 1990205 | На рассмотрении |
| 183 | 1991322 | Конкурсная группа исключена |
| 184 | 1996299 | На рассмотрении |
| 185 | 1996604 | На рассмотрении |
| 186 | 1996740 | На рассмотрении |
| 187 | 1997008 | На рассмотрении |
| 188 | 1997664 | На рассмотрении |
| 189 | 1998558 | На рассмотрении |
| 190 | 2001065 | На рассмотрении |
| 191 | 2001608 | На рассмотрении |
| 192 | 2002696 | На рассмотрении |
| 193 | 2005908 | На рассмотрении |
| 194 | 2011321 | Конкурсная группа исключена |
| 195 | 2011330 | На рассмотрении |
| 196 | 2021510 | На рассмотрении |
| 197 | 2028462 | На рассмотрении |
| 198 | 2031286 | На рассмотрении |
| 199 | 2036476 | На рассмотрении |
| 200 | 2038161 | На рассмотрении |
| 201 | 2038960 | На рассмотрении |
| 202 | 2039325 | На рассмотрении |
| 203 | 2040645 | На рассмотрении |
| 204 | 2047553 | На рассмотрении |
| 205 | 2050197 | Конкурсная группа исключена |
| 206 | 2050887 | На рассмотрении |
| 207 | 2053892 | На рассмотрении |
| 208 | 2054619 | На рассмотрении |
| 209 | 2057862 | На рассмотрении |
| 210 | 2058085 | На рассмотрении |
| 211 | 2069009 | На рассмотрении |
| 212 | 2070097 | На рассмотрении |
| 213 | 2076233 | На рассмотрении |
| 214 | 2076818 | На рассмотрении |
| 215 | 2082755 | На рассмотрении |
| 216 | 2083615 | На рассмотрении |
| 217 | 2085646 | На рассмотрении |
| 218 | 2087110 | На рассмотрении |
| 219 | 2087212 | На рассмотрении |
| 220 | 2088242 | На рассмотрении |
| 221 | 2089584 | На рассмотрении |
| 222 | 2090464 | На рассмотрении |
| 223 | 2091787 | На рассмотрении |
| 224 | 2096016 | На рассмотрении |
| 225 | 2096725 | На рассмотрении |
| 226 | 2096926 | На рассмотрении |
| 227 | 2097304 | На рассмотрении |
| 228 | 2099123 | На рассмотрении |
| 229 | 2099644 | На рассмотрении |
| 230 | 2100507 | На рассмотрении |
| 231 | 2100888 | На рассмотрении |
| 232 | 2101859 | На рассмотрении |
| 233 | 2102110 | Конкурсная группа исключена |
| 234 | 2114323 | На рассмотрении |
| 235 | 2114615 | На рассмотрении |
| 236 | 2124768 | На рассмотрении |
| 237 | 2126047 | На рассмотрении |
| 238 | 2126325 | На рассмотрении |
| 239 | 2126914 | На рассмотрении |
| 240 | 2128608 | На рассмотрении |
| 241 | 2129672 | На рассмотрении |
| 242 | 2132221 | На рассмотрении |
| 243 | 2132636 | На рассмотрении |
| 244 | 2135723 | На рассмотрении |
| 245 | 2136120 | На рассмотрении |
| 246 | 2138424 | На рассмотрении |
| 247 | 2138785 | На рассмотрении |
| 248 | 2140980 | На рассмотрении |
| 249 | 2141189 | На рассмотрении |
| 250 | 2142271 | На рассмотрении |
| 251 | 2142338 | Конкурсная группа исключена |
| 252 | 2143156 | На рассмотрении |
| 253 | 2146101 | Конкурсная группа исключена |
| 254 | 2155682 | На рассмотрении |
| 255 | 2160060 | На рассмотрении |
| 256 | 2161373 | На рассмотрении |
| 257 | 2175095 | На рассмотрении |
| 258 | 2177540 | На рассмотрении |
| 259 | 2180331 | На рассмотрении |
| 260 | 2182150 | На рассмотрении |
| 261 | 2184477 | На рассмотрении |
| 262 | 2184997 | Конкурсная группа исключена |
| 263 | 2204777 | На рассмотрении |
| 264 | 2213270 | На рассмотрении |
| 265 | 2217104 | На рассмотрении |
| 266 | 2217240 | На рассмотрении |
| 267 | 2223159 | На рассмотрении |
| 268 | 2224491 | На рассмотрении |
| 269 | 2225316 | На рассмотрении |
| 270 | 2226329 | На рассмотрении |
| 271 | 2227299 | На рассмотрении |
| 272 | 2229164 | На рассмотрении |
| 273 | 2229241 | На рассмотрении |
| 274 | 2230570 | На рассмотрении |
| 275 | 2241273 | На рассмотрении |
| 276 | 2243999 | На рассмотрении |
| 277 | 2248207 | На рассмотрении |
| 278 | 2248911 | На рассмотрении |
| 279 | 2250915 | На рассмотрении |
| 280 | 2253016 | На рассмотрении |
| 281 | 2253499 | На рассмотрении |
| 282 | 2256616 | На рассмотрении |
| 283 | 2259036 | На рассмотрении |
| 284 | 2259523 | На рассмотрении |
| 285 | 2263216 | Конкурсная группа исключена |
| 286 | 2263919 | На рассмотрении |
| 287 | 2264224 | На рассмотрении |
| 288 | 2264475 | На рассмотрении |
| 289 | 2264786 | На рассмотрении |
| 290 | 2265627 | На рассмотрении |
| 291 | 2270331 | На рассмотрении |
| 292 | 2272742 | На рассмотрении |
| 293 | 2274395 | На рассмотрении |
| 294 | 2279237 | На рассмотрении |
| 295 | 2280952 | На рассмотрении |
| 296 | 2283662 | На рассмотрении |
| 297 | 2284817 | На рассмотрении |
| 298 | 2291592 | На рассмотрении |
| 299 | 2299551 | На рассмотрении |
| 300 | 2301501 | На рассмотрении |
| 301 | 2307460 | На рассмотрении |
| 302 | 2310756 | На рассмотрении |
| 303 | 2312147 | На рассмотрении |
| 304 | 2313033 | На рассмотрении |
| 305 | 2313482 | На рассмотрении |
| 306 | 2314722 | На рассмотрении |
| 307 | 2317162 | На рассмотрении |
| 308 | 2321441 | На рассмотрении |
| 309 | 2324217 | На рассмотрении |
| 310 | 2325881 | На рассмотрении |
| 311 | 2328307 | Конкурсная группа исключена |
| 312 | 2328827 | На рассмотрении |
| 313 | 2331751 | На рассмотрении |
| 314 | 2332021 | На рассмотрении |
| 315 | 2332259 | На рассмотрении |
| 316 | 2332733 | На рассмотрении |
| 317 | 2333838 | На рассмотрении |
| 318 | 2334973 | На рассмотрении |
| 319 | 2335708 | На рассмотрении |
| 320 | 2336019 | На рассмотрении |
| 321 | 2336126 | На рассмотрении |
| 322 | 2336649 | На рассмотрении |
| 323 | 2339509 | На рассмотрении |
| 324 | 2339818 | На рассмотрении |
| 325 | 2342486 | На рассмотрении |
| 326 | 2343925 | На рассмотрении |
| 327 | 2343994 | На рассмотрении |
| 328 | 2346097 | На рассмотрении |
| 329 | 2346522 | На рассмотрении |
| 330 | 2355804 | На рассмотрении |
| 331 | 2356798 | На рассмотрении |
| 332 | 2357298 | На рассмотрении |
| 333 | 2359900 | На рассмотрении |
| 334 | 2361002 | На рассмотрении |
| 335 | 2361843 | На рассмотрении |
| 336 | 2362301 | На рассмотрении |
| 337 | 2365171 | На рассмотрении |
| 338 | 2366866 | На рассмотрении |
| 339 | 2370255 | На рассмотрении |
| 340 | 2370491 | На рассмотрении |
| 341 | 2371510 | На рассмотрении |
| 342 | 2377226 | На рассмотрении |
| 343 | 2380724 | На рассмотрении |
| 344 | 2386549 | На рассмотрении |
| 345 | 2390140 | На рассмотрении |
| 346 | 2390938 | На рассмотрении |
| 347 | 2392403 | На рассмотрении |
| 348 | 2393823 | На рассмотрении |
| 349 | 2395820 | На рассмотрении |
| 350 | 2399163 | На рассмотрении |
| 351 | 2403485 | На рассмотрении |
| 352 | 2407442 | На рассмотрении |
| 353 | 2408340 | На рассмотрении |
| 354 | 2409752 | На рассмотрении |
| 355 | 2409794 | На рассмотрении |
| 356 | 2411359 | На рассмотрении |
| 357 | 2412905 | На рассмотрении |
| 358 | 2414763 | На рассмотрении |
| 359 | 2415088 | На рассмотрении |
| 360 | 2415198 | На рассмотрении |
| 361 | 2415547 | На рассмотрении |
| 362 | 2418547 | На рассмотрении |
| 363 | 2421383 | На рассмотрении |
| 364 | 2424188 | На рассмотрении |
| 365 | 2425201 | На рассмотрении |
| 366 | 2426768 | На рассмотрении |
| 367 | 2426799 | На рассмотрении |
| 368 | 2429134 | На рассмотрении |
| 369 | 2430886 | На рассмотрении |
| 370 | 2438248 | На рассмотрении |
| 371 | 2439832 | На рассмотрении |
| 372 | 2441081 | На рассмотрении |
@@ -0,0 +1,87 @@
# Список поступающих в магистратуру — 45.04.02 Лингвистика (очная, бюджет)
**Вуз:** ФГАОУ ВО Первый МГМУ им. И.М. Сеченова Минздрава России (Сеченовский Университет)
**Программа:** 45.04.02 Лингвистика, очная форма, бюджет
**Количество мест:** 10
**Источник:** https://priem.sechenov.ru/submitted-applicants/master/ (Списки подавших заявление)
**Дата сбора:** 04.08.2026
**Всего поступающих:** 71
| № | Код поступающего | Конкурс. баллы | Статус |
|---|---|---|---|
| 1 | 2405446 | 96 | Участвует в конкурсе |
| 2 | 2123140 | 86 | Участвует в конкурсе |
| 3 | 1034997 | 82 | Участвует в конкурсе |
| 4 | 1264543 | 80 | Участвует в конкурсе |
| 5 | 1956979 | 70 | Участвует в конкурсе |
| 6 | 1961444 | 66 | Участвует в конкурсе |
| 7 | 981869 | 64 | Участвует в конкурсе |
| 8 | 1634623 | 54 | Участвует в конкурсе |
| 9 | 1312228 | 20 | Подал заявление |
| 10 | 1937932 | 20 | Отклонено образовательной организацией |
| 11 | 1161939 | 10 | Подал заявление |
| 12 | 1959962 | 10 | Подал заявление |
| 13 | 1854074 | 10 | Подал заявление |
| 14 | 2171073 | 10 | Отозвано поступающим |
| 15 | 1832746 | 10 | Подал заявление |
| 16 | 2482254 | 10 | Подал заявление |
| 17 | 2263185 | 10 | Подал заявление |
| 18 | 2460207 | 10 | Подал заявление |
| 19 | 2358769 | 10 | Подал заявление |
| 20 | 2174025 | 10 | Подал заявление |
| 21 | 1991322 | 10 | Подал заявление |
| 22 | 2416494 | 10 | Подал заявление |
| 23 | 1958671 | 10 | Подал заявление |
| 24 | 1776417 | 10 | Подал заявление |
| 25 | 1886500 | 10 | Подал заявление |
| 26 | 2325881 | 10 | Подал заявление |
| 27 | 2264475 | 10 | Отозвано поступающим |
| 28 | 1953969 | 10 | Подал заявление |
| 29 | 1555246 | 10 | Подал заявление |
| 30 | 1627565 | 10 | Отозвано поступающим |
| 31 | 2051306 | 0 | Подал заявление |
| 32 | 1484552 | 0 | Подал заявление |
| 33 | 2488272 | 0 | Подал заявление |
| 34 | 2483585 | 0 | Подал заявление |
| 35 | 2371295 | 0 | Подал заявление |
| 36 | 1589947 | 0 | Отозвано поступающим |
| 37 | 1846380 | 0 | Подал заявление |
| 38 | 1488031 | 0 | Подал заявление |
| 39 | 2101254 | 0 | Подал заявление |
| 40 | 1054084 | 0 | Подал заявление |
| 41 | 2255746 | 0 | Отозвано поступающим |
| 42 | 2050887 | 0 | Подал заявление |
| 43 | 2058088 | 0 | Отклонено образовательной организацией |
| 44 | 2414375 | 0 | Подал заявление |
| 45 | 2301501 | 0 | Подал заявление |
| 46 | 1948299 | 0 | Подал заявление |
| 47 | 2473847 | 0 | Подал заявление |
| 48 | 2472512 | 0 | Подал заявление |
| 49 | 2129695 | 0 | Подал заявление |
| 50 | 2481178 | 0 | Отклонено образовательной организацией |
| 51 | 2466587 | 0 | Подал заявление |
| 52 | 2353753 | 0 | Подал заявление |
| 53 | 2198799 | 0 | Подал заявление |
| 54 | 2140385 | 0 | Подал заявление |
| 55 | 2089584 | 0 | Подал заявление |
| 56 | 2141189 | 0 | Подал заявление |
| 57 | 1893673 | 0 | Подал заявление |
| 58 | 2310476 | 0 | Отозвано поступающим |
| 59 | 2384749 | 0 | Подал заявление |
| 60 | 2393823 | 0 | Подал заявление |
| 61 | 1968369 | 0 | Отозвано поступающим |
| 62 | 2096170 | 0 | Подал заявление |
| 63 | 2033331 | 0 | Подал заявление |
| 64 | 1716861 | 0 | Подал заявление |
| 65 | 1583721 | 0 | Подал заявление |
| 66 | 1032394 | 0 | Подал заявление |
| 67 | 1234985 | 0 | Отозвано поступающим |
| 68 | 977854 | 0 | Подал заявление |
| 69 | 1133145 | 0 | Подал заявление |
| 70 | 955308 | 0 | Подал заявление |
| 71 | 1481704 | 0 | Отозвано поступающим |
## Пояснения
- **Участвует в конкурсе** — вступительные сданы, реальные претенденты на бюджет.
- **Подал заявление** — документы приняты, ВИ ещё не сданы (0 баллов).
- **Отозвано / Отклонено** — выбыли из конкурса.
+173 -44
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
@@ -37,21 +37,36 @@ site/app.py ──→ Flask (create_app)
---
## Поддерживаемые форматы файлов
| Формат | Обработка | Детали |
|--------|-----------|--------|
| **`.docx`** | → Markdown | Заголовки (H1–H3), **жирный**, *курсив*, таблицы (`python-docx`) |
| **`.pdf`** | → Markdown | Текст + таблицы (`pdfplumber`), без форматирования |
| **`.txt`** и прочие текстовые | → как есть | Декодируется UTF-8, ошибки заменяются `�` |
| **`.zip`** | → распаковка | Файлы внутри обрабатываются рекурсивно. Защита от ZIP-бомб: ≤500 файлов, ratio ≤100:1, ≤500 MB |
| **`.doc`** (бинарный) | → Markdown | Через LibreOffice headless: `.doc` → `.docx` → штатный `docx_to_markdown` |
Ограничение на размер загрузки: **200 MB** (`MAX_CONTENT_LENGTH`).
---
## Двухпроходная обфускация
### Проход 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, ИНН, ОГРН, КПП,
│ БИК, счета, паспорта, компании, ФИО
@@ -60,7 +75,7 @@ extractor.extract_text() ← извлечь текст из каждог
(только если llm_client передан)
│
▼
mapping = {оригинал → фиктивное} ← глобальный словарь замен
mapping = {оригинал → токен} ← глобальный словарь замен (гибридные токены)
```
### Проход 2: замена
@@ -72,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
@@ -90,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`
Две функции сборки результата:
@@ -147,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)` — единая точка регистрации.
@@ -156,21 +160,129 @@ 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)
```
---
## Требования к 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)
│
├── 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)
│
▼
@@ -180,12 +292,29 @@ send_file(BytesIO(zip_data), mimetype="application/zip")
HTTP 200 + ZIP-архив
```
### Имена скачиваемых файлов
Формат: `drhider_YYYY-MM-DD_HH-MM-SS.zip` и `mapping_YYYY-MM-DD_HH-MM-SS.csv`
Время — московское (UTC+3). Генерируется на сервере в момент запроса:
```python
# api_bp.py — download() / csv_download()
ts = (datetime.now() + timedelta(hours=3)).strftime("%Y-%m-%d_%H-%M-%S")
download_name=f"drhider_{ts}.zip" # → Content-Disposition
download_name=f"mapping_{ts}.csv" # → Content-Disposition
```
Фронтенд (JS) читает имя из заголовка `Content-Disposition`, **не** хардкодит.
---
## Принципы
1. **Модули — чистые функции.** Где возможно — без классов, без состояния.
2. **Единственный класс — TwoPassObfuscator.** Только он управляет состоянием (mapping, sorted_keys).
3. **Генераторы — изолированы.** Каждый в своём файле, не зависят друг от друга.
4. **Flask — тонкая прослойка.** Только принимает запрос, вызывает `obfuscate_files()`, отдаёт ответ.
5. **Без БД.** Всё в памяти. Никаких внешних зависимостей кроме LLM API.
3. **Flask — тонкая прослойка.** Только принимает запрос, вызывает `obfuscate_files()`, отдаёт ответ.
4. **Без БД.** Всё в памяти. Никаких внешних зависимостей кроме LLM API.
5. **⛔ Ноль внешних зависимостей на фронтенде.** Никаких CDN, внешних шрифтов, иконок, JS-библиотек. ВСЁ в коде. Страница грузится за 1 запрос.
6. **⛔ ВСЯ обработка данных — на бэкенде.** Фронтенд только отображает и скачивает. Никакой JS-логики для ZIP, парсинга, обфускации. Один запрос → один ответ.
7. **⛔ Пофайловая обработка — на бэкенде.** Файлы обрабатываются строго последовательно (по одному), без параллельных LLM-запросов. Прогресс — бэкенд сообщает статус каждого файла.
+54
View File
@@ -81,6 +81,60 @@ sudo systemctl restart contracts-drhider
---
## Kubernetes-кластер (iot-naeel)
DrHider работает в кластере `iot-naeel` на платформе Штурвал.
### Доступ к kubectl
kubectl настроен на ВМ `5.172.178.213`. Все команды выполняются через SSH:
```bash
ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213 kubectl ...
```
### DrHider
```bash
# Найти под drhider (namespace — UUID Штурвала)
kubectl get pods --all-namespaces | grep pythonk8s
# Логи
kubectl logs -n <namespace> <pod>
# Перезапуск — через UI Штурвала (Redeploy)
```
### Ingress
```bash
# Реплики
kubectl get deploy -n ingress shturval-ingress-controller-controller
# externalTrafficPolicy
kubectl get svc -n ingress shturval-ingress-controller-controller \
-o jsonpath='{.spec.externalTrafficPolicy}'
# Масштабирование (если сбросилось)
kubectl scale deploy -n ingress shturval-ingress-controller-controller --replicas=4
```
### Cilium / MTU
```bash
# Текущий MTU
kubectl get configmap -n kube-system cilium-config -o jsonpath='{.data.mtu}'
# Статус Cilium
kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep -i mtu
```
### VIP (kube-vip)
VIP кластера: `185.247.187.151`. Раздаётся через kube-vip (ARP) на все 4 ноды.
---
## Локальная разработка
```bash
+134
View File
@@ -0,0 +1,134 @@
# Варианты решения ingress replicas + TCP stall
**Дата:** 2026-07-13
**Источники:** Sonnet analysis, Opus analysis
**Связано:** [KUBEVIP-EXTERNAL-TRAFFIC-POLICY.md](KUBEVIP-EXTERNAL-TRAFFIC-POLICY.md)
---
## Ключевой вывод (оба согласны)
Проблемы **две независимые**, решать по отдельности:
1. **TCP stall 51с** — PMTU Blackhole на Geneve-туннеле, а не свойство `Cluster`-политики. `Local` лечит как побочный эффект (убирает cross-node).
2. **Сброс реплик** — `kubectl scale` обходит Helm, поэтому реконсиляция откатывает.
> ⚠️ **Важно (Opus):** `kubectl scale` вы обходите Helm, поэтому и сбрасывается. Любое итоговое решение должно быть в **values чарта**, а не через `kubectl`.
---
## Варианты
### 1. DaemonSet (рекомендуется)
Одно изменение в Helm values:
```yaml
controller:
kind: DaemonSet
# replicaCount игнорируется
```
- ✅ Автоматически 1 под/ноду
- ✅ Helm не ломает
- ✅ Стандарт для bare-metal / kube-vip
- ✅ Не требует менять сетевую конфигурацию
### 2. Починить MTU (убрать stall в корне)
51с — это классический Path MTU Discovery Blackhole. Geneve: MTU 1500 → ~1450.
**Диагностика:**
```bash
ip link show | grep -E "cilium|genev"
iptables -t mangle -L FORWARD -n -v | grep TCPMSS
```
**Исправление:**
```bash
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
```
Если stall устранён → можно вернуть `externalTrafficPolicy: Cluster` и replicaCount 2.
### 3. Cilium DSR (Direct Server Return)
Ответный трафик идёт напрямую от пода к клиенту, без cross-node SNAT.
```yaml
# cilium-config (требует kube-proxy replacement = strict)
loadBalancer:
mode: dsr
kubeProxyReplacement: strict
```
- ❌ Долго, меняет всю сетевую конфигурацию
### 4. replicaCount: 4 + podAntiAffinity
Если Штурвал не даёт сменить `kind` на DaemonSet:
```yaml
controller:
replicaCount: 4
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
topologyKey: kubernetes.io/hostname
```
- ✅ Гарантирует 1 под/ноду
- ✅ Helm values зафиксированы, не сбрасывается
---
## Рекомендуемый путь (Sonnet + Opus)
| Приоритет | Действие | Эффект |
|-----------|----------|--------|
| 🟢 Сразу | `controller.kind: DaemonSet` в Helm values | Убирает костыль с репликами навсегда |
| 🟡 Параллельно | Диагностика MTU + MSS clamping | Устраняет stall в корне |
| 🔵 Потом | Если MTU починен — вернуть `Cluster` | Можно уйти от `Local` и требования под/ноду |
## Как передать девопсу (Opus)
Для чарта `shturval-ingress-controller-2.12.1`:
```bash
# Вариант A: DaemonSet
helm upgrade -n ingress shturval-ingress-controller <чарт> \
--set controller.kind=DaemonSet
# Вариант B: replicaCount + antiAffinity (если DaemonSet недоступен)
helm upgrade -n ingress shturval-ingress-controller <чарт> \
--set controller.replicaCount=4 \
--set controller.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].labelSelector.matchLabels.app\.kubernetes\.io/name=ingress-nginx \
--set controller.affinity.podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution[0].topologyKey=kubernetes.io/hostname
```
Либо через Штурвал UI — изменить `replicaCount: 4` в настройках чарта.
## Диагностика MTU (Opus)
```bash
# реальный MTU туннеля
ip link show | grep -E "cilium|genev"
# есть ли MSS clamping
iptables -t mangle -L FORWARD -n -v | grep MSS
```
Варианты фикса:
- MSS clamping: `iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu`
- Cilium DSR: `loadBalancer.mode: dsr` + `kubeProxyReplacement: strict`
- Выставить MTU в Cilium под физическую сеть (Geneve overhead ~50 байт)
---
## Текущий статус
`externalTrafficPolicy: Local` — не сбрасывается.
`replicas: 4` — ручной scale, периодически сбрасывается Helm на 2.
+87
View File
@@ -0,0 +1,87 @@
# Kube-vip + externalTrafficPolicy: Local + ingress replicas
**Дата:** 2026-07-13
**Кластер:** iot-naeel
**Сервис:** `ingress/shturval-ingress-controller-controller`
---
## Проблема
`externalTrafficPolicy: Local` на ingress-сервисе + 2 ingress-пода на 4 нодах.
Kube-vip раздаёт VIP (`185.247.187.151`) на **все 4 ноды** через ARP.
С `Local` трафик, пришедший на ноду БЕЗ ingress-пода — **дропается**.
Результат: 50% запросов → `ERR_TIMED_OUT`.
## Почему не было раньше
Раньше стоял `externalTrafficPolicy: Cluster` — kube-vip форвардил трафик
на любую ноду с ingress через Geneve-туннель. F5 работал, но при передаче
**больших файлов** возникал TCP stall на 51 секунду (cross-node Geneve).
## Хронология
1. `Cluster` → всё работало, но большие файлы висли на 51с
2. `Cluster` → `Local` — файлы перестали виснуть, но F5 стал дропаться (реплик 2)
3. `kubectl scale --replicas=4` — обе проблемы решены
4. **2026-07-13 10:24 MSK** — Helm-релиз ревизия 6 → `replicas` сброшены с 4 на 2
5. `kubectl scale --replicas=4` — снова 4
## Почему сбрасывается
Ingress управляется Helm-чартом `shturval-ingress-controller-2.12.1`.
```yaml
# helm get values -n ingress shturval-ingress-controller
controller:
replicaCount: 2 # ← вот причина
```
Штурвал периодически реконсилит деплой под Helm-значения —
видит 4 → возвращает на 2.
`externalTrafficPolicy: Local` при этом **не сбрасывается** (ставилось через `kubectl patch svc`, не через Helm).
## Что нужно от девопса
**Единственное изменение:** `replicaCount: 2 → 4` в Helm-чарте ingress.
```
helm upgrade -n ingress shturval-ingress-controller <чарт> --set controller.replicaCount=4
```
Или DaemonSet.
## Текущее состояние (исправлено вручную, будет сброшено)
```bash
kubectl scale deploy -n ingress shturval-ingress-controller-controller --replicas=4
```
| Под | Нода |
|-----|------|
| `j4z6z` | iot-naeel-control-plane-xb699 |
| `q8v8g` | iot-naeel-workers-vqphm-6f74n |
| `zqxqg` | iot-naeel-workers-vqphm-bhbvs |
| `fp4p2` | iot-naeel-workers-vqphm-v8zq4 |
## Результат
10 последовательных запросов: 10/10 HTTP 200, 130-250ms.
## Что нужно от девопса
1. Перевести ingress на **DaemonSet** — автоматически по поду на ноду
2. Или `replicas = max(2, количество нод)` — автоскейлинг при добавлении нод
3. При передеплое Штурвала `replicas` не должен сбрасываться на 2
## Ноды (4)
| Нода | Тип |
|------|-----|
| iot-naeel-control-plane-xb699 | control-plane |
| iot-naeel-workers-vqphm-6f74n | workers |
| iot-naeel-workers-vqphm-bhbvs | workers |
| iot-naeel-workers-vqphm-v8zq4 | workers |
+288
View File
@@ -0,0 +1,288 @@
# Инструкция: перенос UI и загрузки файлов с ВМ на Flask (Штурвал)
**Дата:** 2026-07-14
**Основано на:** drhider v0.0.26 — проверено в бою
---
## Архитектура загрузки (drhider как образец)
```
Браузер (index.html)
│ JS: uploadFiles() — цикл по файлам
│ XHR POST /api/upload (FormData, по одному файлу)
▼
Flask (api_bp.py)
│ upload(): сохраняет в сессию → {session_id}
│ process_stream(): SSE — обработка с прогрессом
▼
Python (drhider/*.py)
│ obfuscate_files() — основная логика
▼
Flask → ZIP + CSV → браузеру
```
## Файлы, отвечающие за UI и загрузку
### 1. `site/templates/index.html` — весь фронтенд
**Ключевые элементы:**
```html
<!-- Файловый инпут — множественный выбор -->
<input type="file" id="fileInput" multiple accept=".docx,.pdf,.txt,.zip">
<!-- Таблица выбранных файлов -->
<table>
<tbody id="fileList"></tbody>
</table>
<!-- Статус-бар -->
<div class="status" id="status"></div>
<!-- Кнопки скачивания (скрыты до готовности) -->
<div class="dl-btns" id="dlBtns">
<button onclick="downloadZip()">📦 Скачать ZIP</button>
<button onclick="downloadCsv()">📋 Скачать CSV</button>
</div>
```
**Ключевые JS-функции:**
| Функция | Что делает |
|---------|------------|
| `resetAll()` | Сброс всего состояния (F5, между загрузками) |
| `rr()` | Перерисовка таблицы файлов |
| `uploadFiles()` | **Главная** — цикл загрузки + SSE-обработка |
| `ss(idx, html)` | Обновление ячейки статуса в таблице |
| `downloadZip()` / `downloadCsv()` | Скачивание результатов |
**Критичные исправления (v0.0.26 — v0.0.29):**
```javascript
// 1. Прогрев upstream при загрузке страницы — обязательно!
window.addEventListener('load', () => {
sf = []; fi.value = ''; rr();
fetch('/health').catch(() => {}); // ← обязательно! греет upstream
});
// 2. Abort предыдущих запросов перед новым
let activeES = null; // активный EventSource
let activeXHR = null; // активный XHR
// 3. Защита от F5 во время загрузки
window.addEventListener('beforeunload', () => resetAll());
// 4. Сохранение списка файлов до очистки
const files = sf.slice(); // копия перед resetAll()
// 5. Выбор файлов — ДОБАВЛЯЕТ в таблицу (не стирает, с дедупом)
fi.addEventListener('change', () => {
const incoming = Array.from(fi.files);
const seen = new Set(sf.map(f => f.name + '|' + f.size));
for (const f of incoming) {
if (!seen.has(f.name + '|' + f.size)) sf.push(f);
}
const d = new DataTransfer();
sf.forEach(f => d.items.add(f));
fi.files = d.files;
rr();
});
// 6. Цикл загрузки (fetch, не XHR — XHR давал RST на первом запросе)
for (let i = 0; i < total; i++) {
const fd = new FormData();
fd.append('files', f, f.name);
if (currentSid) fd.append('session', currentSid);
const resp = await fetch('/api/upload?_=' + Date.now(), { // ← fetch, не XHR
method: 'POST', body: fd
});
const data = await resp.json();
currentSid = data.session;
}
// 7. SSE-обработка
activeES = new EventSource('/api/process_stream/' + currentSid);
activeES.addEventListener('start', ...); // файл начат
activeES.addEventListener('done', ...); // файл готов
activeES.addEventListener('complete', ...); // всё готово
// 8. Скачивание ZIP/CSV — через fetch+Blob (не window.location!)
function downloadZip() {
if (!currentSid) return;
fetch('/api/download/' + currentSid)
.then(r => {
const disp = r.headers.get('Content-Disposition');
const m = disp && disp.match(/filename="?(.+?)"?$/);
return Promise.all([r.blob(), m ? m[1] : 'result.zip']);
})
.then(([blob, fname]) => {
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = fname;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
});
}
```
### 2. `site/routes/api_bp.py` — API эндпоинты
```
POST /api/upload — загрузка одного файла → {session_id}
GET /api/process_stream/<sid> — SSE: обработка с прогрессом
POST /api/process/<sid> — обработка без SSE (legacy)
GET /api/download/<sid> — скачать ZIP
GET /api/csv/<sid> — скачать CSV отдельно
```
**Критичные заголовки для SSE:**
```python
return Response(
stream_with_context(generate()),
content_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"X-Accel-Buffering": "no" # ← без этого nginx буферизует SSE
}
)
```
**Критично: GeneratorExit в SSE-генераторе:**
```python
def generate():
for idx, (fname, content) in enumerate(files):
try:
yield f"event: start\ndata: ...\n\n"
except GeneratorExit:
return # клиент отключился — не обрабатываем дальше
# ... обработка файла ...
try:
yield f"event: done\ndata: ...\n\n"
except GeneratorExit:
return
```
### 3. `site/routes/health_bp.py` — liveness probe
```python
@health_bp.route("/health")
def health():
return jsonify({"ok": True, "version": "0.0.1"})
```
**Обязательно для Штурвала.**
### 4. `site/app.py` — точка входа
**Критичные настройки:**
```python
VERSION = "0.0.1"
def create_app():
app = Flask(__name__)
app.config["VERSION"] = VERSION
app.config["MAX_CONTENT_LENGTH"] = 200 * 1024 * 1024 # 200 MB
from routes import register_routes
register_routes(app)
@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
return app
app = create_app()
```
---
## Пошаговая инструкция миграции
### Шаг 1: Скопировать скелет UI
Скопировать `site/templates/index.html` из drhider как основу.
Заменить:
- Заголовок (`<title>`, `.title`, `.card-header`)
- Текст описания
- `accept` в `<input type="file">` если другие форматы
- Логику в `uploadFiles()` — вызов своего API вместо `obfuscate_files`
### Шаг 2: Адаптировать API
Скопировать `site/routes/api_bp.py`, заменить:
- Название blueprint'а
- Логику в `process_stream()` — вызов своей функции вместо `obfuscate_files()`
- Формат SSE-событий если нужен другой
### Шаг 3: Health endpoint
Скопировать `site/routes/health_bp.py` как есть. Только версию поменять.
### Шаг 4: app.py
Скопировать `site/app.py`, заменить:
- `VERSION`
- `MAX_CONTENT_LENGTH` если нужен другой лимит
- Импорт своих blueprint'ов
### Шаг 5: session.py
Скопировать `site/session.py` как есть. Это in-memory хранилище загруженных файлов и результатов.
### Шаг 6: Интеграция бизнес-логики
Твоя функция обработки должна принимать тот же интерфейс что и `obfuscate_files`:
```python
def твоя_функция(files, **kwargs):
"""
Args:
files: list of (filename: str, content: bytes, mimetype: str)
Returns:
zip_bytes: bytes — ZIP-архив с результатами
csv_str: str — CSV с маппингом (или "")
"""
```
---
## Чек-лист перед деплоем
- [ ] `/health` возвращает `{"ok": true}`
- [ ] `MAX_CONTENT_LENGTH` достаточен для файлов
- [ ] `no_cache` after_request есть
- [ ] `X-Accel-Buffering: no` на SSE-эндпоинте
- [ ] `GeneratorExit` в каждом `yield` SSE-генератора
- [ ] `stream_with_context` оборачивает генератор
- [ ] `fetch('/health')` при загрузке страницы
- [ ] `beforeunload` → `resetAll()`
- [ ] `activeXHR` и `activeES` очищаются перед новым запуском
- [ ] `fetch` вместо XHR (XHR на первом запросе даёт RST)
- [ ] Выбор файлов — добавляет, не стирает (с дедупом)
- [ ] `Connection: close` — **НЕ ставить** (Waitress/PEP 3333 запрещает hop-by-hop)
---
## Ссылки на файлы-образцы
| Что | Где в drhider |
|-----|---------------|
| HTML/JS/CSS фронтенд | `site/templates/index.html` |
| API (upload + SSE + download) | `site/routes/api_bp.py` |
| Health probe | `site/routes/health_bp.py` |
| Точка входа Flask | `site/app.py` |
| In-memory сессии | `site/session.py` |
| Полная архитектура | `docs/ARCHITECTURE.md` |
| Решённые проблемы кластера | `PROBLEM-AND-SOLUTION.md` |
+90
View File
@@ -0,0 +1,90 @@
# Корневое решение: MSS clamping через Cilium
**Дата:** 2026-07-13 (обновлено после валидации Opus)
**Статус:** План для девопса
**Валидация:** [Opus response](../History/2026-07-13-opus-response-mss-clamping.md)
---
## Текущий костыль
`externalTrafficPolicy: Local` + `kubectl scale replicas=4` (сбрасывается Штурвалом каждые ~час).
## Корень проблемы
Geneve-туннель добавляет ~50 байт → превышение underlay MTU → ICMP Frag Needed блокируется → TCP stall 51с.
## Решение
Выставить корректный MTU в Cilium, чтобы пакеты с Geneve-заголовком всегда влазили.
**Важно:** `mtu` в cilium-config — это MTU **underlay-сети** (физической/VM-сети между нодами). Cilium сам вычитает 50 байт на Geneve:
```
mtu: X → туннель = X − 50 → поды = X − 50
```
## Что сделать девопсу
## Замер (выполнен 2026-07-13)
```bash
kubectl run mtu-test --rm -it --restart=Never --image=nicolaka/netshoot -- ping -c 2 -M do -s 1472 172.16.0.9
# → Message too large (1500 не проходит)
kubectl run mtu-test --rm -it --restart=Never --image=nicolaka/netshoot -- ping -c 2 -M do -s 1422 172.16.0.9
# → 1430 bytes from 172.16.0.9 (1450 проходит)
**Результат:** underlay MTU = **1450**, туннель = **1400**.
### Шаг 1: Выставить MTU в Cilium
```bash
kubectl edit configmap -n kube-system cilium-config
# mtu: <измеренное значение>
```
### Шаг 2: Применить изменения
```bash
# Перезапустить агенты Cilium (ConfigMap сам не применяется)
kubectl rollout restart ds/cilium -n kube-system
# Пересоздать поды приложения (ingress, drhider — чтобы veth получили новый MTU)
kubectl rollout restart deploy -n ingress shturval-ingress-controller-controller
```
### Шаг 3: Вернуть Cluster policy
```bash
kubectl patch svc -n ingress shturval-ingress-controller-controller \
-p '{"spec":{"externalTrafficPolicy":"Cluster"}}'
```
### Шаг 4: Верификация
```bash
# Проверить что Cilium применил MTU
kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep -i mtu
ip link show | grep -E 'cilium|geneve'
# Проверить MSS в SYN-пакетах
tcpdump -ni any 'tcp[tcpflags] & tcp-syn != 0' -vv | grep mss
```
Функционально:
- Загрузить файл > 5 MB через drhider — без паузы 51с
- F5 несколько раз — без ERR_TIMED_OUT
- Проверить Keycloak и другие сервисы
## Почему это грамотно
- Решает корневую причину (PMTU blackhole), не симптомы
- Не трогает логику Штурвала (ingress остаётся на 2 репликах)
- Работает для всего кластера
- Стандартная практика для туннельного CNI
## Примечания
- **iptables как fallback ненадёжен:** Cilium с eBPF может обходить netfilter FORWARD. Если нужно — правило на **всех 4 нодах**, но лучше через `cilium-config`.
- **Понижение MTU безопасно** (меньше = консервативнее). Максимум — микро-потеря throughput.
- **Не смешивать с проблемой реплик.** При `Cluster` 2 реплик достаточно — ручной scale не нужен.
+75
View File
@@ -0,0 +1,75 @@
# Почему Штурвал не рассчитан на underlay 1450
Ответы Соннета, 2026-07-14.
---
## Почему дефолтный MTU 1500, а не 1400
Штурвал — managed-платформа. Разработчики не знают MTU конкретной площадки.
- **Стандартный дата-центр:** underlay 1500 или Jumbo Frames 9000. Pod MTU 1500 + Geneve 50 = 1550 — легко влезает.
- **Nubes — исключение:** underlay 1450 (физика 1500 + свой VXLAN 50 байт для SDN). Штурвал не тестировался конкретно на этой площадке.
- **Снижать MTU глобально «на всякий случай» — плохо:** MTU 1400 снижает пропускную способность на ~7%. Для большинства клиентов это ненужная жертва.
Проблема флапает (при удачном размещении подов работает), поэтому жалобы могли не дойти до разработчиков.
**Вывод:** классическое «работает на наших серверах». Не баг в коде, а пробел в документации/онбординге.
---
## Почему underlay именно 1450
Это признак VXLAN:
```
Outer Ethernet: 14 б
Outer IP: 20 б
UDP: 8 б
VXLAN header: 8 б
─────────────────────
Итого: 50 б
```
Физика у Nubes — стандартные 1500. Поверх — VXLAN для SDN (мультитенантность, изоляция). Арендаторам остаётся 1500 − 50 = 1450.
**Почему не Jumbo Frames 9000:** Jumbo Frames требуют чтобы все коммутаторы, сетевые карты и топология поддерживали их. Настраивается при строительстве ЦОД. Скорее всего Nubes — арендованные мощности или исторически не включили. 9000 — привилегия крупных облаков (AWS, GCP), владеющих своим железом от и до.
**Как проверить на ноде:**
```bash
# Пройдёт если MTU ≥1500 (1472 + 28 заголовков = 1500)
ping -M do -s 1472 <ip_шлюза>
# Пройдёт при 1450 (1422 + 28 = 1450), упадёт на 1423
ping -M do -s 1422 <ip_шлюза>
ping -M do -s 1423 <ip_шлюза> # должен дать "Frag needed"
```
---
## Почему 1400 а не ниже
Geneve-туннели **независимы**. Каждый переход — отдельная инкапсуляция:
```
Pod отправляет 1400 байт
→ Нода A → Нода B: 1400 + 50 (Geneve) = 1450 ✅
→ Нода B → Нода C: снова 1400 + 50 = 1450 ✅
```
Второй туннель берёт **расшифрованный** пакет (снова 1400) и заново оборачивает. Не накапливается. Если бы накапливалось — нужно было бы 1350 (1350+50+50=1450). Но нет — 1400 достаточно для любого количества переходов.
---
## Что сказать девопсам
> Привет! Столкнулся с проблемой при работе managed-сервисов на Штурвале.
>
> У Nubes underlay MTU = 1450 (стандарт 1500 − 50 байт своего VXLAN). При дефолтном Cilium MTU 1500 любой cross-node трафик через Geneve даёт пакет 1550 — не влазит, HTTP POST зависает на ~51с.
>
> Решение: `mtu: 1400` в Cilium при создании кластера. 1400 + 50 (Geneve) = 1450 — ровно в канал. Даже два Geneve-перехода — достаточно (туннели независимы).
>
> Трогать существующие кластеры не предлагаю — там риск. Но можно добавить в шаблон/чеклист для новых кластеров на Nubes.
Тон — коллегиальный. Не «вы облажались», а «нашёл нюанс, вот решение, давайте зафиксируем».
+29 -12
View File
@@ -8,8 +8,10 @@
import io
import csv
import os
import zipfile
import re
import time
from typing import Dict, List, Tuple
from .config import ENTITY_PATTERNS, COMPANY_PATTERN
@@ -18,35 +20,50 @@ from .config import ENTITY_PATTERNS, COMPANY_PATTERN
def build_zip(files: List[Tuple[str, bytes]], mapping_csv: str = "") -> bytes:
"""Собрать ZIP-архив с обфусцированными файлами.
В архив добавляются:
- Все обфусцированные файлы (с оригинальными именами)
- mapping.csv — таблица соответствия оригинал→замена (если не пустая)
В архив добавляются ТОЛЬКО обфусцированные файлы (с оригинальными именами).
mapping.csv в архив НЕ кладётся — это таблица соответствия оригинал→замена,
т.е. ключ расшифровки (секретные данные). Он хранится/скачивается отдельно
(/api/csv), чтобы нельзя было расшифровать архив, получив один ZIP.
Имена файлов в архиве — UTF-8 (бит 11 в flag_bits).
Args:
files: [(filename, content_bytes), ...]
mapping_csv: Строка CSV с таблицей замен (опционально)
mapping_csv: Строка CSV с таблицей замен (опционально, НЕ добавляется в архив)
Returns:
Бинарное содержимое ZIP-архива
"""
buf = io.BytesIO()
# Дедупликация имён: имя+одинаковый контент → пропуск; имя+разный контент → суффикс
seen: Dict[str, bytes] = {}
def _resolve_name(name: str, content: bytes) -> str:
if name not in seen:
seen[name] = content
return name
if seen[name] == content:
return None # точный дубль — пропускаем
base, ext = os.path.splitext(name)
n = 2
while f"{base}_{n}{ext}" in seen:
n += 1
resolved = f"{base}_{n}{ext}"
seen[resolved] = content
return resolved
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
# Добавляем обфусцированные файлы
for fname, content in files:
info = zipfile.ZipInfo(fname)
out_name = _resolve_name(fname, content)
if out_name is None:
continue
info = zipfile.ZipInfo(out_name)
info.date_time = time.localtime(time.time() + 3 * 3600)[:6] # MSK (UTC+3)
info.flag_bits |= 0x800 # Флаг: имя файла в UTF-8
zf.writestr(info, content)
# Добавляем mapping.csv с BOM (для корректного открытия в Excel)
if mapping_csv:
zf.writestr(
"mapping.csv",
'\ufeff'.encode('utf-8') + mapping_csv.encode('utf-8'),
)
return buf.getvalue()
-69
View File
@@ -1,69 +0,0 @@
"""
Контрольные суммы для ИНН (10 и 12 знаков) и ОГРН.
Используются генераторами фиктивных значений для создания
синтаксически корректных номеров.
"""
def checksum_inn10(inn: str) -> str:
"""Контрольная сумма для 10-значного ИНН (юридические лица).
Алгоритм:
1. Умножаем первые 9 цифр на коэффициенты [2,4,10,3,5,9,4,6,8]
2. Суммируем, берём (сумма % 11) % 10
Args:
inn: 9 цифр ИНН (без контрольной суммы)
Returns:
Одна цифра контрольной суммы
"""
coeffs = [2, 4, 10, 3, 5, 9, 4, 6, 8]
s = sum(int(inn[i]) * coeffs[i] for i in range(9))
return str((s % 11) % 10)
def checksum_inn12(inn: str) -> str:
"""Контрольные суммы для 12-значного ИНН (физические лица).
Алгоритм (две контрольные цифры):
1. n1: умножаем первые 10 цифр на [7,2,4,10,3,5,9,4,6,8],
берём (сумма % 11) % 10
2. n2: добавляем n1 к числу, умножаем 11 цифр на
[3,7,2,4,10,3,5,9,4,6,8], берём (сумма % 11) % 10
Args:
inn: 10 цифр ИНН (без контрольных сумм)
Returns:
Две цифры контрольных сумм (n1 + n2)
"""
c1 = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8]
c2 = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8]
# Первая контрольная цифра
s1 = sum(int(inn[i]) * c1[i] for i in range(10))
n1 = (s1 % 11) % 10
# Вторая контрольная цифра (с учётом первой)
inn2 = inn + str(n1)
s2 = sum(int(inn2[i]) * c2[i] for i in range(11))
n2 = (s2 % 11) % 10
return str(n1) + str(n2)
def checksum_ogrn(ogrn: str) -> str:
"""Контрольная сумма для ОГРН (13 знаков).
Алгоритм: первые 12 цифр как число % 11, затем % 10.
Args:
ogrn: 12 цифр ОГРН (без контрольной суммы)
Returns:
Одна цифра контрольной суммы
"""
s = int(ogrn) % 11
return str(s % 10)
+30 -2
View File
@@ -41,8 +41,8 @@ COMPANY_PATTERN = re.compile(
# ФИО: Фамилия + инициалы (Иванов И.И.) или полное имя (Иванов Иван Иванович)
PERSON_PATTERN = re.compile(
r'\b[А-Я][а-я]+\s+[А-Я]\.[А-Я]\.'
r'|\b[А-Я][а-я]+\s+[А-Я][а-я]+\s+[А-Я][а-я]+',
r'\b[А-Я][а-я]+[\s\xa0]+[А-Я]\.[А-Я]\.'
r'|\b[А-Я][а-я]+[\s\xa0]+[А-Я][а-я]+[\s\xa0]+[А-Я][а-я]+',
)
# ═══════════════════════════════════════════════════════════════════════════
@@ -86,3 +86,31 @@ RU_STREETS = [
# Фиктивные домены для генерации email
FAKE_DOMAINS = ["example.ru", "mail.test", "company.local", "org.example.ru"]
# ═══════════════════════════════════════════════════════════════════════════
# Пулы для гибридных токенов (шаблон + номер)
# ═══════════════════════════════════════════════════════════════════════════
# Существительные для названий компаний (20)
COMPANY_NOUNS = [
"Технология", "Прогресс", "Гарант", "Стандарт", "Импульс",
"Вектор", "Сфера", "Альянс", "Синтез", "Меридиан",
"Спектр", "Формат", "Модуль", "Ресурс", "Платформа",
"Система", "Комплекс", "Проект", "Решение", "Сервис",
]
# Города для адресов (20)
CITY_POOL = [
"Москва", "Санкт-Петербург", "Новосибирск", "Екатеринбург",
"Казань", "Нижний_Новгород", "Челябинск", "Самара", "Омск",
"Ростов-на-Дону", "Уфа", "Красноярск", "Воронеж", "Пермь",
"Волгоград", "Краснодар", "Саратов", "Тюмень", "Иркутск", "Хабаровск",
]
# Улицы для адресов (20)
STREET_POOL = [
"Ленина", "Мира", "Пушкина", "Гагарина", "Советская",
"Кирова", "Октябрьская", "Молодёжная", "Садовая", "Центральная",
"Школьная", "Лесная", "Полевая", "Строителей", "Набережная",
"Парковая", "Солнечная", "Зелёная", "Юбилейная", "Весенняя",
]
+269 -179
View File
@@ -1,95 +1,243 @@
"""
Извлечение текста из документов разных форматов.
Извлечение текста из документов — все форматы → Markdown.
Поддерживает:
- .docx (через python-docx)
- .pdf (через pdfplumber)
- .doc (бинарный — не парсится)
- .txt и прочие (как UTF-8)
- .docx → MD (python-docx: стили, жирный/курсив, таблицы)
- .pdf → MD (pdfplumber: текст + таблицы, без форматирования)
- .doc → MD (LibreOffice headless: .doc → .docx → штатный docx_to_markdown)
- .txt и прочие → как есть
Также содержит:
- _expand_zips — распаковка ZIP с защитой от ZIP-бомб
- _convert_pdfs_to_docx — конвертация PDF → DOCX
- expand_zips — распаковка ZIP с защитой от ZIP-бомб
"""
import io
import os
import zipfile
import tempfile
import shutil
import subprocess
import logging
from typing import Dict, List, Tuple, Optional
from typing import List, Tuple
log = logging.getLogger("drhider")
def extract_text(fname: str, content: bytes, ctype: str) -> Tuple[str, Optional[object]]:
"""Извлечь текст из одного файла.
# ═══════════════════════════════════════════════════════════════════════════
# DOCX → Markdown
# ═══════════════════════════════════════════════════════════════════════════
def docx_to_markdown(content: bytes) -> str:
"""Конвертировать DOCX в Markdown с сохранением базового форматирования.
Маппинг стилей:
- Heading 1/2/3 → # / ## / ###
- Bold → **текст**
- Italic → *текст*
- Таблицы → | col1 | col2 | (с разделителем)
Args:
content: Бинарное содержимое .docx файла
Returns:
Строка в формате Markdown
"""
from docx import Document
doc = Document(io.BytesIO(content))
lines = []
for para in doc.paragraphs:
if not para.text.strip():
lines.append("") # Пустая строка — разделитель абзацев
continue
text = para.text
# Применяем форматирование из runs (bold, italic)
if para.runs:
formatted_parts = []
for run in para.runs:
part = run.text
if run.bold:
part = f"**{part}**"
if run.italic:
part = f"*{part}*"
formatted_parts.append(part)
text = "".join(formatted_parts)
# Стиль абзаца
style_name = para.style.name if para.style else ""
if style_name.startswith("Heading"):
# Heading 1 → #, Heading 2 → ##, ...
try:
level = int(style_name.split()[-1])
except (ValueError, IndexError):
level = 1
prefix = "#" * min(level, 6)
lines.append(f"{prefix} {text}")
else:
lines.append(text)
# ── Таблицы ──
for table in doc.tables:
lines.append("") # Отступ перед таблицей
for ri, row in enumerate(table.rows):
cells = [cell.text.replace("\n", " ").strip() for cell in row.cells]
lines.append("| " + " | ".join(cells) + " |")
# Разделитель после первой строки (заголовка таблицы)
if ri == 0:
lines.append("|" + "|".join(["---"] * len(cells)) + "|")
lines.append("") # Отступ после таблицы
return "\n".join(lines)
# ═══════════════════════════════════════════════════════════════════════════
# PDF → Markdown
# ═══════════════════════════════════════════════════════════════════════════
def pdf_to_markdown(content: bytes) -> str:
"""Конвертировать PDF в Markdown (только текст + таблицы, без форматирования).
pdfplumber не различает стили (bold/italic/heading), поэтому
всё извлекается как plain text.
Args:
content: Бинарное содержимое .pdf файла
Returns:
Строка в формате Markdown (plain text + таблицы)
"""
import pdfplumber
lines = []
with pdfplumber.open(io.BytesIO(content)) as pdf:
for page in pdf.pages:
# ── Текст страницы ──
text = page.extract_text()
if text:
lines.append(text)
lines.append("")
# ── Таблицы ──
# Быстрый фильтр: если на странице нет линий/кривых/прямоугольников,
# таблиц нет (pdfplumber ищет таблицы только по векторным линиям).
# Включая page.rects — таблицы на цветном фоне (заливка).
# Пропускаем extract_tables() на «чистых» страницах (скан/текст) — это
# основная стоимость на сканах, где линий нет.
if page.lines or page.curves or page.rects:
tables = page.extract_tables()
else:
tables = []
for table in tables:
if not table:
continue
# Чистим строки таблицы
clean_rows = [
[str(c or "").strip() for c in (row or [])]
for row in table
]
clean_rows = [r for r in clean_rows if any(r)]
if not clean_rows:
continue
# Выводим таблицу в MD
for ri, row in enumerate(clean_rows):
lines.append("| " + " | ".join(row) + " |")
if ri == 0:
lines.append("|" + "|".join(["---"] * len(row)) + "|")
lines.append("")
return "\n".join(lines)
# ═══════════════════════════════════════════════════════════════════════════
# DOC → Markdown (через сервис liberta)
# ═══════════════════════════════════════════════════════════════════════════
def doc_to_markdown(content: bytes) -> str:
"""Конвертировать бинарный .doc → .docx (сервис liberta) → Markdown.
Отправляет .doc на HTTP-сервис конвертации, получает .docx,
затем прогоняет через штатный docx_to_markdown().
Args:
content: Бинарное содержимое .doc файла
Returns:
Строка в формате Markdown (или сообщение об ошибке)
"""
import httpx
url = os.environ.get(
"CONVERT_SERVICE_URL",
"https://liberta.containerk8s.dev.nubes.ru/convert"
)
try:
with httpx.Client(timeout=120) as client:
resp = client.post(
url,
files={"file": ("input.doc", content, "application/msword")}
)
if resp.status_code == 200:
return docx_to_markdown(resp.content)
err = resp.json().get("error", f"HTTP {resp.status_code}")
log.warning("liberta conversion failed: %s", err)
return f"[DOC — conversion failed: {err}]"
except httpx.TimeoutException:
return "[DOC — conversion timed out (>120s)]"
except Exception as e:
log.warning("liberta error: %s", e)
return f"[DOC — conversion error: {e}]"
# ═══════════════════════════════════════════════════════════════════════════
# Единая точка входа
# ═══════════════════════════════════════════════════════════════════════════
def extract_text(fname: str, content: bytes, ctype: str = "") -> str:
"""Извлечь текст из одного файла → Markdown.
Args:
fname: Имя файла (с расширением)
content: Бинарное содержимое файла
ctype: MIME-тип (не используется в текущей версии)
ctype: MIME-тип (зарезервировано, не используется)
Returns:
(text, docx_document_or_None):
text — извлечённый текст (строка)
doc — объект python-docx Document или None
Строка в формате Markdown
"""
doc = None
text = ""
ext = os.path.splitext(fname)[1].lower()
# ── .docx: извлекаем текст + сохраняем Document для замен с форматированием ──
# ── .docx: полный Markdown с форматированием ──
if ext == '.docx':
try:
from docx import Document
return docx_to_markdown(content)
except ImportError:
# Если python-docx не установлен — читаем как plain text
text = content.decode('utf-8', errors='replace')
return text, None
return content.decode('utf-8', errors='replace')
doc = Document(io.BytesIO(content))
# Собираем текст из параграфов
paragraphs = [p.text for p in doc.paragraphs]
# Добавляем текст из таблиц
for table in doc.tables:
for row in table.rows:
row_text = " | ".join(cell.text for cell in row.cells)
paragraphs.append(row_text)
text = "\n".join(paragraphs)
# ── .pdf: извлекаем текст через pdfplumber ──
# ── .pdf: текст + таблицы ──
elif ext == '.pdf':
try:
import pdfplumber
return pdf_to_markdown(content)
except ImportError:
text = content.decode('utf-8', errors='replace')
return text, None
return content.decode('utf-8', errors='replace')
parts = []
with pdfplumber.open(io.BytesIO(content)) as pdf:
for page in pdf.pages:
# Текст страницы
t = page.extract_text()
if t:
parts.append(t)
# Текст из таблиц
for table in page.extract_tables():
for row in table:
row_str = " | ".join(str(c) if c else "" for c in row)
parts.append(row_str)
text = "\n".join(parts)
# ── .doc: бинарный формат — без libreoffice не парсим ──
# ── .doc: бинарный → LibreOffice → DOCX → Markdown ──
elif ext == '.doc':
text = "[DOC binary — not parsed]"
return text, None
return doc_to_markdown(content)
# ── .txt и прочие: читаем как UTF-8 ──
# ── .txt и прочие ──
else:
text = content.decode('utf-8', errors='replace')
return content.decode('utf-8', errors='replace')
return text, doc
# ═══════════════════════════════════════════════════════════════════════════
# ZIP-распаковка (без изменений)
# ═══════════════════════════════════════════════════════════════════════════
def expand_zips(files: List[Tuple[str, bytes, str]]) -> List[Tuple[str, bytes, str]]:
"""Распаковать ZIP-файлы в списке, заменив их содержимым.
@@ -107,154 +255,96 @@ def expand_zips(files: List[Tuple[str, bytes, str]]) -> List[Tuple[str, bytes, s
Returns:
Новый список файлов (ZIP раскрыты, остальные как есть)
"""
result: List[Tuple[str, bytes, str]] = []
# Лимиты защиты от ZIP-бомб (глобально на весь вызов)
MAX_FILES_IN_ARCHIVE = 500
MAX_UNCOMPRESSED = 500 * 1024 * 1024 # 500 MB
MAX_RATIO = 100
result: List[Tuple[str, bytes, str]] = []
queue: List[Tuple[str, bytes, str]] = list(files)
total_uncompressed = 0
def _decode_name(name: str, info) -> str:
"""Декодировать имя из ZIP.
Если флаг UTF-8 (bit 11) выставлен — имя уже корректное (берём как есть).
Иначе zipfile декодировал имя как CP437. Пытаемся восстановить исходные
байты (encode cp437) и декодировать:
1) как UTF-8 — если результат в кириллице/ASCII (Info-ZIP/Linux-зипы
пишут UTF-8 без флага; случайная коллизия CP866→UTF-8 отсекается
проверкой диапазона);
2) как CP866 — реальные 1С-выгрузки.
"""
def _cyr_ok(s: str) -> bool:
# Все символы — ASCII или кириллица (U+0400–U+04FF)
return all(ord(c) < 128 or 0x0400 <= ord(c) <= 0x04FF for c in s)
if not (info.flag_bits & 0x800) and any(ord(c) > 127 for c in name):
try:
raw = name.encode("cp437")
except UnicodeEncodeError:
return name
# 1) UTF-8 (Info-ZIP/Linux без флага)
try:
dec = raw.decode("utf-8")
if _cyr_ok(dec):
return dec
except UnicodeDecodeError:
pass
# 2) CP866 (1С)
try:
return raw.decode("cp866")
except UnicodeDecodeError:
pass
return name
while queue:
fname, content, ctype = queue.pop(0)
for fname, content, ctype in files:
# Пропускаем не-ZIP
if not fname.lower().endswith('.zip'):
result.append((fname, content, ctype))
continue
try:
with zipfile.ZipFile(io.BytesIO(content)) as zf:
# Проверка: не более 500 файлов в архиве
if len(zf.infolist()) > 500:
infos = [i for i in zf.infolist() if not i.is_dir()]
if len(infos) > MAX_FILES_IN_ARCHIVE:
log.warning("ZIP too many files, skipping: %s", fname)
result.append((fname, content, ctype))
continue
total_uncompressed = 0
for info in zf.infolist():
# Пропускаем директории
if info.is_dir():
continue
# Проверка на ZIP-бомбу: ratio
# Проверяем лимиты ДО чтения содержимого (защита от бомб)
bomb = False
for info in infos:
if info.compress_size > 0:
ratio = info.file_size / info.compress_size
if ratio > 100: # Файл сжимается более чем в 100 раз
log.warning(
"ZIP bomb ratio %.0f:1, skipping: %s", ratio, fname
)
result.append((fname, content, ctype))
break # Пропускаем весь архив
if ratio > MAX_RATIO:
bomb = True
break
if total_uncompressed + info.file_size > MAX_UNCOMPRESSED:
bomb = True
break
# Декодируем имя файла: cp437 → utf-8
name = info.filename
try:
name = name.encode("cp437").decode("utf-8", errors="replace")
except (UnicodeDecodeError, UnicodeEncodeError):
pass
if bomb:
log.warning("ZIP bomb/limit, skipping: %s", fname)
result.append((fname, content, ctype))
continue
# Защита от path traversal
for info in infos:
name = _decode_name(info.filename, info)
name = os.path.basename(name)
if (
not name
or name.endswith("/")
or ".." in name
or "/" in name
or "\\" in name
):
if not name:
continue
# Читаем и проверяем накопительный размер
inner_data = zf.read(info)
total_uncompressed += len(inner_data)
if total_uncompressed > 500 * 1024 * 1024: # 500 MB
log.warning(
"ZIP uncompressed limit exceeded, stopping: %s", fname
)
break
result.append((name, inner_data, ""))
# Вложенные ZIP добавляем в очередь на повторную распаковку
queue.append((name, inner_data, ""))
except Exception as e:
log.warning("Failed to expand ZIP %s: %s", fname, e)
result.append((fname, content, ctype))
return result
def convert_pdfs_to_docx(
files: List[Tuple[str, bytes, str]],
) -> List[Tuple[str, bytes, str]]:
"""Конвертировать PDF-файлы в DOCX через pdfplumber.
Не-PDF файлы проходят без изменений.
При совпадении имён к имени добавляется суффикс '_из_pdf'.
Конвертация:
- Текст страницы → параграфы DOCX
- Таблицы → таблицы DOCX со стилем 'Table Grid'
Args:
files: [(filename, content_bytes, content_type), ...]
Returns:
Новый список файлов (PDF заменены на DOCX)
"""
import pdfplumber
from docx import Document as DocxDocument
result: List[Tuple[str, bytes, str]] = []
existing_names = {f[0] for f in files}
for fname, content, ctype in files:
# Пропускаем не-PDF
if not fname.lower().endswith('.pdf'):
result.append((fname, content, ctype))
continue
try:
doc = DocxDocument()
with pdfplumber.open(io.BytesIO(content)) as pdf:
for page in pdf.pages:
# ── Таблицы ──
tables = page.extract_tables()
for table in tables:
if not table:
continue
# Чистим строки: убираем полностью пустые
rows = [
[str(c or "").strip() for c in (row or [])]
for row in table
]
rows = [r for r in rows if any(r)]
if rows:
t = doc.add_table(rows=len(rows), cols=len(rows[0]))
t.style = 'Table Grid'
for ri, row in enumerate(rows):
for ci, cell_text in enumerate(row):
t.rows[ri].cells[ci].text = cell_text
# ── Текст ──
text = page.extract_text()
if text:
for line in text.split('\n'):
line = line.strip()
if line:
doc.add_paragraph(line)
# Сохраняем DOCX в буфер
buf = io.BytesIO()
doc.save(buf)
# Формируем новое имя
new_name = fname[:-4] + '.docx'
if new_name in existing_names:
new_name = fname[:-4] + '_из_pdf.docx'
existing_names.add(new_name)
result.append((new_name, buf.getvalue(), ctype))
except Exception as e:
log.warning("PDF→DOCX error for %s: %s", fname, e)
# При ошибке — оставляем оригинальный PDF
result.append((fname, content, ctype))
return result
-37
View File
@@ -1,37 +0,0 @@
"""
Маппинг типов сущностей на генераторы фиктивных значений.
ENTITY_GENERATORS используется в TwoPassObfuscator._scan_regex()
для автоматического выбора генератора по типу найденной сущности.
"""
from .phone import generate_phone
from .email import generate_email
from .inn import generate_inn10, generate_inn12
from .ogrn import generate_ogrn
from .kpp import generate_kpp
from .bik import generate_bik
from .accounts import generate_rs, generate_ks
from .passport import generate_passport
from .fallback import generate_fallback
# ═══════════════════════════════════════════════════════════════════════════
# Маппинг: entity_type → функция-генератор
# Ключи соответствуют ключам в config.ENTITY_PATTERNS
# ═══════════════════════════════════════════════════════════════════════════
ENTITY_GENERATORS = {
"phone": generate_phone,
"email": generate_email,
"inn_ul": generate_inn10, # 10-значный ИНН → юрлица
"inn_fl": generate_inn12, # 12-значный ИНН → физлица
"ogrn": generate_ogrn,
"kpp": generate_kpp,
"bik": generate_bik,
"rs": generate_rs, # расчётный счёт
"ks": generate_ks, # корреспондентский счёт
"passport": generate_passport,
}
# Fallback-генератор — экспортируется отдельно для использования в scanner/obfuscator
FALLBACK_GENERATOR = generate_fallback

Some files were not shown because too many files have changed in this diff Show More