docs(main): онбординг студента — гид, маркетинг, адаптация docs и docstrings

- Зачем:
  - на main docs и DAG-docstrings утверждали «все реализовано», хотя 18 заглушек;
    студент застревал после шага 6 без явного «что дальше».
- Что:
  - README: маркетинг-баннер, шаг 6 (только эталонные таблицы), шаг 7 → задания.
  - docs/assignment/README: полный гид студента (эталон → ТЗ → заглушки → validate).
  - analyst_spec: DAG-интеграция — «таски уже подключены, менять DAG не нужно».
  - docs ODS/DDS/DM: пометки заглушек, адаптация секций проверки результата.
  - 4 DAG docstrings: эталон vs задания (заглушки).
- Проверка:
  - make test (4 passed, 14 skipped), make lint (clean).
  - grep «все 5 витрин|все реализован» — ложных утверждений без оговорок нет.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 10:49:40 +03:00
co-authored by Claude Opus 4.6
parent 5b98a5b203
commit af27314878
10 changed files with 139 additions and 41 deletions
+25 -15
View File
@@ -1,6 +1,7 @@
# DAG `bookings_to_gp_dm`: `dds` → `dm` в Greenplum
Этот DAG — учебный пример загрузки слоя **DM** (Data Mart / витрины) из текущего состояния **DDS**.
Эталон: витрина `sales_report`. Остальные 4 витрины — задания (заглушки `SELECT 1;` на ветке `main`).
Все 5 витрин загружаются **параллельно** и демонстрируют разные стратегии загрузки —
это ключевая учебная ценность данного DAG.
@@ -8,13 +9,13 @@
Загружает 5 витрин параллельно, для каждой — пара `load → dq`:
| Витрина | Зерно (grain) | Паттерн загрузки |
|---------|---------------|------------------|
| `dm.sales_report` | (flight_date, departure_airport_sk, arrival_airport_sk, tariff_sk) | Инкрементальный UPSERT (HWM по датам) |
| `dm.route_performance` | route_bk | Full Rebuild (TRUNCATE + INSERT) |
| `dm.passenger_loyalty` | passenger_sk | Инкрементальный UPSERT (HWM по затронутым ключам) |
| `dm.airport_traffic` | (traffic_date, airport_sk) | Инкрементальный UPSERT (HWM по датам) |
| `dm.monthly_overview` | (year_actual, month_actual, airplane_sk) | Инкрементальный UPSERT (HWM по месяцам) |
| Витрина | Зерно (grain) | Паттерн загрузки | Статус на main |
|---------|---------------|------------------|----------------|
| `dm.sales_report` | (flight_date, departure_airport_sk, arrival_airport_sk, tariff_sk) | Инкрементальный UPSERT (HWM по датам) | Эталон |
| `dm.route_performance` | route_bk | Full Rebuild (TRUNCATE + INSERT) | Задание (заглушка) |
| `dm.passenger_loyalty` | passenger_sk | Инкрементальный UPSERT (HWM по затронутым ключам) | Задание (заглушка) |
| `dm.airport_traffic` | (traffic_date, airport_sk) | Инкрементальный UPSERT (HWM по датам) | Задание (заглушка) |
| `dm.monthly_overview` | (year_actual, month_actual, airplane_sk) | Инкрементальный UPSERT (HWM по месяцам) | Задание (заглушка) |
## Что должно быть готово перед запуском
@@ -68,6 +69,9 @@ start_dm
- **NULLIF** для защиты от деления на ноль (`boarding_rate = boarded / NULLIF(sold, 0)`);
- **Денормализация**: города и коды аэропортов тянутся в витрину из измерений.
> **Задание.** На ветке `main` этот скрипт — заглушка (`SELECT 1;`).
> Реализуйте по описанию ниже и ТЗ в `analyst_spec.md`.
### 2) `load_dm_route_performance` → `dq_dm_route_performance`
- **SQL:** `sql/dm/route_performance_load.sql`, `sql/dm/route_performance_dq.sql`
@@ -89,6 +93,9 @@ start_dm
Метрики: `avg_load_factor = total_boarded / (total_flights * total_seats)`,
`avg_ticket_price = total_revenue / total_tickets`.
> **Задание.** На ветке `main` этот скрипт — заглушка (`SELECT 1;`).
> Реализуйте по описанию ниже и ТЗ в `analyst_spec.md`.
### 3) `load_dm_passenger_loyalty` → `dq_dm_passenger_loyalty`
- **SQL:** `sql/dm/passenger_loyalty_load.sql`, `sql/dm/passenger_loyalty_dq.sql`
@@ -105,6 +112,9 @@ start_dm
- Фильтрация `passenger_sk IS NOT NULL` — защита от неконсистентных фактов (NULL SK
при data quality аномалиях в измерениях).
> **Задание.** На ветке `main` этот скрипт — заглушка (`SELECT 1;`).
> Реализуйте по описанию ниже и ТЗ в `analyst_spec.md`.
### 4) `load_dm_airport_traffic` → `dq_dm_airport_traffic`
- **SQL:** `sql/dm/airport_traffic_load.sql`, `sql/dm/airport_traffic_dq.sql`
@@ -116,6 +126,9 @@ start_dm
в два «события» (вылет из одного аэропорта и прилёт в другой).
Это позволяет собрать единую статистику аэропорта (departures + arrivals) в одном проходе.
> **Задание.** На ветке `main` этот скрипт — заглушка (`SELECT 1;`).
> Реализуйте по описанию ниже и ТЗ в `analyst_spec.md`.
### 5) `load_dm_monthly_overview` → `dq_dm_monthly_overview`
- **SQL:** `sql/dm/monthly_overview_load.sql`, `sql/dm/monthly_overview_dq.sql`
@@ -145,19 +158,16 @@ make gp-psql
```sql
SELECT COUNT(*) FROM dm.sales_report;
SELECT COUNT(*) FROM dm.route_performance;
SELECT COUNT(*) FROM dm.passenger_loyalty;
SELECT COUNT(*) FROM dm.airport_traffic;
SELECT COUNT(*) FROM dm.monthly_overview;
SELECT COUNT(*) FROM dm.route_performance; -- будет непусто после реализации задания
SELECT COUNT(*) FROM dm.passenger_loyalty; -- будет непусто после реализации задания
SELECT COUNT(*) FROM dm.airport_traffic; -- будет непусто после реализации задания
SELECT COUNT(*) FROM dm.monthly_overview; -- будет непусто после реализации задания
-- Инвариант sales_report: посаженных не больше, чем продано
SELECT COUNT(*) FROM dm.sales_report WHERE tickets_sold < passengers_boarded;
-- Нет дублей по бизнес-ключу route_performance
SELECT route_bk, COUNT(*) FROM dm.route_performance GROUP BY route_bk HAVING COUNT(*) > 1;
```
Ожидаемо: все витрины непусты, инварианты соблюдены, дублей нет.
Ожидаемо: `dm.sales_report` непуста. Остальные — после реализации заданий.
## Типичные ошибки