# Шаг 1 — Что не соответствует стандарту в текущем состоянии

> **Только диагностика.** Никаких решений по рефакторингу — это пойдёт на следующих шагах. Здесь только: что есть сейчас, какое правило стандарта нарушено, и каков масштаб проблемы.

---

## Текущее состояние

| Что | Где | Сколько |
|---|---|---|
| Гин-конфиги (взаимовключаемые через `include`) | `conf/` | 12 файлов: `balanced.gin`, `baseline_no_text.gin`, `text_heavy.gin` (v2 UAV-GeoLoc), `gtauav_balanced.gin`, `gtauav_baseline.gin`, `gtauav_balanced_asym.gin`, `gtauav_baseline_asym.gin`, `gtauav_balanced_stripnet.gin`, `gtauav_balanced_stripnet_unfrozen.gin`, `gtauav_baseline_stripnet.gin`, `gtauav_baseline_stripnet_unfrozen.gin`, `gtauav_text_heavy.gin`, `gtauav_image_heavy.gin` (по скриншоту в локальной копии есть ещё 4 sofia-варианта) |
| Главный `@gin.configurable + @dataclass` | `src/training/train_gtauav.py::TrainConfigGTAUAV` | 1 класс, 50+ полей |
| Legacy `@gin.configurable` на классе тренировки | `src/training/train.py::TrainConfig` (v2) | 1 класс, 14 полей |
| `@gin.configurable` на не-конфиг классах | `src/losses/multi_infonce.py::InfoNCELoss`, `src/losses/weighted_infonce.py::WeightedInfoNCELoss`, `src/datasets/visloc_with_captions.py::GeoLocCaptionDataset` | 3 класса |
| Локальный `@dataclass` для модели (без gin) | `src/models/sofia_v1/config.py::SOFIAv1Config` | 1 класс |
| Module-level пути | `src/training/train_gtauav.py`, `src/datasets/gtauav_dataset.py`, `scripts/make_split.py`, `scripts/filter_segmentation.py` | 4 файла |
| `argparse` | `src/training/train_gtauav.py`, `src/training/train.py`, `scripts/make_split.py`, `scripts/filter_segmentation.py` | 4 файла |

---

## Несоответствия стандарту

### 1. `@gin.configurable + @dataclass` — критическое нарушение

**Правило** (`Стандарт_написания_кода_для_DL_CV.md` §3.1, `Reference Examples → Anti-patterns`):

> «**Запрещено** использовать `dataclass` совместно с gin»

**Что нарушает:**

`src/training/train_gtauav.py`:
```python
@gin.configurable(module="src.training.train_gtauav")
@dataclass                                              # ← FORBIDDEN
class TrainConfigGTAUAV:
    train_json: str = _TRAIN_JSON
    test_json: str = _TEST_JSON
    rgb_root: str = _RGB_ROOT
    # ... 50+ полей всего:
    # пути к данным, к моделям, training schedule, model arch (mona, gates,
    # baseline, stripnet, asymmetric), loss params, sampler params,
    # tracking flags (wandb/tb/gradcam/profiler)
```

**Почему это нарушение:** `@dataclass` авто-генерирует `__init__`, в который gin **тоже** инъектирует параметры. Получается двойная магия: dataclass читает type hints для генерации сигнатуры, gin читает гин-биндинги для подмены значений. На практике работает, но это именно та комбинация, которую стандарт запрещает.

**Масштаб:** **1 класс**, но он держит **всё** — путь, hardware, model, loss, sampler, tracking. Это блокирует разделение конфига на оси.

---

### 2. `@gin.configurable` на не-конфиг классах — критическое нарушение

**Правило** (стандарт §3.1):

> «Декоратор `@gin.configurable` **только на классах** [конфигурации, *не на бизнес-логике*]»

**Что нарушает:**

| Файл | Класс | Параметры (которые проникают в `.gin`) |
|---|---|---|
| `src/losses/multi_infonce.py` | `InfoNCELoss` | `temperature_init`, `temperature_final`, `label_smoothing`, `weight_q2g`, `weight_g2q`, `learnable_temperature`, `tau_min`, `tau_max`, `hard_mining_k` |
| `src/losses/weighted_infonce.py` | `WeightedInfoNCELoss` | `temperature_init`, `learnable_temperature`, `label_smoothing`, `k`, `tau_min`, `tau_max` |
| `src/datasets/visloc_with_captions.py` (v2) | `GeoLocCaptionDataset` | `query_file`, `data_root`, `image_transform`, `drop_caption_prob`, `seed` |

**Почему это нарушение:** конфиг и бизнес-логика — разные слои. Когда `nn.Module` или `Dataset` декорированы `@gin.configurable`, гин лезет в **их** `__init__` помимо лезения в `TrainConfigGTAUAV.__init__`. В `gtauav_balanced.gin` это видно прямо:

```gin
# Параметры дублируются — раз в TrainConfigGTAUAV, раз в InfoNCELoss:
TrainConfigGTAUAV.tau_init = 0.07
TrainConfigGTAUAV.label_smoothing = 0.1
TrainConfigGTAUAV.weight_q2g = 0.6
TrainConfigGTAUAV.weight_g2q = 0.4

# ---- InfoNCELoss (gin-configurable) ----
InfoNCELoss.temperature_init = 0.07          # ← дубль tau_init
InfoNCELoss.label_smoothing = 0.1            # ← дубль
InfoNCELoss.weight_q2g = 0.6                 # ← дубль
InfoNCELoss.weight_g2q = 0.4                 # ← дубль
InfoNCELoss.tau_min = 0.01
InfoNCELoss.tau_max = 0.1
InfoNCELoss.hard_mining_k = 0
```

**Это активный источник тихих багов:** если кто-то поменяет `TrainConfigGTAUAV.tau_init = 0.05`, а `InfoNCELoss.temperature_init` забудет — обучение пойдёт с `0.05` в логике trainer-а и `0.07` в самой loss-функции. Никаких ошибок не будет, метрики просто будут странными.

**Масштаб:** **3 класса**, но самый болезненный — `InfoNCELoss`, потому что у него **самая большая зона перекрытия** с `TrainConfigGTAUAV`.

---

### 3. `argparse` — критическое нарушение

**Правило** (стандарт §3.4):

> «**Запрещён argparse** — все параметры из .gin файлов»

**Что нарушает:**

| Файл | Кол-во CLI флагов | Самые проблемные |
|---|---|---|
| `src/training/train_gtauav.py::main` | ~15 | `--config`, `--baseline`, `--batch-size`, `--epochs`, `--filter-meta`, `--wandb`, `--gradcam`, `--profile`, `--gin-param`, `--resume`, `--output-dir` |
| `src/training/train.py::main` (v2) | 1 | `--config` |
| `scripts/make_split.py::main` | 3 | `--ratio`, `--seed`, `--output-dir` |
| `scripts/filter_segmentation.py::main` | 3 | `--segm-root`, `--threshold`, `--output` |

**Самый ядовитый паттерн** в `train_gtauav.py`:

```python
parser.add_argument("--gin-param", nargs="*", help="Override gin params from CLI")
# ...
gin.parse_config_files_and_bindings([cfg_file], extra_bindings)
```

CLI **перекрывает** gin-биндинги. Это создаёт **3 источника правды** на один и тот же параметр: дефолт в `__init__`, значение в `.gin`, значение в `--gin-param`. Какое из них применилось в конкретном запуске — невозможно установить иначе как чтением логов.

**Масштаб:** **тренировочный код** (`train_gtauav.py`) — критично; **скрипты** (`make_split.py`, `filter_segmentation.py`) — пограничный случай (см. ниже §7).

---

### 4. `include` для композиции `.gin` — пограничный случай

**Правило** (`REQUIREMENTS_GIN_STYLE.md` §8):

> «Использовать **минимальный** набор возможностей gin: `@gin.configurable`, `gin.parse_config_file()`. Нет: `gin.register()`, `gin.constant()`, `gin.query_parameter()`, **макросы, ссылки между конфигами**»

**Что есть сейчас:**

```gin
# conf/gtauav_balanced_asym.gin
include 'conf/gtauav_balanced.gin'

TrainConfigGTAUAV.shared_encoder = False
TrainConfigGTAUAV.mona_last_n_blocks = 24
TrainConfigGTAUAV.output_dir = "out/gtauav/balanced_asym"
```

`include` — это **встроенная фича gin** для композиции. Формально она:
- ✅ Не входит в явный список запрещённого (`gin.constant`, `gin.register`, `gin.query_parameter`, макросы)
- ❌ Попадает под формулировку «**ссылки между конфигами**» в `REQUIREMENTS_GIN_STYLE.md` §8

**По букве правил это нарушение.** По духу — `include` решает реальную проблему (DRY), но создаёт неявную зависимость: чтобы понять, с какими параметрами запускается `gtauav_balanced_stripnet_unfrozen.gin`, нужно прочитать **3 файла** (`unfrozen` → `stripnet` → `balanced`).

**Масштаб:** все 14 `gtauav_*.gin` (кроме `gtauav_balanced.gin`, который сам — корень дерева) и v2 (`baseline_no_text.gin`, `text_heavy.gin` тянут `balanced.gin`).

---

### 5. Один мега-конфиг на всё — не «жёсткое» нарушение, но против духа

**Правило** (стандарт §3.3, `Рекомендуемые_gin-config_категории.md`):

> «Каждый `.gin` → один конфиг-класс»
>
> «Принцип разделения: если два параметра меняются **вместе** — в одном конфиге. Если **независимо** — в разных»

**Что есть сейчас:**

`TrainConfigGTAUAV` миксует **несколько независимых осей изменчивости** в один класс:
- Пути к данным/моделям (меняются при смене машины) ↔ training schedule (меняются при смене эксперимента) ↔ model arch (`baseline_mode`, `shared_encoder`, `mona_*`, `stripnet_*`) ↔ loss (`tau_init`, `label_smoothing`, `weight_q2g`, ...) ↔ sampler (`sampler_type`, `dss_*`) ↔ tracking (`use_wandb`, `use_tb`, `use_gradcam`, `use_profiler`)

В одном `.gin` лежат биндинги для всех этих осей. Когда нужно «то же обучение, но без wandb» — приходится копировать целый `.gin` файл.

**Это не нарушение явного запрета, но прямое следствие нарушения #1:** пока есть один большой `@dataclass + @gin.configurable`, иначе расположить параметры просто негде.

**Масштаб:** проникает во все 12 `gtauav_*.gin` файлов одинаково.

---

### 6. Module-level хардкод путей — нарушение

**Правило** (стандарт §6 чеклист):

> «Нет захардкоженных model ID / промптов / размеров?»

**Что нарушает:**

```python
# src/training/train_gtauav.py (module level)
_RGB_ROOT = "/home/servml/Документы/datasets/GTA-UAV-LR"
_CAPTION_ROOT = "/home/servml/Документы/datasets/GTA-UAV-LR-captions"
_TRAIN_JSON = "meta/train_80.json"
_TEST_JSON = "meta/test_20.json"
_DINO_WEB = "nn_models/DINO_WEB/dinov3-vitl16-pretrain-lvd1689m.pth"
_DINO_SAT = "nn_models/DINO_SAT/model.safetensors"
_LRSCLIP = "nn_models/LRSCLIP/DGTRS-CLIP-ViT-L-14.pt"

# src/datasets/gtauav_dataset.py (module level) — ДУБЛЬ:
_RGB_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR")
_CAPTION_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR-captions")

# scripts/make_split.py (module level):
_RGB_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR")

# scripts/filter_segmentation.py (module level):
SEGM_ROOT = Path("/home/servml/Документы/datasets/GTA-UAV-LR-aug/segm")
```

**Главная проблема — не сам факт хардкода**, а то, что один и тот же путь `/home/servml/Документы/datasets/GTA-UAV-LR` дублируется в **трёх** местах: train_gtauav.py + gtauav_dataset.py + make_split.py. Если переехать на другую машину — нужно править 3 файла; если забыть один — silent breakage.

**Масштаб:** 4 файла, ~10 module-level констант пути.

---

### 7. Скрипты `make_split.py` / `filter_segmentation.py` — спорный случай

**Что есть:**
- `argparse` (3 параметра в каждом)
- module-level пути (`_RGB_ROOT`, `SEGM_ROOT`)
- Запускаются однократно перед тренировкой
- Не интегрированы в `Trainer.run()`

**Применять ли стандарт «нет argparse» к ним?**

Стандарт направлен против `argparse` в **тренировочном коде**, где параметров 50+ и нужна воспроизводимость. У `make_split.py` 3 параметра, и это **разовая препроцессинг-утилита**. Здесь буква и дух стандарта расходятся.

**Это не «несоответствие, требующее немедленной правки»**, а **открытый вопрос для следующего шага**: применять ли gin-стиль к препроцессинг-скриптам или оставить argparse.

---

### 8. Sofia models — `@dataclass` без gin (формально не нарушение)

**Что есть:**

```python
# src/models/sofia_v1/config.py
@dataclass
class SOFIAv1Config:
    variant: Literal["tiny_tiny", "tiny", "small", "small_v2"] = "small"
    in_channels: int = 3
    input_size: int = 256
    dcn_variant: Literal["v2", "v4"] = "v2"
    d_descriptor: int = 1024
    use_film_altitude: bool = True
    altitude_norm: float = 500.0
    use_text_film_uav: bool = True
    use_text_film_sat: bool = True
    text_film_dim: int = 1024
    text_film_hidden: int = 256
```

**Стандарт** запрещает **`@gin.configurable + @dataclass`**, но не запрещает `@dataclass` сам по себе. `SOFIAv1Config` без gin — формально стандарт **не нарушает**.

**Однако:** в `gtauav_balanced_sofia*.gin` (по скриншоту локально есть, в репо пока нет) параметры sofia, очевидно, прокидываются в `TrainConfigGTAUAV` как обычные поля. То есть sofia сейчас живёт в **двух** местах:
- `SOFIAv1Config` (dataclass, в коде модели)
- `TrainConfigGTAUAV.sofia_*` (если там такие есть) или только через позиционное создание `SOFIAv1Config()` в `Trainer`

Это не нарушение, но **архитектурный раскол**: dataclass-конфиги внутри модельных подсистем + gin-конфиги снаружи.

**Это тоже открытый вопрос для следующего шага**, не текущая проблема.

---

### 9. Прочее по чеклисту стандарта (мелочи)

| Пункт | Состояние | Файлы |
|---|---|---|
| `from __future__ import annotations` первой строкой | ✅ есть в ключевых файлах | проверено в `train_gtauav.py`, `gtauav_dataset.py`, `multi_infonce.py`, `weighted_infonce.py`, `train.py` |
| Строгие type hints | ✅ в основном | пара мест с `dict` без параметров (`_atomic_save(obj: dict)`) |
| Google-style docstrings | ✅ есть, качество хорошее | — |
| `@torch.inference_mode()` вместо `@torch.no_grad()` | ❌ используется `@torch.no_grad()` | `train_gtauav.py::_evaluate`, `_embed_drone_queries` |
| Atomic writes | ⚠️ есть, но без cleanup на ошибке | `train_gtauav.py::_atomic_save` (нет `try/except` — `.tmp` остаётся при сбое) |
| Английский в коде/комментариях | ✅ есть | — |
| Импорты stdlib → third-party → local | ✅ есть | — |

---

## Сводка — что нарушено и насколько срочно

| # | Нарушение | Срочность | Зона воздействия |
|---|---|---|---|
| 1 | `@gin.configurable + @dataclass` на `TrainConfigGTAUAV` | 🔴 критично | блокирует всё остальное |
| 2 | `@gin.configurable` на `InfoNCELoss`, `WeightedInfoNCELoss`, `GeoLocCaptionDataset` | 🔴 критично | активный источник тихих багов |
| 3 | `argparse` в тренировочном коде | 🔴 критично | три источника правды на параметр |
| 4 | `include` между `.gin` файлами | 🟡 пограничный | формально нарушает «нет ссылок между конфигами» |
| 5 | Один мега-конфиг (нет разделения на оси) | 🟡 следствие #1 | разрешится с #1 |
| 6 | Module-level пути в 4 файлах | 🟡 нарушение, но не критично | дубли — реальная проблема |
| 7 | `argparse` в скриптах препроцессинга | ⚪ открытый вопрос | спорный случай |
| 8 | `@dataclass` в `SOFIAv1Config` (без gin) | ⚪ открытый вопрос | формально не нарушение |
| 9 | `@torch.no_grad()` вместо `@torch.inference_mode()` | 🟢 мелочь | косметика |
| 10 | `_atomic_save` без cleanup .tmp | 🟢 мелочь | редкое последствие |

---

## Что предлагаю обсудить дальше (Шаг 2)

Прежде чем двигать код, нужно принять решения по 4 развилкам:

1. **`include` между `.gin`** — терпим как удобный DRY-механизм или приводим к плоской иерархии (каждый эксперимент = самодостаточный набор `.gin` без `include`)?

2. **Разделение `TrainConfigGTAUAV` на классы** — на сколько и по каким осям?
   - Вариант **5 классов** (Pipeline / Hardware / Models / Training / Tracking) из «`Рекомендуемые_gin-config_категории.md`».
   - Вариант **6 классов** (отдельный Loss + Sampler).
   - Вариант **3 класса** (всё, что было — в Training, плюс Pipeline + Tracking).

3. **Sofia + dataclass** — оставлять `SOFIAv1Config` как dataclass-структуру внутри модели или переписать в обычный класс/влить в `ModelsConfig`?

4. **Скрипты** — переводить на gin или оставить argparse?

После решения этих 4 пунктов план рефакторинга становится однозначным.