From df8c2c36fa42803f6483e055489fc805824e71f4 Mon Sep 17 00:00:00 2001 From: Dmitry Dementev Date: Sun, 8 Mar 2026 18:35:24 +0300 Subject: [PATCH] =?UTF-8?q?docs(internal):=20=D1=80=D0=B0=D0=B7=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D1=91=D0=BD=20PRD=20=D0=BD=D0=B0=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=B0=D1=82=D0=B5=D0=B3=D0=B8=D1=8E=20=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D0=B9=D0=BD=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Зачем: - PRD стал перегруженным — смешаны стратегические и тактические решения - Что: - PRD.md сокращён до стратегии (видение, аудитория, скоуп, критерии, риски) - создан assignment_design.md (эталонный срез, порядок выполнения, SCD2-подход, валидационный DAG, формат ТЗ) - зафиксированы решения: эталон sales_report, валидационный DAG, SCD2 как задание с подсказками - Проверка: - просмотр файлов docs/internal/PRD.md и docs/internal/assignment_design.md Co-Authored-By: Claude Opus 4.6 --- docs/internal/PRD.md | 174 ++++------------------------- docs/internal/assignment_design.md | 139 +++++++++++++++++++++++ 2 files changed, 160 insertions(+), 153 deletions(-) create mode 100644 docs/internal/assignment_design.md diff --git a/docs/internal/PRD.md b/docs/internal/PRD.md index e93825c..0d40fc3 100644 --- a/docs/internal/PRD.md +++ b/docs/internal/PRD.md @@ -134,30 +134,24 @@ pgmeta (Postgres 16) ─────────────> Airflow (webserver ## 6. Педагогическая модель +Подробности — в [assignment_design.md](assignment_design.md). + ### Принцип: «Эталонный срез + ТЗ» Студент получает репозиторий, в котором: -1. **Эталонный вертикальный срез** — полностью реализованная цепочка для одной - витрины DM и всех её источников вниз по слоям (STG → ODS → DDS → DM). - Это — образец, на который студент ориентируется. - -2. **ТЗ от аналитика** — Markdown-документ с описанием остальных таблиц: - маппинги полей, бизнес-правила, ожидаемая гранулярность, тип SCD. - -3. **Частично готовый DAG** — Python-файлы DAG с реализованными тасками - эталонного среза. Студент добавляет свои таски по аналогии. - -4. **DQ-проверки** — готовые проверки, - которые студент запускает для самоконтроля. +1. **Эталонный вертикальный срез** — полностью реализованная цепочка + `sales_report` и все её источники вниз по слоям (STG → ODS → DDS → DM). +2. **ТЗ от аналитика** — описание остальных таблиц + ([analyst_spec.md](../assignment/analyst_spec.md)). +3. **Частично готовый DAG** — студент добавляет свои таски по аналогии. +4. **Валидационный DAG** — студент запускает для самоконтроля. ### Что делает студент -- Пишет DDL для назначенных таблиц (`*_ddl.sql`) -- Пишет SQL-загрузки (`*_load.sql`) -- При необходимости пишет DQ-проверки (`*_dq.sql`) +- Пишет DDL, SQL-загрузки, DQ-проверки для назначенных таблиц - Добавляет таски в существующий DAG -- Проверяет результат через DQ и запросы в Greenplum +- Проверяет результат через валидационный DAG и запросы в Greenplum ### Что студент НЕ делает @@ -166,131 +160,9 @@ pgmeta (Postgres 16) ─────────────> Airflow (webserver - Не настраивает Airflow Connections (преднастроены) - Не работает с PXF-конфигурацией (настроен) -### Выбор эталонного среза - -### Эталонный срез: витрина `sales_report` - -Эталоном выбрана витрина `dm.sales_report` и вся её цепочка вниз по слоям. - -**Почему `sales_report`:** -- Покрывает SCD1 (airports, tariffs), HWM-инкремент, fact load -- Богатая денормализация — хороший образец для подражания -- Средняя сложность — не пугает, но и не тривиальна - -**Эталонные таблицы (даны студенту):** - -| Слой | Таблицы | -|------|-----------------------------------------------------------------| -| DM | `sales_report` | -| DDS | `fact_flight_sales`, `dim_airports` (SCD1), `dim_tariffs` (SCD1), `dim_calendar` | -| ODS | `bookings`, `tickets`, `segments`, `flights`, `boarding_passes`, `airports` | -| STG | `bookings`, `tickets`, `segments`, `flights`, `boarding_passes`, `airports` | - -**Задание студенту:** - -| Слой | Таблицы | Что нового для студента | -|------|-------------------------------------------------------------------|--------------------------------------------------| -| STG | `airplanes`, `seats`, `routes` | Практика по аналогии с эталоном | -| ODS | `airplanes`, `seats`, `routes` | Практика SCD1 UPSERT по аналогии | -| DDS | `dim_airplanes` (SCD1), `dim_passengers` (SCD1), `dim_routes` (SCD2) | **SCD2 — ключевой вызов курсовой** | -| DM | `airport_traffic`, `monthly_overview`, `route_performance`, `passenger_loyalty` | Разная сложность (от простой к сложной) | - -**Рекомендуемый порядок выполнения для студента:** -1. STG (airplanes, seats, routes) — разминка, по аналогии -2. ODS (airplanes, seats, routes) — закрепление UPSERT -3. DDS dim_airplanes, dim_passengers (SCD1) — новые измерения -4. DDS dim_routes (**SCD2**) — ключевой вызов -5. DM airport_traffic — простая витрина, похожа на sales_report -6. DM route_performance — TRUNCATE+INSERT, SCD2-агрегация по BK -7. DM monthly_overview — двухуровневая агрегация -8. DM passenger_loyalty — самая сложная, пересчёт истории - -### SCD2 в задании: подход «рецепт без готового SQL» - -Реализация `dim_routes` (SCD2) — ключевой вызов курсовой. Студент делает это -самостоятельно, но ТЗ содержит пошаговую подсказку: - -1. Алгоритм SCD2 текстом (без SQL): - - Вычисли `hashdiff` по набору атрибутов (атрибуты перечислены в ТЗ) - - Найди строки, у которых `hashdiff` изменился - - Закрой старую версию (`valid_to = текущая_дата`) - - Вставь новую версию (`valid_from = текущая_дата`, `valid_to = NULL`) -2. Формула hashdiff: `md5(concat_ws('|', field1, field2, ...))` -3. Ссылка на `naming_conventions.md` (поля `valid_from`, `valid_to`, `hashdiff`) -4. Напоминание: полуоткрытый интервал `[valid_from, valid_to)` -5. Если застрял — ветка `solution` - -Самостоятельная реализация — ключ к запоминанию. SCD2 — обязательный вопрос -на собеседованиях DE, и студент должен уметь объяснить его на основе -собственного опыта. - --- -### 6.1. Валидационный DAG (`bookings_validate`) - -Отдельный DAG для самопроверки студента. Запускается вручную в Airflow UI -после реализации заданий. Таски сгруппированы по слоям — студент видит, -где именно проблема. - -**Примерная структура тасков:** - -``` -bookings_validate -├── validate_stg -│ ├── check_stg_airplanes_exists (таблица создана, >0 строк) -│ ├── check_stg_seats_exists -│ └── check_stg_routes_exists -├── validate_ods -│ ├── check_ods_airplanes_rowcount (ODS >= STG по кол-ву уникальных BK) -│ ├── check_ods_seats_rowcount -│ ├── check_ods_routes_rowcount -│ └── check_ods_no_null_pks (PK not null) -├── validate_dds -│ ├── check_dim_airplanes_exists -│ ├── check_dim_passengers_exists -│ ├── check_dim_routes_scd2 (valid_from/valid_to корректны) -│ └── check_dim_routes_no_gaps (нет «дыр» в версиях SCD2) -└── validate_dm - ├── check_airport_traffic_exists - ├── check_monthly_overview_exists - ├── check_route_performance_exists - └── check_passenger_loyalty_exists -``` - -**Реализация:** `PostgresOperator` + SQL-скрипты в `sql/validate/`. -Каждый SQL-скрипт выполняет SELECT и бросает исключение (через -`DO $$ ... RAISE EXCEPTION ... $$`), если проверка не пройдена. -Сообщения об ошибках — дружелюбные, с подсказкой что делать дальше. - -**Ключевые проверки:** -- Таблицы существуют и содержат данные -- PK не содержат NULL -- SCD2: `valid_to IS NULL` для текущих версий, нет перекрытий интервалов -- Кросс-слойная консистентность (row count ODS vs STG) -- DM-витрины содержат данные за загруженные дни - ---- - -## 7. Формат ТЗ от аналитика - -Файл: `docs/assignment/analyst_spec.md` (или несколько файлов по слоям). - -Для каждой таблицы-задания документ содержит: - -- **Имя таблицы** и целевая схема (stg / ods / dds / dm) -- **Описание** — что хранит таблица, бизнес-смысл -- **Список полей** с типами и описанием -- **Маппинг источников** — откуда берётся каждое поле -- **Бизнес-правила и фильтры** (если есть) -- **Тип историзации** (SCD1 / SCD2 / snapshot / append) -- **Гранулярность** (одна строка = ?) -- **Distribution key** (подсказка или задание на выбор) - -Формат — приближен к реальным ТЗ, которые студент встретит на работе. - ---- - -## 8. Ветки и workflow +## 7. Ветки и workflow ``` main (стартовое состояние) @@ -298,7 +170,7 @@ main (стартовое состояние) ├── ТЗ от аналитика ├── Инфраструктура (Docker, Make, PXF) ├── Заглушки / TODO-маркеры для студенческих заданий - └── DQ-проверки для самоконтроля + └── Валидационный DAG для самоконтроля solution (полное решение) └── Все таблицы реализованы — эталон для самопроверки @@ -314,21 +186,21 @@ solution (полное решение) 5. Запускает DDL-DAG'и (эталонные таблицы создаются) 6. Запускает ETL-DAG'и — эталонный срез работает 7. Реализует задания из ТЗ (SQL + таски в DAG) -8. Проверяет себя через DQ +8. Проверяет себя через валидационный DAG 9. `make bookings-generate-day` — генерирует новый день, проверяет инкрементальность 10. Защищает работу перед ментором --- -## 9. Критерии приёмки курсовой +## 8. Критерии приёмки курсовой ### Для студента (самопроверка) - [ ] Стенд поднимается (`make up`) без ошибок - [ ] Все DAG'и проходят без failed-тасков - [ ] Данные доезжают от STG до DM -- [ ] DQ-проверки проходят на всех реализованных таблицах +- [ ] Валидационный DAG проходит на всех реализованных таблицах - [ ] После `make bookings-generate-day` + повторного запуска DAG данные корректно доливаются (инкрементальность работает) @@ -343,7 +215,7 @@ solution (полное решение) --- -## 10. Ограничения и риски +## 9. Ограничения и риски | Риск / ограничение | Митигация | |--------------------------------------------|-------------------------------------------------| @@ -361,7 +233,7 @@ solution (полное решение) --- -## 11. Таймлайн +## 10. Таймлайн | Когда | Что | |------------------|------------------------------------------------------------| @@ -370,16 +242,12 @@ solution (полное решение) --- -## 12. Открытые вопросы (TODO) +## 11. Открытые вопросы (TODO) -1. ~~**Выбор эталонного среза**~~ — **РЕШЕНО.** Эталон: `sales_report` и её - цепочка. Задание: остальные 4 витрины + 3 STG/ODS + 3 DDS-измерения. - SCD2 (`dim_routes`) — задание с подсказками в ТЗ. (Раздел 6) +1. ~~**Выбор эталонного среза**~~ — **РЕШЕНО.** См. [assignment_design.md](assignment_design.md). -2. ~~**Формат DQ для самоконтроля**~~ — **РЕШЕНО.** Отдельный валидационный DAG - (`bookings_validate.py`). Под капотом — SQL-проверки. Студент запускает DAG - в Airflow UI и видит красные/зелёные таски по слоям. Дополнительный бонус — - практика чтения логов Airflow. (см. Раздел 6.1 ниже) +2. ~~**Формат DQ для самоконтроля**~~ — **РЕШЕНО.** Валидационный DAG. + См. [assignment_design.md](assignment_design.md). 3. **Вынос CSV-пайплайна** — перенести `csv_to_greenplum.py`, `csv_to_greenplum_dq.py`, `ddl_greenplum_base.py`, `helpers/greenplum.py`, diff --git a/docs/internal/assignment_design.md b/docs/internal/assignment_design.md new file mode 100644 index 0000000..b186606 --- /dev/null +++ b/docs/internal/assignment_design.md @@ -0,0 +1,139 @@ +# Дизайн курсового задания + +> Тактические решения по нарезке задания, порядку выполнения и самопроверке. +> Стратегию и контекст см. в [PRD.md](PRD.md). + +--- + +## 1. Эталонный срез: витрина `sales_report` + +Эталоном выбрана витрина `dm.sales_report` и вся её цепочка вниз по слоям. + +**Почему `sales_report`:** +- Покрывает SCD1 (airports, tariffs), HWM-инкремент, fact load +- Богатая денормализация — хороший образец для подражания +- Средняя сложность — не пугает, но и не тривиальна + +### Эталонные таблицы (даны студенту) + +| Слой | Таблицы | +|------|-----------------------------------------------------------------| +| DM | `sales_report` | +| DDS | `fact_flight_sales`, `dim_airports` (SCD1), `dim_tariffs` (SCD1), `dim_calendar` | +| ODS | `bookings`, `tickets`, `segments`, `flights`, `boarding_passes`, `airports` | +| STG | `bookings`, `tickets`, `segments`, `flights`, `boarding_passes`, `airports` | + +### Задание студенту + +| Слой | Таблицы | Что нового для студента | +|------|-------------------------------------------------------------------|--------------------------------------------------| +| STG | `airplanes`, `seats`, `routes` | Практика по аналогии с эталоном | +| ODS | `airplanes`, `seats`, `routes` | Практика SCD1 UPSERT по аналогии | +| DDS | `dim_airplanes` (SCD1), `dim_passengers` (SCD1), `dim_routes` (SCD2) | **SCD2 — ключевой вызов курсовой** | +| DM | `airport_traffic`, `monthly_overview`, `route_performance`, `passenger_loyalty` | Разная сложность (от простой к сложной) | + +--- + +## 2. Рекомендуемый порядок выполнения + +Студенту рекомендуется (но не обязательно) двигаться в таком порядке: + +1. **STG** (airplanes, seats, routes) — разминка, по аналогии +2. **ODS** (airplanes, seats, routes) — закрепление UPSERT +3. **DDS** dim_airplanes, dim_passengers (SCD1) — новые измерения +4. **DDS** dim_routes (**SCD2**) — ключевой вызов +5. **DM** airport_traffic — простая витрина, похожа на sales_report +6. **DM** route_performance — TRUNCATE+INSERT, SCD2-агрегация по BK +7. **DM** monthly_overview — двухуровневая агрегация +8. **DM** passenger_loyalty — самая сложная, пересчёт истории + +Порядок выстроен от простого к сложному. Каждый шаг опирается на опыт +предыдущего. + +--- + +## 3. SCD2: подход «рецепт без готового SQL» + +Реализация `dim_routes` (SCD2) — ключевой вызов курсовой. Студент делает это +самостоятельно, но ТЗ содержит пошаговую подсказку: + +1. Алгоритм SCD2 текстом (без SQL): + - Вычисли `hashdiff` по набору атрибутов (атрибуты перечислены в ТЗ) + - Найди строки, у которых `hashdiff` изменился + - Закрой старую версию (`valid_to = текущая_дата`) + - Вставь новую версию (`valid_from = текущая_дата`, `valid_to = NULL`) +2. Формула hashdiff: `md5(concat_ws('|', field1, field2, ...))` +3. Ссылка на `naming_conventions.md` (поля `valid_from`, `valid_to`, `hashdiff`) +4. Напоминание: полуоткрытый интервал `[valid_from, valid_to)` +5. Если застрял — ветка `solution` + +Самостоятельная реализация — ключ к запоминанию. SCD2 — обязательный вопрос +на собеседованиях DE, и студент должен уметь объяснить его на основе +собственного опыта. + +--- + +## 4. Валидационный DAG (`bookings_validate`) + +Отдельный DAG для самопроверки студента. Запускается вручную в Airflow UI +после реализации заданий. Таски сгруппированы по слоям — студент видит, +где именно проблема. Дополнительный бонус — практика чтения логов Airflow. + +### Примерная структура тасков + +``` +bookings_validate +├── validate_stg +│ ├── check_stg_airplanes_exists (таблица создана, >0 строк) +│ ├── check_stg_seats_exists +│ └── check_stg_routes_exists +├── validate_ods +│ ├── check_ods_airplanes_rowcount (ODS >= STG по кол-ву уникальных BK) +│ ├── check_ods_seats_rowcount +│ ├── check_ods_routes_rowcount +│ └── check_ods_no_null_pks (PK not null) +├── validate_dds +│ ├── check_dim_airplanes_exists +│ ├── check_dim_passengers_exists +│ ├── check_dim_routes_scd2 (valid_from/valid_to корректны) +│ └── check_dim_routes_no_gaps (нет «дыр» в версиях SCD2) +└── validate_dm + ├── check_airport_traffic_exists + ├── check_monthly_overview_exists + ├── check_route_performance_exists + └── check_passenger_loyalty_exists +``` + +### Реализация + +- `PostgresOperator` + SQL-скрипты в `sql/validate/` +- Каждый SQL-скрипт выполняет SELECT и бросает исключение (через + `DO $$ ... RAISE EXCEPTION ... $$`), если проверка не пройдена +- Сообщения об ошибках — дружелюбные, с подсказкой что делать дальше + +### Ключевые проверки + +- Таблицы существуют и содержат данные +- PK не содержат NULL +- SCD2: `valid_to IS NULL` для текущих версий, нет перекрытий интервалов +- Кросс-слойная консистентность (row count ODS vs STG) +- DM-витрины содержат данные за загруженные дни + +--- + +## 5. Формат ТЗ от аналитика + +Файл: `docs/assignment/analyst_spec.md` (или несколько файлов по слоям). + +Для каждой таблицы-задания документ содержит: + +- **Имя таблицы** и целевая схема (stg / ods / dds / dm) +- **Описание** — что хранит таблица, бизнес-смысл +- **Список полей** с типами и описанием +- **Маппинг источников** — откуда берётся каждое поле +- **Бизнес-правила и фильтры** (если есть) +- **Тип историзации** (SCD1 / SCD2 / snapshot / append) +- **Гранулярность** (одна строка = ?) +- **Distribution key** (подсказка или задание на выбор) + +Формат — приближен к реальным ТЗ, которые студент встретит на работе.