feat(dds): перенесён lookup аэропортов на ods.routes, обновлена документация задания

- Зачем:
  - fact_flight_sales брал airport_sk через dds.dim_routes (студенческое задание SCD2);
    на ветке main без реализованного dim_routes все airport_sk были NULL,
    и эталонные витрины sales_report/airport_traffic становились бесполезны.
- Что:
  - fact_flight_sales_load.sql: добавлен subquery ods_rte (ROW_NUMBER по ods.routes),
    dep/arr airport_sk теперь через ods.routes (эталон); airplane_sk — через dim_routes (point-in-time).
  - analyst_spec.md: удалены STG-задания (1.1–1.3) и ods.routes (2.3), добавлены секции
    «Почему STG уже реализован» и «Пересчёт факта», исправлен ODS intro, перенумерованы части.
  - assignment_design.md: обновлены таблицы эталон/задание, порядок выполнения; убраны
    validate_stg и check_ods_routes_rowcount из структуры валидационного DAG.
  - db_schema.md: статус уточнён («реализовано в ветке solution»), добавлена оговорка
    о заглушках на main, DQ-контракт дополнен примечанием.
- Проверка:
  - make test → 4 passed, 9 skipped.
  - ручная проверка: TRUNCATE dds.fact_flight_sales → перезапуск DDS DAG →
    COUNT(departure_airport_sk) должен равняться COUNT(*).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 10:39:19 +03:00
co-authored by Claude Sonnet 4.6
parent f018f1e7da
commit 4e9d8f5a1c
4 changed files with 109 additions and 162 deletions
+18 -3
View File
@@ -48,6 +48,21 @@ WITH fact_src AS (
-- Учебный комментарий: Late-arriving dimensions (Опаздывающие измерения)
-- Мы используем LEFT JOIN, так как факт (рейс/билет) может прийти раньше,
-- чем справочник (пассажир/маршрут) обновится в DDS.
--
-- Airport lookup через ods.routes (эталонный справочник), а не через dds.dim_routes
-- (студенческое задание SCD2). Это архитектурное решение: эталонный пайплайн работает
-- независимо от студенческого кода. Аэропорты вылета/прилёта одинаковы во всех
-- версиях одного route_no — безопасно брать из ODS без point-in-time логики.
-- ROW_NUMBER по validity DESC: выбираем актуальную версию расписания маршрута.
LEFT JOIN (
SELECT route_no, departure_airport, arrival_airport
FROM (
SELECT route_no, departure_airport, arrival_airport,
ROW_NUMBER() OVER (PARTITION BY route_no ORDER BY validity DESC) AS rn
FROM ods.routes
) ranked
WHERE rn = 1
) AS ods_rte ON ods_rte.route_no = flt.route_no
LEFT JOIN dds.dim_routes AS rte
ON rte.route_bk = flt.route_no
AND flt.scheduled_departure::DATE >= rte.valid_from
@@ -55,11 +70,11 @@ WITH fact_src AS (
LEFT JOIN dds.dim_calendar AS cal
ON cal.date_actual = flt.scheduled_departure::DATE
LEFT JOIN dds.dim_airports AS dep
ON dep.airport_bk = rte.departure_airport
ON dep.airport_bk = ods_rte.departure_airport -- через ods.routes (эталон)
LEFT JOIN dds.dim_airports AS arr
ON arr.airport_bk = rte.arrival_airport
ON arr.airport_bk = ods_rte.arrival_airport -- через ods.routes (эталон)
LEFT JOIN dds.dim_airplanes AS ap
ON ap.airplane_bk = rte.airplane_code
ON ap.airplane_bk = rte.airplane_code -- через dim_routes (point-in-time, как прежде)
LEFT JOIN dds.dim_tariffs AS tar
ON tar.fare_conditions = seg.fare_conditions
LEFT JOIN dds.dim_passengers AS pax