docs(all): реструктурирована документация — docs/internal/ заменён на design/, reference/, archive/, plans/
- Зачем: - docs/internal/ превратился в свалку: дизайн-документы, ревью, планы и справочники лежали вперемешку. - архивные планы были неотличимы от живых документов. - Что: - docs/internal/ удалён; файлы распределены по docs/design/, docs/reference/, docs/archive/, docs/plans/, docs/assignment/. - educational-tasks.md убран из корня в архив (устарел). - обновлены все перекрёстные ссылки в AGENTS.md, TODO.md, README.md, docs/README.md и внутри design/reference/. - актуализированы architecture_review.md (статус DM-слоя), db_schema.md (DM-слой), TESTING.md, dag_execution_order.md, pxf_bookings.md. - добавлены заглушки docs/assignment/README.md и docs/plans/README.md. - Проверка: - make test && make lint - rg 'docs/internal' --glob '!docs/archive/*' — должно быть пусто.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
# Ревью решения (образец для студентов): `bookings-db` → `stg` в Greenplum
|
||||
|
||||
Этот документ фиксирует рекомендации по улучшению учебного решения ETL (Airflow + Greenplum + PXF)
|
||||
на основе ревью изменений ветки `chore/bookings-etl` (добавление полного STG слоя и пайплайна загрузки).
|
||||
|
||||
Цель ревью — сделать решение **безоговорочно рекомендуемым** к изучению начинающими:
|
||||
понятным, предсказуемым, с корректной терминологией и честными инженерными компромиссами.
|
||||
|
||||
---
|
||||
|
||||
## 1) Сильные стороны решения (что уже хорошо и стоит сохранить)
|
||||
|
||||
1. **Единый “шаблон” по таблицам** в `sql/stg/`:
|
||||
- `{table}_ddl.sql` — создаёт `*_ext` и внутреннюю таблицу;
|
||||
- `{table}_load.sql` — загружает данные;
|
||||
- `{table}_dq.sql` — валидирует качество и останавливает пайплайн при проблемах.
|
||||
|
||||
Это отличная учебная структура: студент быстро понимает, “где что лежит” и как добавлять новые таблицы.
|
||||
|
||||
2. **DAG как оркестратор, SQL как логика**:
|
||||
- `airflow/dags/bookings_to_gp_stage.py` и `airflow/dags/bookings_stg_ddl.py` используют `PostgresOperator`
|
||||
и читают SQL с диска через `template_searchpath="/sql"`.
|
||||
Это соответствует “канонической” модели: Airflow управляет шагами, а трансформации живут в SQL.
|
||||
|
||||
3. **Понятные сообщения при падении DQ** (в большинстве скриптов): студенту легче дебажить.
|
||||
|
||||
---
|
||||
|
||||
## 2) Критичные замечания (статус на текущий момент)
|
||||
|
||||
### 2.1. Некорректные утверждения про MPP и co-location (статус: исправлено)
|
||||
|
||||
В Greenplum производительность JOIN сильно зависит от распределения данных по сегментам.
|
||||
Если ключ распределения двух таблиц совпадает с ключом JOIN — часто удаётся обойтись без перераспределения данных (motion).
|
||||
|
||||
Раньше в некоторых DDL-комментариях обещалась co-location там, где её не будет.
|
||||
Это педагогически опасно: студент запоминает неверную модель, а потом “не понимает”, почему запросы медленные.
|
||||
|
||||
Что сделано:
|
||||
- DDL-комментарии приведены к честной формулировке “ключ выбран так-то, но JOIN по другим ключам может требовать motion”.
|
||||
- Исправлены места, где co-location заявлялась ошибочно (в т.ч. `routes`, `flights`, `segments`, `boarding_passes`).
|
||||
|
||||
Файлы: `sql/stg/routes_ddl.sql`, `sql/stg/flights_ddl.sql`, `sql/stg/segments_ddl.sql`, `sql/stg/boarding_passes_ddl.sql`.
|
||||
|
||||
### 2.2. DQ-проверки ссылочной целостности: “текущий батч” vs “вся история” (статус: исправлено)
|
||||
|
||||
Часть DQ-скриптов проверяет наличие “родительских” записей в таблице **без фильтра `_load_id`**.
|
||||
При append-only истории это может скрыть проблемы текущей загрузки:
|
||||
родитель был загружен в прошлом батче → проверка пройдёт, даже если текущий батч родителя не загрузил.
|
||||
|
||||
Что сделано:
|
||||
- `routes_dq.sql`: проверка airports/airplanes стала батч-строгой (`_load_id = текущий батч`).
|
||||
- `seats_dq.sql`: проверка airplanes стала батч-строгой (`_load_id = текущий батч`).
|
||||
- `flights_dq.sql`: проверка routes стала батч-строгой (`_load_id = текущий батч`).
|
||||
|
||||
Примечание (почему не везде `_load_id = текущий батч`):
|
||||
- Если дочерняя таблица грузится инкрементом, то ссылки могут указывать на “исторические” записи,
|
||||
загруженные в предыдущих батчах → для таких связей корректнее проверять “существует в STG вообще”.
|
||||
- Для `boarding_passes` (full snapshot) ссылки на `tickets/segments` также проверяются по STG-истории,
|
||||
потому что `tickets/segments` не перезагружаются полным снэпшотом каждый запуск.
|
||||
|
||||
### 2.3. Smoke-тесты DAG’ов (статус: исправлено)
|
||||
|
||||
Что сделано:
|
||||
- Тесты усилены: теперь проверяются ключевые зависимости графа через `get_direct_relatives(upstream=False)` и “барьеры” через `get_flat_relatives(upstream=False)`.
|
||||
|
||||
### 2.4. Документация по DAG (статус: синхронизировано)
|
||||
|
||||
Что сделано:
|
||||
- `docs/bookings_to_gp_stage.md` обновлён так, чтобы отражать текущий набор таблиц и шагов пайплайна.
|
||||
|
||||
---
|
||||
|
||||
## 3) Рекомендации по качеству и читаемости (Clean Code для SQL и DAG)
|
||||
|
||||
### 3.1. “Empty window” в инкременте: договориться о политике (fail vs skip) (статус: исправлено)
|
||||
|
||||
Раньше поведение было разным:
|
||||
- часть DQ-скриптов падала, если в окне инкремента 0 строк;
|
||||
- `boarding_passes_dq.sql` делал `RAISE NOTICE` и `RETURN`.
|
||||
|
||||
Обе стратегии допустимы, но в учебном решении важно выбрать одну и объяснить:
|
||||
- **Fail** полезен, когда “ожидаем данные в каждом запуске” (например, учебный генератор должен добавлять день);
|
||||
- **Skip** полезен, когда “окно может быть пустым и это нормально”.
|
||||
|
||||
Выбранная политика для учебного стенда:
|
||||
- Для инкрементальных таблиц (`bookings`, `tickets`, `flights`, `segments`) “пустое окно” **допустимо**:
|
||||
DQ логирует `NOTICE` и завершает проверку, не падая.
|
||||
- Для snapshot-справочников (`airports`, `airplanes`, `routes`, `seats`) пустой источник считаем ошибкой:
|
||||
это почти всегда признак проблем с PXF/источником.
|
||||
|
||||
### 3.2. Комментарии в `*_load.sql`: точнее формулировать “идемпотентность”, а не “дедупликацию источника” (статус: исправлено)
|
||||
|
||||
Типовой паттерн:
|
||||
```sql
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM stg.table WHERE _load_id = '{{ run_id }}' AND key = ext.key
|
||||
);
|
||||
```
|
||||
|
||||
Это в первую очередь защита от повторного запуска того же таска в рамках одного `_load_id` (retry),
|
||||
а не “лечение” дублей в источнике.
|
||||
|
||||
Что сделано:
|
||||
- В `sql/stg/*_load.sql` комментарии приведены к формулировке “идемпотентность при повторном запуске/ретрае”.
|
||||
|
||||
### 3.3. Проверка составных ключей: избегать склейки строк (статус: исправлено)
|
||||
|
||||
Паттерн вида `COUNT(DISTINCT col1 || '|' || col2)` теоретически может давать коллизии (если в данных встречается разделитель).
|
||||
В учебном стенде риск небольшой, но как “эталон” лучше показывать более безопасный подход.
|
||||
|
||||
Что сделано:
|
||||
- Заменили склейку строк на `COUNT(DISTINCT md5(ROW(col1, col2)::text))` в DQ‑скриптах для составных ключей.
|
||||
Такой подход сохраняет DV‑стиль и убирает неоднозначность разделителей.
|
||||
|
||||
---
|
||||
|
||||
## 4) Практические примеры “как улучшить”
|
||||
|
||||
### 4.1. Батч-строгая ссылочная целостность (пример подхода)
|
||||
|
||||
Если таблицы грузятся как snapshot в рамках батча, проверки можно сделать батч-строгими:
|
||||
“в текущем батче ссылки указывают на строки текущего батча”.
|
||||
|
||||
Идея (пример для routes → airports):
|
||||
```sql
|
||||
LEFT JOIN stg.airports AS a
|
||||
ON r.departure_airport = a.airport_code
|
||||
AND a._load_id = v_batch_id
|
||||
```
|
||||
|
||||
### 4.2. Smoke-тест реального графа (минимальный полезный уровень)
|
||||
|
||||
Вместо “таски существуют” лучше проверять ключевые зависимости:
|
||||
```python
|
||||
tickets_dq = dag.get_task("check_tickets_dq")
|
||||
airports_load = dag.get_task("load_airports_to_stg")
|
||||
assert airports_load in tickets_dq.get_direct_relatives(upstream=False)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5) Чек-лист “готово как эталон”
|
||||
|
||||
- [x] В DDL-комментариях нет неверных обещаний про co-location/уникальность ключей.
|
||||
- [x] Для DQ определена и описана политика “0 строк”: где fail, где skip.
|
||||
- [x] DQ ссылочной целостности не маскирует проблемы текущего батча (batch-строгие проверки там, где это уместно).
|
||||
- [x] `docs/bookings_to_gp_stage.md` соответствует фактическому DAG.
|
||||
- [x] Smoke-тесты проверяют хотя бы критические зависимости графа.
|
||||
@@ -0,0 +1,205 @@
|
||||
# План: Денормализация dim_routes + упрощение route_performance
|
||||
|
||||
> **Статус:** реализовано.
|
||||
|
||||
## Контекст
|
||||
|
||||
`dds.dim_routes` хранит только FK-ссылки (`departure_airport`, `arrival_airport`, `airplane_code`) без атрибутов (город, модель самолёта, кол-во мест). Из-за этого витрина `dm.route_performance` вынуждена делать 4-JOIN цепочку вместо одного JOIN к измерению. Это противоречит принципу Кимбалла (измерение должно быть «самодостаточным») и усложняет учебный материал.
|
||||
|
||||
**Цель:** добавить в dim_routes 4 денормализованных колонки, упростить route_performance до 1 JOIN, показать студентам правильную Star Schema.
|
||||
|
||||
---
|
||||
|
||||
## Файлы для изменения
|
||||
|
||||
| # | Файл | Суть |
|
||||
|---|---|---|
|
||||
| 1 | `sql/dds/dim_routes_ddl.sql` | +4 колонки через ALTER TABLE |
|
||||
| 2 | `sql/dds/dim_routes_load.sql` | JOIN к airports/airplanes при INSERT + refresh-шаг |
|
||||
| 3 | `sql/dds/dim_routes_dq.sql` | NOT NULL проверка для новых колонок (current-срез) |
|
||||
| 4 | `airflow/dags/bookings_to_gp_dds.py` | Зависимость: airports+airplanes DQ → routes load |
|
||||
| 5 | `sql/dm/route_performance_load.sql` | Упрощение 4-JOIN → 1-JOIN |
|
||||
| 6 | `tests/test_dags_smoke.py` | Новые assert для зависимости routes от airports/airplanes |
|
||||
| 7 | `docs/internal/db_schema.md` | Обновить схему dim_routes |
|
||||
|
||||
---
|
||||
|
||||
## Шаг 1. DDL — `sql/dds/dim_routes_ddl.sql`
|
||||
|
||||
После существующего `COMMENT ON TABLE` добавить учебный комментарий и 4 ALTER TABLE:
|
||||
|
||||
```sql
|
||||
-- Денормализация атрибутов из SCD1-измерений (dim_airports, dim_airplanes).
|
||||
--
|
||||
-- Учебный комментарий (Kimball Star Schema):
|
||||
-- Измерение должно быть «самодостаточным»: один JOIN к dim_routes —
|
||||
-- и аналитик видит маршрут, города, модель самолёта и кол-во мест.
|
||||
--
|
||||
-- Эти колонки НЕ участвуют в hashdiff. Версия SCD2 фиксирует изменения
|
||||
-- атрибутов маршрута (аэропорт, самолёт, расписание). Если изменится
|
||||
-- название города — обновим отдельным refresh-шагом, не создавая новую версию.
|
||||
ALTER TABLE dds.dim_routes ADD COLUMN IF NOT EXISTS departure_city TEXT;
|
||||
ALTER TABLE dds.dim_routes ADD COLUMN IF NOT EXISTS arrival_city TEXT;
|
||||
ALTER TABLE dds.dim_routes ADD COLUMN IF NOT EXISTS airplane_model TEXT;
|
||||
ALTER TABLE dds.dim_routes ADD COLUMN IF NOT EXISTS total_seats INTEGER;
|
||||
```
|
||||
|
||||
Колонки nullable — `ALTER TABLE ADD COLUMN ... NOT NULL` без DEFAULT упадёт на существующих строках. DQ проверит NOT NULL для current-среза.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 2. Load — `sql/dds/dim_routes_load.sql`
|
||||
|
||||
Структура: 3 фазы вместо 2.
|
||||
|
||||
**Фаза 1 (без изменений):** temp table + hashdiff из ods.routes, UPDATE закрытие версий. Hashdiff **не включает** денормализованные атрибуты.
|
||||
|
||||
**Фаза 2 (изменён INSERT):** при вставке новой версии — LEFT JOIN к dim_airports (×2) и dim_airplanes для заполнения departure_city, arrival_city, airplane_model, total_seats.
|
||||
|
||||
Конкретно: в INSERT (Statement 2, строки 59-107) добавить:
|
||||
- В список колонок: `departure_city, arrival_city, airplane_model, total_seats`
|
||||
- В SELECT: `dep.city, arr.city, air.model, air.total_seats`
|
||||
- LEFT JOIN к dim_airports и dim_airplanes (LEFT — чтобы не терять маршруты при отсутствии аэропорта; DQ поймает)
|
||||
|
||||
**Фаза 3 (новая):** refresh денормализованных атрибутов для всех current-версий:
|
||||
|
||||
```sql
|
||||
-- Фаза 3: Обновление денормализованных атрибутов (refresh).
|
||||
-- Нужна для SCD1-изменений в dim_airports/dim_airplanes (напр. переименование города).
|
||||
-- Обновляем ТОЛЬКО current-версии (valid_to IS NULL).
|
||||
UPDATE dds.dim_routes AS d
|
||||
SET departure_city = dep.city,
|
||||
arrival_city = arr.city,
|
||||
airplane_model = air.model,
|
||||
total_seats = air.total_seats,
|
||||
updated_at = now(),
|
||||
_load_id = '{{ run_id }}',
|
||||
_load_ts = now()
|
||||
FROM dds.dim_airports AS dep,
|
||||
dds.dim_airports AS arr,
|
||||
dds.dim_airplanes AS air
|
||||
WHERE d.valid_to IS NULL
|
||||
AND dep.airport_bk = d.departure_airport
|
||||
AND arr.airport_bk = d.arrival_airport
|
||||
AND air.airplane_bk = d.airplane_code
|
||||
AND (
|
||||
d.departure_city IS DISTINCT FROM dep.city
|
||||
OR d.arrival_city IS DISTINCT FROM arr.city
|
||||
OR d.airplane_model IS DISTINCT FROM air.model
|
||||
OR d.total_seats IS DISTINCT FROM air.total_seats
|
||||
);
|
||||
```
|
||||
|
||||
Этот же шаг при первом запуске заполнит колонки для существующих данных (backfill).
|
||||
|
||||
---
|
||||
|
||||
## Шаг 3. DQ — `sql/dds/dim_routes_dq.sql`
|
||||
|
||||
Перед финальным `RAISE NOTICE` добавить проверку:
|
||||
|
||||
```sql
|
||||
-- Денормализованные поля не пустые в текущих (актуальных) версиях.
|
||||
-- Исторические версии могли быть загружены ДО добавления колонок — пропускаем.
|
||||
SELECT COUNT(*) INTO v_null_count
|
||||
FROM dds.dim_routes
|
||||
WHERE valid_to IS NULL
|
||||
AND (departure_city IS NULL OR arrival_city IS NULL
|
||||
OR airplane_model IS NULL OR total_seats IS NULL);
|
||||
|
||||
IF v_null_count <> 0 THEN
|
||||
RAISE EXCEPTION
|
||||
'DQ FAILED: в current-срезе dim_routes найдены NULL денормализованные поля: %',
|
||||
v_null_count;
|
||||
END IF;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 4. DAG — `airflow/dags/bookings_to_gp_dds.py`
|
||||
|
||||
Убрать `load_dds_dim_routes` из параллельного fan-out, добавить зависимость от DQ airports и airplanes:
|
||||
|
||||
```python
|
||||
# Было:
|
||||
dq_dds_dim_calendar >> [
|
||||
load_dds_dim_airports,
|
||||
load_dds_dim_airplanes,
|
||||
load_dds_dim_tariffs,
|
||||
load_dds_dim_passengers,
|
||||
load_dds_dim_routes, # параллельно
|
||||
]
|
||||
|
||||
# Стало:
|
||||
dq_dds_dim_calendar >> [
|
||||
load_dds_dim_airports,
|
||||
load_dds_dim_airplanes,
|
||||
load_dds_dim_tariffs,
|
||||
load_dds_dim_passengers,
|
||||
]
|
||||
|
||||
# dim_routes зависит от airports и airplanes (денормализация).
|
||||
[dq_dds_dim_airports, dq_dds_dim_airplanes] >> load_dds_dim_routes
|
||||
```
|
||||
|
||||
Транзитивная зависимость от calendar сохраняется через airports/airplanes.
|
||||
|
||||
---
|
||||
|
||||
## Шаг 5. Витрина — `sql/dm/route_performance_load.sql`
|
||||
|
||||
Упростить Шаг 3. Было 4 JOIN → станет 1 JOIN:
|
||||
|
||||
```sql
|
||||
FROM tmp_route_metrics m
|
||||
JOIN dds.dim_routes r_curr
|
||||
ON m.route_bk = r_curr.route_bk AND r_curr.valid_to IS NULL
|
||||
```
|
||||
|
||||
SELECT использует `r_curr.departure_airport AS departure_airport_bk`, `r_curr.departure_city`, `r_curr.airplane_code AS airplane_bk`, `r_curr.airplane_model`, `r_curr.total_seats`. Добавить учебный комментарий: «Благодаря денормализации dim_routes — один JOIN вместо четырёх.»
|
||||
|
||||
---
|
||||
|
||||
## Шаг 6. Тесты — `tests/test_dags_smoke.py`
|
||||
|
||||
Добавить в `test_bookings_to_gp_dds_dag_structure`:
|
||||
|
||||
```python
|
||||
# dim_routes зависит от airports и airplanes (денормализация).
|
||||
_assert_reachable(dag, "dq_dds_dim_airports", "load_dds_dim_routes")
|
||||
_assert_reachable(dag, "dq_dds_dim_airplanes", "load_dds_dim_routes")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Шаг 7. Документация — `docs/internal/db_schema.md`
|
||||
|
||||
Обновить описание dim_routes: добавить 4 новые колонки, пометить «денормализовано из dim_airports/dim_airplanes». Обновить граф зависимостей DAG.
|
||||
|
||||
---
|
||||
|
||||
## Что НЕ меняется
|
||||
|
||||
- **fact_flight_sales_load.sql** — факт резолвит SK через свои JOIN-ы. Без изменений.
|
||||
- **passenger_loyalty, monthly_overview** — используют только `route_bk` из dim_routes.
|
||||
- **airport_traffic** — вообще не джойнит dim_routes.
|
||||
- **hashdiff** — остаётся прежним (только атрибуты из ods.routes).
|
||||
|
||||
---
|
||||
|
||||
## Верификация
|
||||
|
||||
1. `make ddl-gp` — накатить DDL (ALTER TABLE добавит колонки)
|
||||
2. Запустить DAG `bookings_to_gp_dds` — проверить:
|
||||
- airports/airplanes грузятся ДО routes (Graph view)
|
||||
- Фаза 3 (refresh) заполняет departure_city, arrival_city, airplane_model, total_seats
|
||||
- DQ проходит без ошибок
|
||||
3. Запустить DAG `bookings_to_gp_dm` — проверить:
|
||||
- route_performance загружается с 1 JOIN
|
||||
- Витрина содержит корректные города и модели самолётов
|
||||
4. `make test` — smoke-тесты DAG проходят (включая новые assert'ы)
|
||||
5. SQL-проверка:
|
||||
```sql
|
||||
SELECT route_bk, departure_city, arrival_city, airplane_model, total_seats
|
||||
FROM dds.dim_routes WHERE valid_to IS NULL LIMIT 5;
|
||||
```
|
||||
@@ -0,0 +1,220 @@
|
||||
# План ревизии документации
|
||||
|
||||
> Статус: **ЧЕРНОВИК v3** | Дата: 2026-03-10
|
||||
> Контекст: перед Этапом 2 (подготовка main) нужно навести порядок в docs/
|
||||
|
||||
---
|
||||
|
||||
## Проблемы
|
||||
|
||||
- `docs/internal/` — свалка: дизайн-документы, планы, ревью, баг-трекеры, стандарты
|
||||
- 3 архивных плана лежат рядом с живыми документами (неотличимы)
|
||||
- 3 осиротевших документа (никто не ссылается)
|
||||
- `educational-tasks.md` в корне — устарел (раздел 2.2 говорит «ODS/DDS/DM будут позже»)
|
||||
- 5 документов содержат устаревшие фрагменты
|
||||
- `docs/README.md` не знает про несколько живых документов
|
||||
|
||||
---
|
||||
|
||||
## Принципы
|
||||
|
||||
- **Архив замораживается.** Файлы в `docs/archive/` не правим — ссылки внутри них
|
||||
могут быть битыми, это ожидаемо. Они сохраняются как исторические артефакты.
|
||||
- **Активные планы** живут в `docs/plans/`, после выполнения переезжают в `docs/archive/`.
|
||||
- **Студенческий entry point** не должен исчезать: пока `analyst_spec.md` (Этап 3)
|
||||
не создан, в `docs/assignment/` будет заглушка `README.md` со ссылкой на эталонный
|
||||
срез для самостоятельного изучения.
|
||||
|
||||
---
|
||||
|
||||
## Фаза 1. Структура каталогов
|
||||
|
||||
Создать новые каталоги:
|
||||
|
||||
```
|
||||
docs/design/ — дизайн-документы, стандарты, архитектура
|
||||
docs/reference/ — техническая справка, баг-трекеры, бенчмарки
|
||||
docs/plans/ — активные планы работ
|
||||
docs/archive/ — выполненные планы, закрытые ревью
|
||||
docs/assignment/ — заглушка README.md (подготовка для Этапа 3)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Фаза 2. Перемещение файлов
|
||||
|
||||
### В `docs/archive/` (4 файла)
|
||||
|
||||
| Откуда | Файл | Причина |
|
||||
|--------|-------|---------|
|
||||
| корень | `educational-tasks.md` | Устарел, заменён `assignment_design.md` |
|
||||
| `docs/internal/` | `stg_naming_unification_plan.md` | Выполнен |
|
||||
| `docs/internal/` | `dim_routes_denormalization_plan.md` | Выполнен |
|
||||
| `docs/internal/` | `bookings_stg_code_review.md` | Все замечания закрыты |
|
||||
|
||||
### В `docs/plans/` (1 файл)
|
||||
|
||||
| Откуда | Файл | Примечание |
|
||||
|--------|-------|---------|
|
||||
| `docs/internal/` | `docs_restructuring_plan.md` | Этот план — активный; после выполнения → `docs/archive/` |
|
||||
|
||||
### В `docs/design/` (9 файлов из `docs/internal/`)
|
||||
|
||||
| Файл | Роль |
|
||||
|------|------|
|
||||
| `PRD.md` | Стратегия продукта |
|
||||
| `assignment_design.md` | Дизайн курсового задания |
|
||||
| `naming_conventions.md` | Стандарт нейминга (единый источник) |
|
||||
| `db_schema.md` | Схема БД всех слоёв DWH |
|
||||
| `bookings_stg_design.md` | Дизайн STG-слоя |
|
||||
| `bookings_ods_design.md` | Дизайн ODS-слоя |
|
||||
| `bookings_dds_design.md` | Дизайн DDS-слоя |
|
||||
| `bookings_dm_design.md` | Дизайн DM-слоя |
|
||||
| `architecture_review.md` | Архитектурные решения (ADR) |
|
||||
|
||||
### В `docs/reference/` (5 файлов из `docs/internal/`)
|
||||
|
||||
| Файл | Роль |
|
||||
|------|------|
|
||||
| `pxf_bookings.md` | PXF: настройка, проблемы |
|
||||
| `bookings_tz.md` | Источник bookings-db |
|
||||
| `bookings_db_issues.md` | Известные проблемы bookings-db |
|
||||
| `bookings_generation_benchmark.md` | Бенчмарк генерации |
|
||||
| `qa-plan.md` | План отладки пайплайна |
|
||||
|
||||
**Итог:** `docs/internal/` опустеет → удалить.
|
||||
|
||||
---
|
||||
|
||||
## Фаза 3. Обновление перекрёстных ссылок
|
||||
|
||||
### Категория A. Корневые и публичные файлы (ссылаются на `docs/internal/`)
|
||||
|
||||
| Файл | Ссылок | Детали замен |
|
||||
|------|--------|------|
|
||||
| `AGENTS.md` | 2 | строки 15, 45: `docs/internal/naming_conventions.md` → `docs/design/naming_conventions.md` |
|
||||
| `TODO.md` | 6 | строки 6, 7, 43: → `docs/design/`; строка 121: → `docs/reference/pxf_bookings.md`; строка 128: → `docs/reference/bookings_db_issues.md`; строка 141: → `docs/archive/dim_routes_denormalization_plan.md` |
|
||||
| `docs/README.md` | 7 | строки 18-24: все `internal/*` → `design/*` или `reference/*` |
|
||||
| `docs/bookings_to_gp_stage.md` | 2 | строка 151: → `reference/pxf_bookings.md`; строка 156: → `archive/bookings_stg_code_review.md` |
|
||||
| `docs/stack.md` | 1 | строка 85: → `reference/pxf_bookings.md` |
|
||||
|
||||
### Категория B. Файлы внутри `docs/design/` и `docs/reference/`
|
||||
|
||||
Полный реестр ссылок с `docs/internal/` в файлах, которые переедут в `design/` или
|
||||
`reference/`. Все требуют обновления — либо кросс-каталожные пути, либо display-тексты.
|
||||
|
||||
**B1. Кросс-каталожные ссылки (ссылка ведёт в другой каталог — путь сломается):**
|
||||
|
||||
| Файл (→ design/) | Строка | Ссылка | Новый путь |
|
||||
|------|--------|--------|------|
|
||||
| `db_schema.md` | 426 | `bookings_tz.md` (отн.) | `../reference/bookings_tz.md` |
|
||||
| `db_schema.md` | 427 | `pxf_bookings.md` (отн.) | `../reference/pxf_bookings.md` |
|
||||
| `db_schema.md` | 425 | `bookings_stg_code_review.md` (отн.) | `../archive/bookings_stg_code_review.md` |
|
||||
| `bookings_stg_design.md` | 5 | `docs/internal/bookings_tz.md` | `../reference/bookings_tz.md` |
|
||||
| `bookings_stg_design.md` | 130 | `docs/internal/bookings_tz.md` | `../reference/bookings_tz.md` |
|
||||
| `bookings_stg_design.md` | 131 | `docs/internal/pxf_bookings.md` | `../reference/pxf_bookings.md` |
|
||||
|
||||
| Файл (→ reference/) | Строка | Ссылка | Новый путь |
|
||||
|------|--------|--------|------|
|
||||
| `qa-plan.md` | 10 | `docs/internal/bookings_db_issues.md` | `bookings_db_issues.md` (тот же каталог) |
|
||||
|
||||
**B2. Внутрикаталожные, но с полным путём `docs/internal/...` (путь не сломается
|
||||
для ссылок с относительным target, но display-текст устареет):**
|
||||
|
||||
| Файл (→ design/) | Строки | Что обновить |
|
||||
|------|--------|------|
|
||||
| `PRD.md` | 209 | `docs/internal/naming_conventions.md` → `docs/design/naming_conventions.md` |
|
||||
| `bookings_ods_design.md` | 48 | display-текст `docs/internal/naming_conventions.md` → `docs/design/naming_conventions.md` |
|
||||
| `bookings_dds_design.md` | 8 | `docs/internal/bookings_ods_design.md` → `docs/design/bookings_ods_design.md` |
|
||||
| `bookings_dds_design.md` | 185 | display-текст `docs/internal/naming_conventions.md` → `docs/design/naming_conventions.md` |
|
||||
| `bookings_dds_design.md` | 828-829 | `docs/internal/bookings_dds_design.md`, `docs/internal/db_schema.md` → `docs/design/...` |
|
||||
| `bookings_dds_design.md` | 945 | `docs/internal/db_schema.md` → `docs/design/db_schema.md` |
|
||||
| `bookings_dm_design.md` | 279 | `docs/internal/db_schema.md` → `docs/design/db_schema.md` |
|
||||
| `bookings_dm_design.md` | 283, 291 | `docs/internal/bookings_dm_design.md` → `docs/design/bookings_dm_design.md` |
|
||||
| `bookings_dm_design.md` | 395 | `docs/internal/naming_conventions.md` → `docs/design/naming_conventions.md` |
|
||||
| `db_schema.md` | 26 | display-текст `docs/internal/naming_conventions.md` → `docs/design/naming_conventions.md` |
|
||||
| `db_schema.md` | 421-423 | display-тексты `docs/internal/bookings_*_design.md` → `docs/design/...` |
|
||||
| `architecture_review.md` | 70 | `docs/internal/bookings_dm_design.md` → `docs/design/bookings_dm_design.md` |
|
||||
| `architecture_review.md` | 146 | `docs/internal/distribution_strategy.md` → **удалить путь** (файл не существует, оставить как текстовый backlog-пункт без ссылки) |
|
||||
|
||||
### Категория C. Ссылки на `educational-tasks.md`
|
||||
|
||||
| Файл | Действие |
|
||||
|------|----------|
|
||||
| `README.md` (корень) | Заменить ссылку на `educational-tasks.md` → `docs/assignment/` |
|
||||
| `docs/README.md` | Заменить ссылку на `educational-tasks.md` → `assignment/` |
|
||||
|
||||
### Категория D. Файлы в `docs/archive/` — НЕ ТРОГАЕМ
|
||||
|
||||
Архивные файлы замораживаются. Ссылки внутри них могут быть битыми — это ожидаемо.
|
||||
|
||||
### Комментарий в SQL
|
||||
|
||||
`sql/dm/sales_report_ddl.sql` строка 52: `naming_conventions.md` (без пути) —
|
||||
оставить как есть (комментарий, не ссылка; путь и так неточный).
|
||||
|
||||
---
|
||||
|
||||
## Фаза 4. Актуализация содержания
|
||||
|
||||
| Файл (новый путь) | Что сделать |
|
||||
|------|-------------|
|
||||
| `docs/design/architecture_review.md` | DM завершён (5/5 витрин), пометить выполненные P2; строка 146 — убрать путь к несуществующему `distribution_strategy.md`, оставить как текстовый backlog-пункт |
|
||||
| `docs/design/db_schema.md` | Добавить DM-слой, убрать выполненный TODO |
|
||||
| `TESTING.md` | Убрать артефакт «Docker-стенд не запускался» |
|
||||
| `docs/dag_execution_order.md` | Добавить все 4 DDL DAG-а |
|
||||
| `docs/reference/pxf_bookings.md` | Исправить нумерацию разделов (7→9→8→10 → последовательную) |
|
||||
|
||||
---
|
||||
|
||||
## Фаза 5. Обновление индексов и заглушка assignment
|
||||
|
||||
### `docs/README.md`
|
||||
|
||||
Переписать структуру: разделы по каталогам (`design/`, `reference/`, `plans/`,
|
||||
`archive/`, `assignment/`). Включить ранее пропущенные документы: `db_schema.md`,
|
||||
`dag_execution_order.md`, `e2e-etl-test-protocol.md`, `agent-dag-testing.md`.
|
||||
|
||||
### `README.md` (корень)
|
||||
|
||||
Заменить ссылку на `educational-tasks.md` → `docs/assignment/`, проверить остальные.
|
||||
|
||||
### `docs/assignment/README.md` (новый файл)
|
||||
|
||||
Временная заглушка:
|
||||
- Указание, что курсовые задания появятся в Этапе 3
|
||||
- Ссылка на эталонный срез (DAG-и STG→ODS→DDS→DM) для самостоятельного изучения
|
||||
- Ссылка на `docs/design/assignment_design.md` для менторов
|
||||
|
||||
---
|
||||
|
||||
## Фаза 6. Финальная проверка
|
||||
|
||||
Шаги выполняются строго по порядку:
|
||||
|
||||
1. [ ] `make test` — тесты проходят
|
||||
2. [ ] `make lint` — стиль кода
|
||||
3. [ ] Удалить пустой каталог `docs/internal/`
|
||||
4. [ ] Перенести план из `docs/plans/` в `docs/archive/docs_restructuring_plan.md`
|
||||
5. [ ] grep по `internal/` в живых .md файлах (`rg --glob '!docs/archive/*'`) — нет битых ссылок
|
||||
6. [ ] grep по `educational-tasks` в живых .md файлах (`rg --glob '!docs/archive/*'`) — нет битых ссылок
|
||||
|
||||
---
|
||||
|
||||
## Порядок выполнения
|
||||
|
||||
Фазы 1→2→3 делаются вместе (иначе ссылки будут битыми).
|
||||
Фаза 4 — независима, можно параллельно.
|
||||
Фаза 5 — после всех перемещений.
|
||||
Фаза 6 — в конце.
|
||||
|
||||
---
|
||||
|
||||
## Что это даёт следующим этапам
|
||||
|
||||
| Этап | Как помогает |
|
||||
|------|-------------|
|
||||
| **Этап 2** (подготовка main) | Чистая структура — понятно, что удалять, что оставлять |
|
||||
| **Этап 3** (ТЗ от аналитика) | Готовый каталог `docs/assignment/` с заглушкой, место для `analyst_spec.md` |
|
||||
| **Этап 4** (валидационный DAG) | `docs/reference/qa-plan.md` — рядом с другими справочными |
|
||||
| **Этап 5** (ветка solution) | Архив отделён — не попадёт в ветку solution |
|
||||
@@ -0,0 +1,91 @@
|
||||
# Учебные задания по стенду
|
||||
|
||||
Этот документ собирает в одном месте задания для менти.
|
||||
Он разбит на блоки: от архитектуры Greenplum и демо‑БД bookings до реализации аналитических слоев DWH.
|
||||
|
||||
Если вы только начинаете, выполняйте задания по порядку.
|
||||
|
||||
---
|
||||
|
||||
## 1. Greenplum и модель данных (введение)
|
||||
|
||||
В следующих заданиях мы будем опираться на демо‑БД bookings (Postgres) и слой STG в Greenplum.
|
||||
На этом этапе достаточно бегло посмотреть на структуру и понять общую идею, детальная проработка пойдёт позже.
|
||||
|
||||
### 1.1. Знакомство с демо‑БД bookings
|
||||
|
||||
1. Прочитайте `bookings/README.md` — какие сервисы и команды относятся к демобазе.
|
||||
2. Поднимите стенд и выполните:
|
||||
- `make up`
|
||||
- `make bookings-init`
|
||||
3. Подключитесь к демобазе:
|
||||
- `make bookings-psql`
|
||||
- посмотрите таблицы в схеме `bookings` (например, `\dt bookings.*`).
|
||||
4. Найдите таблицу `bookings.bookings` и посмотрите на её структуру:
|
||||
- какие типы колонок используются;
|
||||
- какие поля выглядят как ключи, даты, суммы.
|
||||
|
||||
### 1.2. Знакомство с STG в Greenplum
|
||||
|
||||
1. Прочитайте `sql/stg/bookings_ddl.sql` и краткое описание потока `docs/bookings_to_gp_stage.md` (если интересно — `docs/internal/bookings_stg_design.md`).
|
||||
2. Ответьте себе на вопросы:
|
||||
- чем внешняя таблица `stg.bookings_ext` отличается от внутренней `stg.bookings`;
|
||||
- зачем нужны тех.колонки `event_ts`, `_load_ts`, `_load_id`;
|
||||
- чем слой STG отличается от итоговых витрин (DDS/DM) с точки зрения моделирования.
|
||||
3. Выполните `make ddl-gp`, затем зайдите в Greenplum (`make gp-psql`) и проверьте наличие схемы и таблиц:
|
||||
- `\dn` и `\dt stg.*`
|
||||
- `SELECT * FROM stg.bookings LIMIT 5;` (после запуска соответствующего DAG).
|
||||
|
||||
### 1.3. Как генерируются учебные данные bookings
|
||||
|
||||
1. Откройте файл `bookings/generate_next_day.sql` и ответьте себе на вопросы:
|
||||
- с какой даты начинается генерация данных (посмотрите на GUC `bookings.start_date` и переменную `v_start_cfg`);
|
||||
- сколько дней генерируется при первой установке (переменная `bookings.init_days`);
|
||||
- что происходит, если таблица `bookings.bookings` уже не пустая.
|
||||
2. В демобазе (`make bookings-psql`) выполните:
|
||||
- `SELECT min(book_date), max(book_date) FROM bookings.bookings;`
|
||||
- затем запустите `make bookings-generate-day` и повторите запрос — как изменился максимальный день?
|
||||
3. Откройте `sql/src/bookings_generate_day_if_missing.sql` и обратите внимание, что:
|
||||
- логическая дата запуска DAG (`{{ ds }}`) не влияет на выбор дня генерации;
|
||||
- скрипт всегда смотрит на `max(book_date)` и добавляет **следующий** день (или несколько стартовых дней, если база пуста).
|
||||
4. Сделайте вывод: генератор всегда «шагает» по датам вперёд от максимальной даты, поэтому:
|
||||
- при `make bookings-init` вы получаете готовые данные из seed-дампа (при `make bookings-generate` генератор создаст `BOOKINGS_INIT_DAYS` дней начиная с `BOOKINGS_START_DATE`);
|
||||
- при последующих вызовах (`make bookings-generate-day` или DAG) добавляется ровно один новый день.
|
||||
|
||||
---
|
||||
|
||||
## 2. DAG bookings_to_gp_stage (заготовка заданий)
|
||||
|
||||
Этот DAG показывает путь данных от демо‑БД bookings в Postgres до сырого слоя STG в Greenplum.
|
||||
Сейчас он уже реализован как учебный пример, а в будущем вокруг него появятся отдельные задания по моделированию DWH.
|
||||
|
||||
### 2.1. Что есть сейчас
|
||||
|
||||
1. Откройте `airflow/dags/bookings_to_gp_stage.py`.
|
||||
2. Найдите в коде ссылки на SQL‑файлы:
|
||||
- `sql/src/bookings_generate_day_if_missing.sql`
|
||||
- `sql/stg/bookings_load.sql`
|
||||
- `sql/stg/bookings_dq.sql`
|
||||
3. Соотнесите шаги DAG с документом `docs/bookings_to_gp_stage.md`:
|
||||
- генерация учебного дня в `bookings.bookings`;
|
||||
- загрузка инкремента в `stg.bookings`;
|
||||
- проверка количества строк между источником и STG.
|
||||
4. Обратите внимание, как в DAG используется логическая дата запуска:
|
||||
- `{{ run_id }}` используется как `_load_id` — метка загрузки в таблице `stg.bookings` для конкретного запуска;
|
||||
- сами даты данных (какие дни есть в `bookings.bookings`) определяются генератором по `max(book_date)`, а не по `ds`.
|
||||
|
||||
На этом этапе достаточно понять общую цепочку. Детальные задания по переработке модели данных и построению ODS/DDS/DM слоёв будут добавлены позже.
|
||||
|
||||
### 2.2. Идеи для будущих заданий (черновик)
|
||||
|
||||
> Ниже — набросок задач, к которым мы вернёмся, когда базовые темы по Airflow будут освоены.
|
||||
|
||||
Планируемые направления:
|
||||
|
||||
- Спроектировать модель данных для основных сущностей демобазы bookings (рейсы, билеты, перелёты) в слоях ODS/DDS/DM.
|
||||
- Реализовать слой ODS поверх STG, аккуратно работая с временными атрибутами и ключами.
|
||||
- Построить витрины (DM) для типичных аналитических вопросов: загрузка рейсов, выручка по направлениям, динамика бронирований.
|
||||
- Добавить DAG’и, которые используют `stg.bookings` как источник и строят следующие слои DWH.
|
||||
- Расширить проверки качества данных для потоков bookings → STG → витрины.
|
||||
|
||||
Когда будете готовы к этим темам, вернитесь к этому разделу — он станет основой для следующего «модуля» лабораторных заданий.
|
||||
@@ -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 механические замены в каждом
|
||||
Reference in New Issue
Block a user