# Загрузка DDS → DM: `bookings_to_gp_dm` DAG собирает витрины из факта и измерений DDS. На `main` полностью работает `dm.sales_report`; остальные четыре загрузки и их DQ содержат `SELECT 1;`. Успешный запуск с заглушками не означает, что все витрины наполнены. На [карте sales_report](design/architecture-map.html#node=dm.sales_report) можно проследить источники полей. Как сегменты билетов превращаются в строки витрины, объясняет [руководство о структуре](design/db_schema.md#агрегация-фактов-в-витрине-продаж). Карту открывайте [локально](design/db_schema.md#как-открыть-карту). ## Перед запуском Дождитесь успешного `bookings_to_gp_dds`. Создайте таблицы через `bookings_dm_ddl`, если еще не запускали его или общий `make ddl-gp`. Затем запустите `bookings_to_gp_dm` в Airflow UI. Общий порядок находится в [инструкции запуска DAG](dag_execution_order.md). Для готовой `sales_report` достаточно готового факта, календаря, аэропортов и тарифов. Для своих витрин сначала заполните нужные измерения и выполните [пересчет факта после заданий DDS](assignment/analyst_spec.md#пересчёт-факта-после-реализации-измерений). ## Что делает sales_report Зерно витрины: `(flight_date, departure_airport_sk, arrival_airport_sk, tariff_sk)`. Одна строка содержит продажи сегментов за дату вылета, пару аэропортов и тариф. `tickets_sold = COUNT(*)` считает строки факта в этой группе. [sales_report_load.sql](../sql/dm/sales_report_load.sql) работает в три шага: 1. Находит даты вылета фактов с `_load_ts > MAX(_load_ts)` витрины. Для пустой витрины нижняя граница равна `1900-01-01`. 2. Для этих дат пересчитывает агрегаты по всем их фактам во временную таблицу. В расчет входят и строки, загруженные ранее: например, новая посадка меняет итог дня, сохраняя остальные продажи этого дня. 3. Обновляет существующие группы при изменении проверяемых полей и вставляет новые. `_load_id` записанных строк равен текущему `run_id` DM. Если после пропуска запуска в DDS накопились факты с более новой `_load_ts`, следующий запуск включит их даты в пересчет. Изменение только описания измерения само по себе не выбирает дату: отбор привязан к метке факта. [sales_report_dq.sql](../sql/dm/sales_report_dq.sql) берет даты из строк витрины, записанных текущим запуском. Для них проверяет непустоту при наличии фактов, дубли ключа, обязательные поля, неотрицательную выручку и долю посадок от 0 до 1. Если витрина в этом запуске не менялась, DQ выводит `NOTICE` и завершает работу. ## Учебные витрины Ниже указано ожидаемое поведение заданий. Поля и формулы находятся в ТЗ; загрузку и DQ для каждого объекта нужно написать самостоятельно. | Задание | Одна строка | Что нужно пересчитывать | |---|---|---| | [airport_traffic](assignment/analyst_spec.md#31-dmairport_traffic) | Дата и аэропорт | Полные итоги затронутых дат, с учетом ролей вылета и прилета | | [route_performance](assignment/analyst_spec.md#32-dmroute_performance) | Маршрут по `route_bk` | Всю историю при каждом запуске, объединяя версии маршрута | | [monthly_overview](assignment/analyst_spec.md#33-dmmonthly_overview) | Год, месяц и модель по `airplane_sk` | Затронутые месяцы; сначала метрики рейса, затем месяца | | [passenger_loyalty](assignment/analyst_spec.md#34-dmpassenger_loyalty) | Пассажир по `passenger_sk` | Всю историю пассажиров, чьи факты изменились | Готовый пример отбора затронутых дат находится в `sales_report_load.sql`. В заданиях меняется единица пересчета: дата, месяц, пассажир или вся таблица. Следуйте [порядку заданий](assignment/README.md#следующие-этапы), чтобы каждый следующий расчет опирался на уже освоенный прием. ## Порядок задач В [DAG](../airflow/dags/bookings_to_gp_dm.py) `start_dm` запускает пять независимых веток. Каждая имеет вид `load_dm_<витрина> → dq_dm_<витрина> → finish_dm_summary`. Финальная задача ждет все пять DQ. Витрины читают DDS и не читают друг друга, поэтому между их задачами загрузки нет зависимостей. ## Проверка результата В `make gp-psql` посмотрите готовую витрину: ```sql SELECT COUNT(*) FROM dm.sales_report; SELECT flight_date, departure_airport_bk, arrival_airport_bk, fare_conditions, tickets_sold, passengers_boarded, total_revenue FROM dm.sales_report ORDER BY flight_date DESC, departure_airport_bk, arrival_airport_bk, fare_conditions LIMIT 10; SELECT COUNT(*) AS invalid_boarding_rows FROM dm.sales_report WHERE tickets_sold < passengers_boarded; ``` После первого успешного запуска `sales_report` непуста, `invalid_boarding_rows = 0`. Возьмите одну строку и найдите на карте, откуда пришли ее дата, два аэропорта и тариф. После реализации своих витрин запустите `bookings_validate` и посмотрите `validate_dm`. Проверки этой группы требуют непустые таблицы; правильность метрик должна проверять ваша DQ по ТЗ. Соблюдайте [правила запуска валидации](assignment/README.md#запуск-и-проверка): в том же DAG выполняется активный SCD2-тест с восстановлением данных. ## Если загрузка не прошла - Нет `dm.*`: выполните `bookings_dm_ddl`. - `sales_report` пуста: проверьте наличие факта и соответствий его SK календарю, аэропортам и тарифам. Витрина соединяет их через `INNER JOIN`. - Учебная витрина пуста: проверьте замену загрузочной заглушки и наличие необходимых SK факта после заданий DDS. - Ошибка метрики: разберите одну группу и сравните результат со строками факта и формулой в ТЗ. Дополнительные примеры проверок есть в [справочнике DQ](reference/dq_taxonomy.md). - `finish_dm_summary` не выполняется: найдите первую упавшую задачу, откройте ее Log и исправьте причину до следующего запуска слоя.