# Наработанные навыки
## OCR / PaddleOCR
- **PP-StructureV3**: конфигурация через YAML-файл, замена моделей распознавания (`eslav_PP-OCRv5_mobile_rec` для русского, `PP-OCRv6_medium_rec` для английского), смена детектора `PP-OCRv5_server_det` → `PP-OCRv6_medium_det` без апгрейда `paddlepaddle`
- **Архитектура детекторов**: DB (Differentiable Binarization) vs DB++ (RepLKFPN, ASF, DCN), PPLCNetV4, EncoderWithLightSVTR
- **Модельный зоопарк**: `eslav` vs `cyrillic` (обе mobile, разница ~1%), `PP-OCRv6_medium_rec` (46 латинских + CN/JP/EN, без кириллицы), PP-FormulaNet для формул, SLANeXt для таблиц
- **Параметры детектора**: `thresh` (порог карты вероятности), `box_thresh` (фильтр bbox), `unclip_ratio` (расширение контура), `max_side_limit` (обрезает upscale)
- **Источники моделей**: PaddlePaddle CDN, HuggingFace Hub, modelscope — fallback-цепочка в `paddlex`
## Surya 2 / llama.cpp
- **CPU-оптимизации**: `SURYA_INFERENCE_KEEP_ALIVE=true` (-23s повторный старт), `CTX_PER_SLOT=8192` (-30% RAM), `PARALLEL=1` (-80% слотов), `pre-scale=1056px` (~96 DPI), `-t 8 --threads-batch 8` (стабильная загрузка CPU)
- **Health-check + retry**: проверка `http://host:port/health`, пересоздание engine при падении llama-server, `lru_cache.cache_clear()` для сброса
## OpenCV
- **Unwarping**: Canny → findContours → approxPolyDP → _order_points → getPerspectiveTransform → warpPerspective с порогом `min_area ≥ 30%` (чтобы не детектить внутренние таблицы как страницу)
- **Denoising**: `fastNlMeansDenoisingColored` — Non-Local Means. Полезен для шумных сканов, NOP для чистых. НЕ вреден для нейросетевого OCR
- **Бинаризация**: `adaptiveThreshold` (Gaussian, окно 31) вредна для нейросетевого OCR — жёсткие границы рвут буквы, создают ложные контуры. Нейросети обучены на естественных изображениях, не на бинарных
- **Upscale**: `cv2.resize(INTER_CUBIC)` бесполезен, если OCR-движок обрезает `max_side_limit`
## Архитектура Python / SOLID
- **Вынос общих моделей**: `ParsedBlock`, `OcrPageResult` → отдельный `models.py` — 7 модулей отвязаны от `paddle_engine`
- **Protocol → Callable**: если Protocol никем не реализован — YAGNI. `Callable[[ParsedBlock], None]` проще
- **lru_cache вместо глобальных синглтонов**: `@functools.lru_cache(maxsize=N)` вместо `_engine: X | None = None` + `global _engine`
- **idempotent env loader**: `load_env()` с внутренним guard'ом — не нужны внешние `_ENV_LOADED` флаги
- **Shared API client**: `call_qwen_vlm()` + `image_to_base64()` в `qwen_client.py` — общий для OCR-движка и корректора
## CLI / Typer
- **Annotated-параметры**: `Annotated[Path, typer.Option("--input", "-i")]` — минимум бойлерплейта
- **Параметры по умолчанию**: обязательные без default, опциональные с `= None`, булевы через `--flag / --no-flag`
- **Collapsible --help в README**: `--help
...