docs(map): уточнены заголовки и добавлены образцы легенды
This commit is contained in:
1 parent
b9bcfe7230
commit
6be0442838
4 files changed
+116
-41
No files matched your search
@@ -53,11 +53,9 @@
|
||||
outline: 3px solid #d79078;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.legend { display: flex; flex-wrap: wrap; gap: 15px; color: var(--muted); }
|
||||
.legend { display: flex; flex-wrap: wrap; gap: 8px 20px; color: var(--muted); }
|
||||
.legend-item { display: inline-flex; align-items: center; gap: 7px; }
|
||||
.legend-line { width: 34px; border-top: 2px solid var(--line); }
|
||||
.legend-line.dashed { border-top-style: dashed; }
|
||||
.legend-line.dotted { border-top-style: dotted; }
|
||||
.legend-icon { display: block; width: 50px; height: 20px; flex: none; }
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 370px;
|
||||
@@ -71,7 +69,7 @@
|
||||
border-radius: 10px;
|
||||
background: var(--panel);
|
||||
}
|
||||
svg { display: block; width: 100%; min-width: 1040px; height: auto; }
|
||||
#map { display: block; width: 100%; min-width: 1040px; height: auto; }
|
||||
.layer-title { font-size: 15px; font-weight: 700; text-anchor: middle; }
|
||||
.layer-caption { font-size: 11px; fill: var(--muted); text-anchor: middle; }
|
||||
.zone-caption { font-size: 12px; fill: var(--muted); }
|
||||
@@ -87,7 +85,7 @@
|
||||
.edge-line.dotted { stroke-dasharray: 2 6; stroke-linecap: round; }
|
||||
.edge-hit { fill: none; stroke: transparent; stroke-width: 14; cursor: pointer; }
|
||||
.node { cursor: pointer; transition: opacity .15s; }
|
||||
.node rect {
|
||||
.node rect, .legend-node rect {
|
||||
fill: #f1f2f2;
|
||||
stroke: #858b90;
|
||||
stroke-width: 1.2;
|
||||
@@ -130,12 +128,12 @@
|
||||
a { color: #8a351e; text-underline-offset: 2px; }
|
||||
.hint { color: var(--muted); font-size: 13px; }
|
||||
.edge[hidden] { display: none; }
|
||||
.node.student rect { stroke-dasharray: 5 3; }
|
||||
.node.student rect, .legend-node.student rect { stroke-dasharray: 5 3; }
|
||||
.node.student .stream-mark { fill: #875a14; }
|
||||
.edge.expected .edge-line { stroke-dasharray: 5 4; }
|
||||
.node.is-upstream rect { fill: #e3eff5; stroke: #307c9a; }
|
||||
.node.is-downstream rect { fill: #e7f1e5; stroke: #527a43; }
|
||||
.node.is-selected rect { fill: var(--accent-soft); stroke: var(--accent); }
|
||||
.edge.expected .edge-line, .legend-arrow.expected .edge-line { stroke-dasharray: 5 4; }
|
||||
.node.is-upstream rect, .legend-node.is-upstream rect { fill: #e3eff5; stroke: #307c9a; }
|
||||
.node.is-downstream rect, .legend-node.is-downstream rect { fill: #e7f1e5; stroke: #527a43; }
|
||||
.node.is-selected rect, .legend-node.is-selected rect { fill: var(--accent-soft); stroke: var(--accent); }
|
||||
.relation-button { border-radius: 5px; padding: 5px 7px; text-align: left; width: 100%; margin-top: 5px; font-size: 13px; }
|
||||
.badge { font-size: 13px; padding: 3px 8px; border-radius: 4px; background: #eef1ed; display: inline-block; }
|
||||
.badge.student { background: #fff1cf; color: #775018; }
|
||||
@@ -155,9 +153,41 @@
|
||||
<div class="controls">
|
||||
<div class="switcher" id="switcher" aria-label="Вид связей"></div>
|
||||
<p id="view-hint" class="hint"></p>
|
||||
<div class="legend" aria-label="Обозначения">
|
||||
<span>Сплошная рамка: готово</span><span>Пунктирная рамка: задание</span>
|
||||
<span id="upstream-legend">Синий: источники</span><span id="downstream-legend">Зеленый: потребители</span><span>Оранжевый: выбор</span>
|
||||
<div class="legend" aria-label="Обозначения стрелок">
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-arrow" viewBox="0 0 50 20" aria-hidden="true"><path class="edge-line" d="M 2 10 H 43"/></svg>
|
||||
<span id="ready-edge-legend">Готовая загрузка</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-arrow expected" viewBox="0 0 50 20" aria-hidden="true"><path class="edge-line" d="M 2 10 H 43"/></svg>
|
||||
<span id="expected-edge-legend">Загрузка по ТЗ</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-arrow is-path" viewBox="0 0 50 20" aria-hidden="true"><path class="edge-line" d="M 2 10 H 43"/></svg>
|
||||
<span>Выделенные связи</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="legend" aria-label="Обозначения таблиц">
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span>Готовая таблица</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node student" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span>Учебная заглушка</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node is-upstream" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span id="upstream-legend">Источники</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node is-downstream" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span id="downstream-legend">Потребители</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node is-selected" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span>Выбранная таблица</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
@@ -5142,9 +5172,11 @@
|
||||
}
|
||||
function renderState(message = "") {
|
||||
document.querySelectorAll("[data-view]").forEach(b => b.setAttribute("aria-pressed",String(b.dataset.view === state.view)));
|
||||
document.getElementById("upstream-legend").textContent = state.view === "flow" ? "Синий: источники" : "Синий: связанные родители";
|
||||
document.getElementById("downstream-legend").textContent = state.view === "flow" ? "Зеленый: потребители" : "Зеленый: связанные потомки";
|
||||
document.getElementById("view-hint").textContent = state.view === "flow" ? "Стрелка: откуда загрузчик читает данные и куда записывает результат. Пунктир — ожидается по ТЗ. Календарь генерируется отдельно." : "Стрелка: от родительской строки к связанным строкам. Например, от бронирования к билетам. Это не порядок загрузки и не физические FK.";
|
||||
document.getElementById("upstream-legend").textContent = state.view === "flow" ? "Источники" : "Связанные родители";
|
||||
document.getElementById("downstream-legend").textContent = state.view === "flow" ? "Потребители" : "Связанные потомки";
|
||||
document.getElementById("ready-edge-legend").textContent = state.view === "flow" ? "Готовая загрузка" : "Связь по ключам";
|
||||
document.getElementById("expected-edge-legend").textContent = state.view === "flow" ? "Загрузка по ТЗ" : "Связь с учебной заглушкой";
|
||||
document.getElementById("view-hint").textContent = state.view === "flow" ? "Стрелка: откуда загрузчик читает данные и куда записывает результат. Календарь генерируется отдельно." : "Стрелка: от родительской строки к связанным строкам. Например, от бронирования к билетам. Это не порядок загрузки и не физические FK.";
|
||||
if (state.selected?.type === "node") renderNodeCard(nodeById.get(state.selected.id));
|
||||
else if (state.selected?.type === "edge") renderEdgeCard(edgeById.get(state.selected.id));
|
||||
else renderIntro(message);
|
||||
|
||||
@@ -52,11 +52,9 @@
|
||||
outline: 3px solid #d79078;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.legend { display: flex; flex-wrap: wrap; gap: 15px; color: var(--muted); }
|
||||
.legend { display: flex; flex-wrap: wrap; gap: 8px 20px; color: var(--muted); }
|
||||
.legend-item { display: inline-flex; align-items: center; gap: 7px; }
|
||||
.legend-line { width: 34px; border-top: 2px solid var(--line); }
|
||||
.legend-line.dashed { border-top-style: dashed; }
|
||||
.legend-line.dotted { border-top-style: dotted; }
|
||||
.legend-icon { display: block; width: 50px; height: 20px; flex: none; }
|
||||
main {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 370px;
|
||||
@@ -70,7 +68,7 @@
|
||||
border-radius: 10px;
|
||||
background: var(--panel);
|
||||
}
|
||||
svg { display: block; width: 100%; min-width: 1040px; height: auto; }
|
||||
#map { display: block; width: 100%; min-width: 1040px; height: auto; }
|
||||
.layer-title { font-size: 15px; font-weight: 700; text-anchor: middle; }
|
||||
.layer-caption { font-size: 11px; fill: var(--muted); text-anchor: middle; }
|
||||
.zone-caption { font-size: 12px; fill: var(--muted); }
|
||||
@@ -86,7 +84,7 @@
|
||||
.edge-line.dotted { stroke-dasharray: 2 6; stroke-linecap: round; }
|
||||
.edge-hit { fill: none; stroke: transparent; stroke-width: 14; cursor: pointer; }
|
||||
.node { cursor: pointer; transition: opacity .15s; }
|
||||
.node rect {
|
||||
.node rect, .legend-node rect {
|
||||
fill: #f1f2f2;
|
||||
stroke: #858b90;
|
||||
stroke-width: 1.2;
|
||||
@@ -129,12 +127,12 @@
|
||||
a { color: #8a351e; text-underline-offset: 2px; }
|
||||
.hint { color: var(--muted); font-size: 13px; }
|
||||
.edge[hidden] { display: none; }
|
||||
.node.student rect { stroke-dasharray: 5 3; }
|
||||
.node.student rect, .legend-node.student rect { stroke-dasharray: 5 3; }
|
||||
.node.student .stream-mark { fill: #875a14; }
|
||||
.edge.expected .edge-line { stroke-dasharray: 5 4; }
|
||||
.node.is-upstream rect { fill: #e3eff5; stroke: #307c9a; }
|
||||
.node.is-downstream rect { fill: #e7f1e5; stroke: #527a43; }
|
||||
.node.is-selected rect { fill: var(--accent-soft); stroke: var(--accent); }
|
||||
.edge.expected .edge-line, .legend-arrow.expected .edge-line { stroke-dasharray: 5 4; }
|
||||
.node.is-upstream rect, .legend-node.is-upstream rect { fill: #e3eff5; stroke: #307c9a; }
|
||||
.node.is-downstream rect, .legend-node.is-downstream rect { fill: #e7f1e5; stroke: #527a43; }
|
||||
.node.is-selected rect, .legend-node.is-selected rect { fill: var(--accent-soft); stroke: var(--accent); }
|
||||
.relation-button { border-radius: 5px; padding: 5px 7px; text-align: left; width: 100%; margin-top: 5px; font-size: 13px; }
|
||||
.badge { font-size: 13px; padding: 3px 8px; border-radius: 4px; background: #eef1ed; display: inline-block; }
|
||||
.badge.student { background: #fff1cf; color: #775018; }
|
||||
@@ -154,9 +152,41 @@
|
||||
<div class="controls">
|
||||
<div class="switcher" id="switcher" aria-label="Вид связей"></div>
|
||||
<p id="view-hint" class="hint"></p>
|
||||
<div class="legend" aria-label="Обозначения">
|
||||
<span>Сплошная рамка: готово</span><span>Пунктирная рамка: задание</span>
|
||||
<span id="upstream-legend">Синий: источники</span><span id="downstream-legend">Зеленый: потребители</span><span>Оранжевый: выбор</span>
|
||||
<div class="legend" aria-label="Обозначения стрелок">
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-arrow" viewBox="0 0 50 20" aria-hidden="true"><path class="edge-line" d="M 2 10 H 43"/></svg>
|
||||
<span id="ready-edge-legend">Готовая загрузка</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-arrow expected" viewBox="0 0 50 20" aria-hidden="true"><path class="edge-line" d="M 2 10 H 43"/></svg>
|
||||
<span id="expected-edge-legend">Загрузка по ТЗ</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-arrow is-path" viewBox="0 0 50 20" aria-hidden="true"><path class="edge-line" d="M 2 10 H 43"/></svg>
|
||||
<span>Выделенные связи</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="legend" aria-label="Обозначения таблиц">
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span>Готовая таблица</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node student" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span>Учебная заглушка</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node is-upstream" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span id="upstream-legend">Источники</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node is-downstream" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span id="downstream-legend">Потребители</span>
|
||||
</span>
|
||||
<span class="legend-item">
|
||||
<svg class="legend-icon legend-node is-selected" viewBox="0 0 50 20" aria-hidden="true"><rect x="4" y="2" width="40" height="16"/></svg>
|
||||
<span>Выбранная таблица</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<main>
|
||||
@@ -393,9 +423,11 @@
|
||||
}
|
||||
function renderState(message = "") {
|
||||
document.querySelectorAll("[data-view]").forEach(b => b.setAttribute("aria-pressed",String(b.dataset.view === state.view)));
|
||||
document.getElementById("upstream-legend").textContent = state.view === "flow" ? "Синий: источники" : "Синий: связанные родители";
|
||||
document.getElementById("downstream-legend").textContent = state.view === "flow" ? "Зеленый: потребители" : "Зеленый: связанные потомки";
|
||||
document.getElementById("view-hint").textContent = state.view === "flow" ? "Стрелка: откуда загрузчик читает данные и куда записывает результат. Пунктир — ожидается по ТЗ. Календарь генерируется отдельно." : "Стрелка: от родительской строки к связанным строкам. Например, от бронирования к билетам. Это не порядок загрузки и не физические FK.";
|
||||
document.getElementById("upstream-legend").textContent = state.view === "flow" ? "Источники" : "Связанные родители";
|
||||
document.getElementById("downstream-legend").textContent = state.view === "flow" ? "Потребители" : "Связанные потомки";
|
||||
document.getElementById("ready-edge-legend").textContent = state.view === "flow" ? "Готовая загрузка" : "Связь по ключам";
|
||||
document.getElementById("expected-edge-legend").textContent = state.view === "flow" ? "Загрузка по ТЗ" : "Связь с учебной заглушкой";
|
||||
document.getElementById("view-hint").textContent = state.view === "flow" ? "Стрелка: откуда загрузчик читает данные и куда записывает результат. Календарь генерируется отдельно." : "Стрелка: от родительской строки к связанным строкам. Например, от бронирования к билетам. Это не порядок загрузки и не физические FK.";
|
||||
if (state.selected?.type === "node") renderNodeCard(nodeById.get(state.selected.id));
|
||||
else if (state.selected?.type === "edge") renderEdgeCard(edgeById.get(state.selected.id));
|
||||
else renderIntro(message);
|
||||
|
||||
@@ -60,7 +60,9 @@ Docker для карты не нужны. Храните HTML рядом с ос
|
||||
Для связи сегмента с талоном понадобятся оба поля ключа, иначе талон
|
||||
первого рейса можно ошибочно присоединить ко второму.
|
||||
|
||||
## Как устроен поток целиком?
|
||||
<a id="как-устроен-поток-целиком"></a>
|
||||
|
||||
## Путь данных от источника до витрин
|
||||
|
||||
Оставьте билет выбранным и переключитесь на **"Поток данных"**.
|
||||
Таблицы останутся на местах, выбранный билет сохранится; теперь стрелки
|
||||
@@ -86,7 +88,9 @@ Docker для карты не нужны. Храните HTML рядом с ос
|
||||
проверки календаря. Порядок выполнения смотрите по ссылке **"DAG · порядок задач"**
|
||||
в карточке. Поля, SQL и проверки раскрываются там же, после выбора таблицы.
|
||||
|
||||
## Как собирается факт?
|
||||
<a id="как-собирается-факт"></a>
|
||||
|
||||
## Сборка факта продаж
|
||||
|
||||
Откройте [dds.fact_flight_sales](architecture-map.html#node=dds.fact_flight_sales).
|
||||
В нашем примере в нем будет **три строки**, по одной на каждый сегмент.
|
||||
@@ -111,7 +115,9 @@ SK здесь обозначены словами: конкретные числ
|
||||
Нет талона - сегмент сохраняется, `seat_no` остается `NULL`, `is_boarded = false`.
|
||||
В этом стенде признак посадки означает именно наличие талона.
|
||||
|
||||
### Откуда берутся два аэропорта
|
||||
<a id="откуда-берутся-два-аэропорта"></a>
|
||||
|
||||
### Аэропорты вылета и прилета
|
||||
|
||||
В карточке факта откройте связи **"Коды двух аэропортов"**,
|
||||
**"Аэропорт вылета"** и **"Аэропорт прилета"**:
|
||||
@@ -128,7 +134,9 @@ SK здесь обозначены словами: конкретные числ
|
||||
SQL использует `LEFT JOIN`, поэтому строка с таким пропуском сохранится;
|
||||
допустимость пропуска отдельно оценивает [DQ факта](../../sql/dds/fact_flight_sales_dq.sql).
|
||||
|
||||
### Какая версия маршрута нужна рейсу
|
||||
<a id="какая-версия-маршрута-нужна-рейсу"></a>
|
||||
|
||||
### Версия маршрута на дату рейса
|
||||
|
||||
Связь **"Версия на дату рейса"** ведет к `dds.dim_routes` по `route_bk = route_no`
|
||||
и дате `scheduled_departure::DATE`. Дата должна попасть в полуоткрытый
|
||||
@@ -139,7 +147,9 @@ SQL использует `LEFT JOIN`, поэтому строка с таким
|
||||
поиска `airplane_sk` в `dim_airplanes`. Поэтому одного заполненного справочника
|
||||
моделей недостаточно: без подходящей версии маршрута `airplane_sk` не появится.
|
||||
|
||||
## Что кормит витрины?
|
||||
<a id="что-кормит-витрины"></a>
|
||||
|
||||
## Агрегация фактов в витрине продаж
|
||||
|
||||
Перейдите к [dm.sales_report](architecture-map.html#node=dm.sales_report).
|
||||
Зерно меняется: день вылета × два аэропорта × класс обслуживания.
|
||||
|
||||
@@ -43,8 +43,9 @@
|
||||
11. `LEFT JOIN dds.dim_passengers` даёт `passenger_sk`.
|
||||
12. `LEFT JOIN ods.boarding_passes` даёт `seat_no` и признак `is_boarded`.
|
||||
|
||||
Так список связей превращается в [схему «Как собирается факт?»](db_schema.md#как-собирается-факт):
|
||||
четыре обязательных входа рисуются сплошными стрелками, а остальные пунктирными.
|
||||
Этот путь разобран в разделе [«Сборка факта продаж»](db_schema.md#сборка-факта-продаж).
|
||||
На карте сплошные стрелки показывают готовые загрузки, пунктирные — ожидаемые
|
||||
по ТЗ. Тип соединения и поведение при отсутствии строки описаны в карточке связи.
|
||||
|
||||
## Как проверить свой слой
|
||||
|
||||
|
||||
Reference in new issue
Block a user