refactor(all): унифицированы служебные поля STG — переход на канонический нейминг

- Зачем:
  - STG-слой использовал legacy-имена (batch_id, load_dttm, src_created_at_ts),
    тогда как ODS/DDS/DM уже работали с каноном (_load_id, _load_ts, event_ts).
    Студент видел разные имена для одного понятия — это убрано.
- Что:
  - переименованы колонки в 9 STG DDL: batch_id→_load_id, load_dttm→_load_ts,
    src_created_at_ts→event_ts; добавлен NOT NULL для _load_id во всех таблицах.
  - обновлены 9 STG Load, 9 STG DQ, 9 ODS Load, 9 ODS DQ (INSERT/SELECT/WHERE).
  - обновлены DAG-файлы bookings_to_gp_stage.py и bookings_to_gp_ods.py
    (встроенный SQL резолвера, комментарии; Python-идентификаторы не тронуты).
  - обновлены тесты и ~15 документов (naming_conventions, PRD, db_schema,
    design-docs, qa-plan, README, TESTING и др.).
- Проверка:
  - grep -rn 'load_dttm\|src_created_at_ts' sql/ airflow/ tests/ — 0 совпадений.
  - make test — 4 passed.
  - e2e-etl: day1 прошёл полностью, day2 стартовал без ошибок.
This commit is contained in:
2026-03-10 00:07:08 +03:00
parent 3de4db8aa6
commit 5972bcc2d8
64 changed files with 582 additions and 484 deletions
@@ -0,0 +1,118 @@
# Унификация нейминга служебных полей в STG
## Контекст
В STG-слое используются legacy-имена служебных полей (`batch_id`, `load_dttm`, `src_created_at_ts`), а начиная с ODS — каноничные (`_load_id`, `_load_ts`, `event_ts`). Студент видит разные имена для одного понятия. Цель — привести STG к канону, убрав расхождение.
> **Breaking change (dev-only).** Это ломающее переименование колонок. Миграционный шаг (ALTER TABLE … RENAME COLUMN) не предусмотрен. DDL-файлы используют `CREATE TABLE IF NOT EXISTS`, поэтому сами по себе они не пересоздадут существующие таблицы с новыми именами колонок. План предполагает заранее пересозданную среду (например, `make down && make up`) или ручной `DROP TABLE` / `DROP SCHEMA` перед `make ddl-gp`. Обратная совместимость не обеспечивается.
## Маппинг
| Legacy (STG сейчас) | Канон (ODS/DDS/DM) |
|---|---|
| `batch_id` | `_load_id` |
| `load_dttm` | `_load_ts` |
| `src_created_at_ts` | `event_ts` |
### Оговорка про `event_ts` в snapshot-справочниках
В транзакционных STG-таблицах (bookings, tickets, flights, segments, boarding_passes) поле `src_created_at_ts` действительно хранит время события из источника — переименование в `event_ts` семантически точно.
В snapshot-справочниках (airports, airplanes, routes, seats) это поле заполняется `now()` при загрузке, т.е. по факту это ещё одно load-time, а не время события. Тем не менее мы сохраняем единое имя `event_ts` как **учебное упрощение** — ради консистентной структуры STG-таблиц. Это зафиксировано как осознанный trade-off: единообразие важнее семантической точности в справочниках. В `naming_conventions.md` нужно добавить соответствующую оговорку (раздел 4, «Time Rule»).
## Что НЕ переименовываем
- PL/pgSQL переменная `v_batch_id` — это локальная переменная, не колонка
- Python-функция `_resolve_stg_batch_id`, переменная `stg_batch_id`, task_id `resolve_stg_batch_id` — это Python/Airflow-идентификаторы
- XCom-ключи, ссылающиеся на task_id
## Порядок выполнения
### Шаг 1: STG DDL (9 файлов)
`sql/stg/{bookings,tickets,flights,segments,airports,airplanes,routes,seats,boarding_passes}_ddl.sql`
В каждом: `batch_id``_load_id`, `load_dttm``_load_ts`, `src_created_at_ts``event_ts`.
### Шаг 2: STG Load (9 файлов)
`sql/stg/{bookings,tickets,flights,segments,airports,airplanes,routes,seats,boarding_passes}_load.sql`
INSERT-списки, SELECT, WHERE, комментарии — те же 3 замены.
### Шаг 3: STG DQ (9 файлов)
`sql/stg/{bookings,tickets,flights,segments,airports,airplanes,routes,seats,boarding_passes}_dq.sql`
WHERE-условия (`batch_id = v_batch_id``_load_id = v_batch_id`), RAISE-сообщения, комментарии.
### Шаг 4: ODS Load (9 файлов)
Два подтипа — обрабатывать по-разному.
#### 4a: Транзакционные таблицы (5 файлов)
`sql/ods/{bookings,tickets,flights,segments,boarding_passes}_load.sql`
SELECT из STG: `s.batch_id``s._load_id`, `s.load_dttm``s._load_ts`, `s.src_created_at_ts``s.event_ts`. Убрать лишние алиасы (`s.src_created_at_ts AS event_ts` → просто `s.event_ts`).
#### 4b: Snapshot-справочники (4 файла)
`sql/ods/{airports,airplanes,routes,seats}_load.sql`
Здесь `event_ts` отсутствует в целевой ODS-таблице — менять только ссылки на STG-колонки: `s.batch_id``s._load_id`, `s.load_dttm``s._load_ts`, `s.src_created_at_ts``s.event_ts` (только в ORDER BY / WHERE, где они читают из STG). ODS-колонка `_load_ts` по-прежнему заполняется через `now()`, это не меняется.
### Шаг 5: ODS DQ (9 файлов)
`sql/ods/{bookings,tickets,flights,segments,airports,airplanes,routes,seats,boarding_passes}_dq.sql`
`WHERE batch_id =``WHERE _load_id =`, RAISE-сообщения.
### Шаг 6: DAG-файлы (2 файла)
- `airflow/dags/bookings_to_gp_stage.py` — комментарий про `batch_id`
- `airflow/dags/bookings_to_gp_ods.py` — встроенный SQL-запрос резолвера: все `batch_id` как колонка → `_load_id`, `load_dttm``_load_ts`. Python-имена не трогаем.
### Шаг 7: Тесты (3 файла)
- `tests/test_ods_snapshot_integration.py` — inline DDL и INSERT в тестах
- `tests/test_dags_smoke.py` — комментарии
- `tests/test_ods_sql_contract.py` — docstring
### Шаг 8: Документация (~15 файлов)
- `docs/internal/naming_conventions.md` — убрать legacy-исключение (секция 5/STG), убрать переходный маппинг (секция 6), добавить оговорку про `event_ts` в snapshot-справочниках (секция 4)
- `docs/internal/db_schema.md` — описания STG-полей
- `docs/internal/bookings_stg_design.md` — дизайн STG
- `docs/internal/bookings_ods_design.md` — маппинг STG→ODS, SQL-примеры
- `docs/internal/qa-plan.md` — SQL-запросы проверок
- `docs/internal/architecture_review.md` — архитектурные заметки
- `docs/internal/bookings_stg_code_review.md` — код-ревью
- `docs/bookings_to_gp_stage.md` — описание STG DAG, примеры полей
- `docs/bookings_to_gp_ods.md` — описание ODS DAG
- `docs/dag_execution_order.md` — порядок выполнения DAG
- `educational-tasks.md` — учебные задания
- `README.md` — SQL-примеры в README
- `TESTING.md` — чек-лист тестирования
### Шаг 9: Верификация
```bash
# 1. Проверить SQL и Python — не должно быть колонок batch_id
# (допустимы только: v_batch_id, stg_batch_id, resolve_stg_batch_id)
grep -rn 'batch_id' sql/stg/ sql/ods/ airflow/dags/ tests/ --include='*.sql' --include='*.py'
# 2. Ноль совпадений по старым именам в коде
grep -rn 'load_dttm' sql/ airflow/dags/ tests/ --include='*.sql' --include='*.py'
grep -rn 'src_created_at_ts' sql/ airflow/dags/ tests/ --include='*.sql' --include='*.py'
# 3. Проверить документацию — не должно быть старых имён как актуальных
# (допустимы упоминания в историческом контексте)
grep -rn 'batch_id\|load_dttm\|src_created_at_ts' docs/ educational-tasks.md README.md TESTING.md
# 4. Тесты и линтер
make test
make fmt && make lint
```
## Итого: ~55 файлов, ~3 механические замены в каждом