Python 3.12+
Полная типизация обязательна.
Избегать использования Any.
Желательно не более 40 строк.
Максимум около 80 строк.
Если функция становится длиннее — разделить её.
Желательно до 300 строк.
Максимум около 500 строк.
Использовать понятные имена.
Хорошо:
load_image()
split_grid()
rotate_image()
add_border()
Плохо:
proc()
run2()
tmp()
Не использовать глобальные переменные.
Не использовать Singleton.
Все значения должны быть вынесены в именованные константы либо параметры функций.
Использовать исключения.
CLI должен завершаться ненулевым кодом возврата.
Сообщения должны быть понятны пользователю.
Использовать logging.
Не использовать print() для отладки.
Следовать PEP8.
Использовать Ruff.
Не оставлять:
Функции-корректоры (LLM, VLM, fixups) изменяют ParsedBlock.content на месте и возвращают None.
def fix_ocr_errors(blocks: list[ParsedBlock]) -> None: # ← None, не list
for block in blocks:
if block.label == "formula":
block.content = _fix_formula_errors(block.content)
Протокол:
class Corrector(Protocol):
def __call__(self, block: ParsedBlock) -> None: ...
src/env.pyЛюбой модуль, читающий .env, использует from src.env import load_env. Не дублировать _load_env() в каждом файле.
def _get_ocr_engine(name: str):
if name == "surya":
return surya_ocr_image, surya_ocr_batch, "Surya 2 VLM"
return ocr_image, None, "PP-StructureV3"
Возвращает (single_fn, batch_fn, display_name). Новый движок — добавить elif-ветку.
Допустимы ленивые синглтоны для тяжёлых объектов (модели, pipeline’ы), но через фабричную функцию:
_engine: OcrEngine | None = None
def ocr_image(path: str) -> OcrPageResult:
global _engine
if _engine is None:
_engine = OcrEngine()
return _engine.process(path)
Не использовать модульные глобальные переменные напрямую.
После реализации крупной фичи — прогнать аудит по .ai/SOLID_AUDIT.md. Не накапливать технический долг.