Преглед на файлове

polishing the code and docs

master
Evgeniy Ierusalimov преди 5 дни
родител
ревизия
d1c9afdfdf
променени са 7 файла, в които са добавени 207 реда и са изтрити 82 реда
  1. 57
    0
      .ai/CODE_STYLE.md
  2. 60
    0
      .ai/SOLID_AUDIT.md
  3. 67
    0
      .ai/VLM_OFFLOAD.md
  4. 6
    2
      .env.dist
  5. 0
    49
      3_1200_02_surya.md
  6. 0
    31
      3_surya.md
  7. 17
    0
      README.md

+ 57
- 0
.ai/CODE_STYLE.md Целия файл

@@ -99,3 +99,60 @@ CLI должен завершаться ненулевым кодом возвр
99 99
 - закомментированный код;
100 100
 - неиспользуемый код;
101 101
 - TODO без объяснения.
102
+
103
+---
104
+
105
+## Паттерны, выработанные в проекте
106
+
107
+### Корректоры: мутация in-place
108
+
109
+Функции-корректоры (LLM, VLM, fixups) изменяют `ParsedBlock.content` на месте и возвращают `None`.
110
+
111
+```python
112
+def fix_ocr_errors(blocks: list[ParsedBlock]) -> None:  # ← None, не list
113
+    for block in blocks:
114
+        if block.label == "formula":
115
+            block.content = _fix_formula_errors(block.content)
116
+```
117
+
118
+Протокол:
119
+
120
+```python
121
+class Corrector(Protocol):
122
+    def __call__(self, block: ParsedBlock) -> None: ...
123
+```
124
+
125
+### Общие переменные окружения через `src/env.py`
126
+
127
+Любой модуль, читающий `.env`, использует `from src.env import load_env`. Не дублировать `_load_env()` в каждом файле.
128
+
129
+### Multi-engine через фабрику
130
+
131
+```python
132
+def _get_ocr_engine(name: str):
133
+    if name == "surya":
134
+        return surya_ocr_image, surya_ocr_batch, "Surya 2 VLM"
135
+    return ocr_image, None, "PP-StructureV3"
136
+```
137
+
138
+Возвращает `(single_fn, batch_fn, display_name)`. Новый движок — добавить elif-ветку.
139
+
140
+### Синглтоны для ML-моделей
141
+
142
+Допустимы ленивые синглтоны для тяжёлых объектов (модели, pipeline'ы), но через фабричную функцию:
143
+
144
+```python
145
+_engine: OcrEngine | None = None
146
+
147
+def ocr_image(path: str) -> OcrPageResult:
148
+    global _engine
149
+    if _engine is None:
150
+        _engine = OcrEngine()
151
+    return _engine.process(path)
152
+```
153
+
154
+Не использовать модульные глобальные переменные напрямую.
155
+
156
+### Периодический SOLID/DRY/KISS/YAGNI аудит
157
+
158
+После реализации крупной фичи — прогнать аудит по `.ai/SOLID_AUDIT.md`. Не накапливать технический долг.

+ 60
- 0
.ai/SOLID_AUDIT.md Целия файл

@@ -0,0 +1,60 @@
1
+# SOLID/DRY/KISS/YAGNI Audit
2
+
3
+Периодический аудит кода по принципам SOLID, DRY, KISS, YAGNI.
4
+
5
+## Когда запускать
6
+
7
+- После реализации крупной фичи (>200 строк нового кода)
8
+- Перед финальным тестированием этапа
9
+- При рефакторинге
10
+- Когда файл превышает 300 строк
11
+
12
+## Категории проверки
13
+
14
+### YAGNI — код, который не используется
15
+- Функции/классы без вызовов
16
+- Импорты без использования
17
+- Параметры, которые всегда передаются с одним значением
18
+- Return-значения, которые игнорируются ВСЕМИ вызывающими
19
+
20
+### DRY — дублирование
21
+- Идентичные блоки кода в разных файлах (ctrl-c/ctrl-v)
22
+- Одинаковая логика с разными именами
23
+- Дублирование `_load_env`, шаблонов, моделей данных
24
+- Повторяющиеся импорты внутри функции/модуля
25
+
26
+### KISS — излишняя сложность
27
+- Вложенность >3 уровней
28
+- Функции >40 строк без явной причины
29
+- Сложная арифметика там, где есть библиотечная функция
30
+- Типы `Any` там, где известна структура
31
+- Magic numbers вместо именованных констант
32
+
33
+### SRP — смешение ответственности
34
+- Функция делает ≥3 логически разных действий
35
+- Модуль импортирует несвязанные библиотеки
36
+- Класс/функция и генерирует данные, и форматирует, и сохраняет
37
+
38
+### Связность (Coupling)
39
+- Глобальные переменные-синглтоны
40
+- Прямые импорты конкретных реализаций вместо протоколов
41
+- Жёсткая привязка к путям в ФС (`/home/user/...`)
42
+
43
+### Обработка ошибок
44
+- Нет try/except вокруг внешних вызовов (сеть, диск, API)
45
+- `assert` для control flow
46
+- Строковые срезы без проверки на `find() == -1`
47
+
48
+## Процесс
49
+
50
+1. Пройти по всем `src/*.py` сверху вниз
51
+2. Для каждого файла отметить проблемы с номером строки
52
+3. Сгруппировать по категориям
53
+4. Приоритезировать: bug > DRY > YAGNI > style
54
+5. Исправлять от высокого приоритета к низкому
55
+
56
+## Что НЕ является проблемой (не чинить)
57
+
58
+- Ленивые импорты внутри функций (для избежания циклических зависимостей)
59
+- Синглтоны для тяжёлых объектов (ML-модели, pipeline'ы) — оправдано производительностью
60
+- Кодогенерация/шаблоны — читаемость важнее «чистоты» строк

+ 67
- 0
.ai/VLM_OFFLOAD.md Целия файл

@@ -0,0 +1,67 @@
1
+# Offload to Cloud VLM
2
+
3
+## Когда применять
4
+
5
+Если локальное железо не может выполнить задачу:
6
+- Нет GPU, CPU-инференс >5 минут на страницу
7
+- Модель не помещается в RAM
8
+- Точность локальной модели недостаточна, а fine-tune невозможен
9
+
10
+**И есть подписка с доступом к VLM по API** (OpenCode Go, OpenAI, Anthropic).
11
+
12
+Тогда локальный OCR делает быструю «грязную» работу, а сомнительные регионы отправляются в cloud VLM.
13
+
14
+## Паттерн
15
+
16
+```
17
+Локальный OCR (быстрый, среднее качество)
18
+    │
19
+    ├─ confidence > 0.8 → OK, сохраняем как есть
20
+    │
21
+    └─ confidence < 0.8 → вырезаем bbox из изображения
22
+            │
23
+            ▼
24
+       Cloud VLM API (высокое качество)
25
+            │
26
+            ▼
27
+       Заменяем текст блока
28
+```
29
+
30
+## Что отправлять
31
+
32
+- **Не** весь документ — только регион изображения (bbox + padding 5px)
33
+- Формат: base64 JPEG, quality=90
34
+- Prompt: «Извлеки весь текст из этого региона. Сохрани формулы как LaTeX.»
35
+- API: Anthropic Messages (`/v1/messages`) или OpenAI Chat Completions
36
+
37
+## Интеграция
38
+
39
+```python
40
+class VlmCorrector:
41
+    def __init__(self, image_path: str, model: str = "qwen3.8-max"):
42
+        self._image = cv2.imread(image_path)
43
+        self._model = model
44
+    
45
+    def __call__(self, block: ParsedBlock) -> None:
46
+        # crop bbox → base64 → API call → block.content = result
47
+```
48
+
49
+Через `Corrector` протокол подключается к общему циклу `apply_corrector()`.
50
+
51
+## Конфигурация
52
+
53
+API-ключ через `.env`: `OPENCODE_API_KEY=sk-...`
54
+URL из документации провайдера (для OpenCode Go: `https://opencode.ai/zen/go/v1/messages`)
55
+
56
+## Ограничения
57
+
58
+- Требует интернет (нарушает offline-требование)
59
+- Задержка API: 5-15s на блок
60
+- Лимиты подписки (OpenCode Go: 160 запросов/5ч для Qwen3.8 Max)
61
+- Стоимость: ~$0.01 за блок (при 200 выходных токенах)
62
+
63
+## Когда НЕ применять
64
+
65
+- Весь документ умещается в RAM и обрабатывается <30s локально
66
+- Нет API-подписки
67
+- Документ содержит конфиденциальные данные (offline-требование)

+ 6
- 2
.env.dist Целия файл

@@ -1,2 +1,6 @@
1
-OPENCODE_API_KEY=your_private_opencode_api_key
2
-HF_TOKEN=your_private_huggingface_readonly_api_key
1
+# OpenCode Go — API-ключ для VLM-корректора (Qwen3.8 Max)
2
+OPENCODE_API_KEY=sk-...
3
+
4
+# HuggingFace — токен для скачивания моделей (Surya 2, Qwen GGUF)
5
+# Получить: https://huggingface.co/settings/tokens (read-only)
6
+HF_TOKEN=hf_...

+ 0
- 49
3_1200_02_surya.md Целия файл

@@ -1,49 +0,0 @@
1
-## 38
2
-
3
-$$
4
-CRec_{it} = \sum_T^t MRec_{it}
5
-$$
6
-
7
-[17]
8
-
9
-где:
10
-
11
-i – индекс договора;
12
-
13
-t – номер месяца в дефолте, на который прогнозируются возвраты для договора, в диапазоне между текущим сроком в дефолте и горизонтом взыскания;
14
-
15
-T – рассчитанный горизонт взыскания;
16
-
17
-$MRec_{it}$ – маржинальные возвраты, оцененные по модели, для договора i в месяц t.
18
-
19
-7.1.10. На основе текущих полученных возвратов по дефолту на дату разработки модели и оцененной суммарной оставшейся доли возвратов к получению рассчитывается общий уровень потерь по дефолту за весь период взыскания с учетом экстраполяции (формула [18] ниже). В процессе моделирования минимальное экстраполированное значение уровня потерь ограничивается нулем.
20
-
21
-$$
22
-LGD_{extr_i} = \max(LGD_{act_{it}} - CRec_{it}, 0)
23
-$$
24
-
25
-[18]
26
-
27
-$$
28
-LGD_{act_{it}} = 1 - \frac{\sum_t DCF_{i,t}}{EAD_i},
29
-$$
30
-
31
-[19]
32
-
33
-где:
34
-
35
-$LGD_{extr_i}$ – экстраполированное значение LGD для i-го договора в месяц нахождения в дефолте t;
36
-
37
-$CRec_{it}$ – оцененная суммарная оставшаяся доля возвратов для i-го договора в месяц нахождения в дефолте t;
38
-
39
-$LGD_{act_{it}}$ – фактический уровень потерь для i-го договора в месяц нахождения в дефолте t;
40
-
41
-$\sum_t DCF_{i,t}$ – дисконтированная сумма возвратов, полученных на t-ый месяц нахождения в дефолте;
42
-
43
-$EAD_i$ – величина задолженности на дату дефолта для договора i.
44
-
45
-7.1.11. Полученные значения уровней потерь для незавершенных дефолтов могут быть использованы в процессе моделирования. При учете таких дефолтов они рассматриваются как не выздоровевшие.
46
-
47
-7.2. Модель на основе дерева решений
48
-
49
-7.2.1. Данный подраздел применяется при выборе структуры Моделей на основе дерева решений по ключевым характеристикам портфеля.

+ 0
- 31
3_surya.md Целия файл

@@ -1,31 +0,0 @@
1
-## 2. Literature review
2
-
3
-## 2.1. Highlights of the IRB set-up and evolution
4
-
5
-1987 Vasicek model. IRB owes to the Nobel laureate in economics Robert Merton and Oldrich von Vasicek for its birth. As early as in Merton (1974), there came an idea to model a hypothetical borrower's finance, its assets more specifically, as a Normally-distributed random walk. The default event happens when the asset pattern breaches the fixed thresholds signaling for the artificial liabilities. Hence, the probability of such a breach given the known (assumed) nature of the probabilistic distribution allows us to proceed with the probability of default ($PD$) at the individual borrower's level.
6
-
7
-More than a decade later, the concept to consider a bunch of such individual borrowers was developed in Vasicek (1987). The starting point was to decompose the individual borrowers' pattern into specific (idiosyncratic) and common (systemic) components as in Eq. (1).
8
-
9
-$$
10
-A_i = Z \cdot r + u_i \cdot \sqrt{1 - R} \quad (1)
11
-$$
12
-
13
-where $Z \sim N(0, 1)$ - common factor; $u_i \sim N(0, 1)$ - specific factor; $r = \text{corr}(A_i; Z)$ - correlation of factors (conventional mathematical correlation) as it originally appeared without a squared power in the works by Vasicek (1987); Gordy (2000) (let us call it default correlation);
14
-
15
-$R = r^2$ - asset (value) correlation using BCBS notations (mathematically speaking, it is the square of the conventional mathematical correlation between $A_i$ and $Z$).
16
-
17
-Due to the nice properties of the Gaussian distribution, Vasicek uses the Value-at-Risk (VaR) risk-measure to slice the default rate (DR) distribution of the loan portfolio and arrives at the worst feasible default rate realization given the chosen significance level of $\alpha$ in Eq. (2).
18
-
19
-$$
20
-VaR = N \left( \frac{N^{-1}(PD) + N^{-1}(1 - \alpha) \cdot \sqrt{R}}{\sqrt{1 - R}} \right). \quad (2)
21
-$$
22
-
23
-Important to note that Vasicek did not impose any restrictions over $r$, over its feasible values. The only thing he notes is that in case $r > 50\%$, the DR distribution becomes U-shaped (bimodal). Remembering the notations that $R = r^2$, or $r = \sqrt{R}$, we should take away that Vasicek was speaking of DR distribution bimodality when the asset (value) correlation exceeds 25% ($R > 25\%$).
24
-
25
-2006 BCBS amendment to the Vasicek model. The idea to internationally introduce model-based (IRB) credit risk regulation seems to have become popular after the success of such regulation introduction for the market risk in the Basel I amendment BCBS (1996). Thus, the very first draft of the future Basel II Accord incorporating IRB appeared on the edge of the new millennium, see Penikas (2020a), while it took another six years to polish it to the very final (comprehensive) version of BCBS (2006).
26
-
27
-When designing IRB, the Basel Committee implemented five conceptual amendments, originally not previewed in the Vasicek model:
28
-
29
-1. BCBS chose the IRB significance level of $\alpha = 0.1\%$ (inversely, confidence level of 99.9%) what corresponds to the risk-measure breach (bank failure) once in one hundred years given the annual horizon for $PD$, see BCBS (2005a).2. BCBS added other credit risk parameters like loss given default ($LGD$), exposure at default ($EAD$), maturity ($M$) etc. For instance, $VaR$ from Eq. (2) is multiplied by $LGD$ and $EAD$.
30
-
31
-3

+ 17
- 0
README.md Целия файл

@@ -85,12 +85,29 @@ python -m src.image_split -i scan.jpg --slice-auto
85 85
 python -m src.image_to_latex -i page.jpg
86 86
 
87 87
 # Пакетный режим — все изображения в каталоге (Surya: batch-оптимизация)
88
+
88 89
 python -m src.image_to_latex -i ./pages/ --ocr-engine surya
89 90
 
90 91
 # Пакетный режим (PaddleOCR: последовательно)
91 92
 python -m src.image_to_latex -i ./pages/ --ocr-engine paddle
92 93
 ```
93 94
 
95
+### Переменные окружения (`.env`)
96
+
97
+Скопируй шаблон и заполни своими ключами:
98
+
99
+```bash
100
+cp .env.dist .env
101
+# Отредактируй .env — вставь свои API-ключи
102
+```
103
+
104
+| Переменная | Назначение | Где взять |
105
+|-----------|-----------|-----------|
106
+| `OPENCODE_API_KEY` | VLM-корректор через OpenCode Go | [opencode.ai/auth](https://opencode.ai/auth) → скопировать ключ |
107
+| `HF_TOKEN` | Скачивание моделей с HuggingFace (Surya 2) | [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) (read-only) |
108
+
109
+`.env` уже добавлен в `.gitignore` — не коммитится.
110
+
94 111
 ---
95 112
 
96 113
 ## Результат

Loading…
Отказ
Запис