feat(dm): добавлена эталонная витрина dm.sales_report

- Зачем:
  - требуется эталонная витрина для обучения паттернам DM-слоя
  - демонстрация UPSERT-логики с IS DISTINCT FROM для идемпотентности
- Что:
  - DDL: heap-таблица dm.sales_report с 18 полями, DISTRIBUTED BY (flight_date)
  - Load: UPSERT (UPDATE + INSERT) с JOIN dim_calendar, dim_airports (x2), dim_tariffs, fact_flight_sales
  - DQ: PL/pgSQL DO $$ с проверками непустоты, уникальности, tickets_sold >= passengers_boarded, boarding_rate BETWEEN 0 AND 1
  - DAGs: bookings_dm_ddl (DDL), bookings_to_gp_dm (ETL + DQ с параллельными ветками)
  - Tests: smoke-тесты для обоих DAG
  - sql/ddl_gp.sql: добавлен \i dm/sales_report_ddl.sql
- Проверка:
  - make fmt && make lint — passed
  - make test — 15 passed, 11 skipped
  - make ddl-gp — DDL applied successfully
  - airflow dags test bookings_to_gp_dm 2026-02-28T13:00:00 — 4 tasks SUCCESS
  - 9243 rows loaded, _load_id подставлен корректно (Jinja2 templating works)
  - DQ checks passed
This commit is contained in:
2026-02-28 22:13:11 +03:00
parent e577f31a67
commit 7d18b3fe5c
7 changed files with 543 additions and 0 deletions
+33
View File
@@ -382,3 +382,36 @@ def test_bookings_to_gp_dds_dag_structure():
# Финальная сводка должна ждать DQ факта.
_assert_reachable(dag, "dq_dds_fact_flight_sales", "finish_dds_summary")
def test_bookings_dm_ddl_dag_structure():
"""Проверка структуры DAG bookings_dm_ddl."""
dag = _load_dag("airflow.dags.bookings_dm_ddl")
expected_tasks = {
"apply_dm_sales_report_ddl",
}
assert expected_tasks.issubset(dag.task_dict.keys())
# На данном этапе только одна витрина
# В будущем: линейная цепочка из 5 задач
def test_bookings_to_gp_dm_dag_structure():
"""Проверка структуры DAG bookings_to_gp_dm."""
dag = _load_dag("airflow.dags.bookings_to_gp_dm")
expected_tasks = {
"start_dm",
"load_dm_sales_report",
"dq_dm_sales_report",
"finish_dm_summary",
}
assert expected_tasks.issubset(dag.task_dict.keys())
# Проверяем зависимости load -> dq
_assert_direct_edge(dag, "load_dm_sales_report", "dq_dm_sales_report")
# Проверяем, что start -> load -> dq -> finish
_assert_reachable(dag, "start_dm", "load_dm_sales_report")
_assert_reachable(dag, "dq_dm_sales_report", "finish_dm_summary")