compare panel, docs update, wind visualisation
This commit is contained in:
parent
b7f7ec8dc5
commit
48140f0f77
29 changed files with 2299 additions and 38 deletions
41
docs/diagrams/README.md
Normal file
41
docs/diagrams/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# Диаграммы (PlantUML)
|
||||
|
||||
Исходники диаграмм для диссертации. Построены по фактическому коду
|
||||
(`src/lib/api/*`, `src/lib/features/tracking/*`, `src/lib/features/wind/*`).
|
||||
|
||||
## Архитектура
|
||||
|
||||
| Файл | Что описывает |
|
||||
|------|----------------|
|
||||
| [architecture.puml](architecture.puml) | Слоистая архитектура клиента: routes → features → lib (api/map/ui/state/i18n/auth/domain), внешние сервисы (Backend, предсказатель) и библиотеки |
|
||||
|
||||
## Диаграммы последовательности (по всем запросам)
|
||||
|
||||
| Файл | Что описывает | Запросы |
|
||||
|------|----------------|---------|
|
||||
| [seq-request-csrf.puml](seq-request-csrf.puml) | Сквозная обёртка `request<T>()`: CSRF, cookie, обработка ошибок и 401 | `GET /api/csrf/` |
|
||||
| [seq-auth.puml](seq-auth.puml) | Проверка сессии, вход, выход | `GET /api/session/`, `GET /api/whoami/`, `POST /api/login/`, `POST /api/logout/` |
|
||||
| [seq-resource-crud.puml](seq-resource-crud.puml) | Единый CRUD-шаблон справочников | `/api/saved-points/`, `/api/saved-templates/`, `/api/saved-profiles/` (GET/POST/PUT/DELETE) |
|
||||
| [seq-prediction.puml](seq-prediction.puml) | Запуск прогноза траектории | `POST /api/predictions/` |
|
||||
| [seq-telemetry.puml](seq-telemetry.puml) | Загрузка истории и приём телеметрии | `GET /api/{id}/telemetry/`, `WS /api/ws/satellite/{id}/telemetry/` |
|
||||
| [seq-wind.puml](seq-wind.puml) | Визуализация поля ветра (статика + синхронизация) | `GET /api/v1/wind/field`, `GET /api/v1/wind/meta` |
|
||||
|
||||
## Диаграмма потоков данных
|
||||
|
||||
| Файл | Что описывает |
|
||||
|------|----------------|
|
||||
| [dfd-telemetry.puml](dfd-telemetry.puml) | DFD подсистемы слежения: внешние сущности, процессы 1.0–5.0, хранилища D1–D3 |
|
||||
|
||||
## Рендеринг в PNG/SVG
|
||||
|
||||
В системе есть Java, но нет CLI PlantUML. Один раз скачать jar и собрать все
|
||||
диаграммы:
|
||||
|
||||
```sh
|
||||
curl -L -o plantuml.jar https://github.com/plantuml/plantuml/releases/latest/download/plantuml.jar
|
||||
java -jar plantuml.jar -tpng docs/diagrams/*.puml # PNG
|
||||
java -jar plantuml.jar -tsvg docs/diagrams/*.puml # SVG (для печати)
|
||||
```
|
||||
|
||||
Альтернатива без установки — онлайн-редактор <https://www.plantuml.com/plantuml>
|
||||
или расширение PlantUML для VS Code (предпросмотр `Alt+D`).
|
||||
102
docs/diagrams/architecture.puml
Normal file
102
docs/diagrams/architecture.puml
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
@startuml architecture
|
||||
title Архитектура клиентской части системы
|
||||
|
||||
skinparam shadowing false
|
||||
skinparam defaultFontName Helvetica
|
||||
skinparam packageStyle rectangle
|
||||
skinparam rectangle {
|
||||
BorderColor #2C5AA0
|
||||
BackgroundColor #EAF1FB
|
||||
}
|
||||
skinparam package {
|
||||
BorderColor #6B6B6B
|
||||
BackgroundColor #FFFFFF
|
||||
}
|
||||
skinparam node {
|
||||
BorderColor #444444
|
||||
BackgroundColor #F5F5F5
|
||||
}
|
||||
|
||||
node "Браузер (SPA) — SvelteKit 5 + Vite" as spa {
|
||||
|
||||
package "routes/ (страницы)" as routes {
|
||||
rectangle "/login" as r_login
|
||||
rectangle "/predict" as r_predict
|
||||
rectangle "/track" as r_track
|
||||
rectangle "/user/*" as r_user
|
||||
}
|
||||
|
||||
package "features/ (функциональные модули)" as features {
|
||||
rectangle "auth\nLoginForm, Navbar" as f_auth
|
||||
rectangle "prediction\nControlPanel, ScenarioPanel,\nCurveEditor" as f_pred
|
||||
rectangle "workspaces\nWorkspacesPanel,\nWorkspaceRenderer, store" as f_ws
|
||||
rectangle "tracking\nTelemetryPanel,\nTelemetryStore" as f_track
|
||||
rectangle "wind\nWindRenderer,\nParticleField, WindCache" as f_wind
|
||||
rectangle "timeline\nTimeLine, store" as f_time
|
||||
rectangle "settings\nSettingsPanel, schema, store" as f_set
|
||||
rectangle "footer" as f_foot
|
||||
}
|
||||
|
||||
package "lib/ (ядро)" as core {
|
||||
rectangle "api/\nclient (HTTP+WS, CSRF),\npredictions, telemetry, wind,\npoints, scenarios, profiles" as l_api
|
||||
rectangle "map/\nобёртка MapLibre GL JS,\nслои, инструменты" as l_map
|
||||
rectangle "ui/\nнезависимые примитивы\n(CollapsibleCard, Toast, …)" as l_ui
|
||||
rectangle "i18n/\nсловари ru / en" as l_i18n
|
||||
rectangle "auth/\nguard, store" as l_auth
|
||||
rectangle "state/\npersisted store\n(localStorage)" as l_state
|
||||
rectangle "domain/\ngeo, math, telemetry,\nprediction, scenario, wind\n(чистые типы и функции)" as l_dom
|
||||
}
|
||||
}
|
||||
|
||||
' ── Внешние сервисы ───────────────────────────────────────────────
|
||||
node "Backend (Django)" as be {
|
||||
rectangle "REST API\n/api/*" as be_rest
|
||||
rectangle "WebSocket\n/api/ws/satellite/{id}/telemetry/" as be_ws
|
||||
}
|
||||
node "Сервис предсказателя\n(127.0.0.1:8080)" as predictor {
|
||||
rectangle "GET /api/v1/wind/*" as pred_wind
|
||||
}
|
||||
|
||||
' ── Внешние библиотеки ───────────────────────────────────────────
|
||||
package "Внешние библиотеки" as libs {
|
||||
rectangle "Svelte 5 / SvelteKit / Vite" as lib_svelte
|
||||
rectangle "MapLibre GL JS" as lib_map
|
||||
rectangle "Bootstrap / Sveltestrap" as lib_bs
|
||||
rectangle "Chart.js, Luxon, js-cookie" as lib_misc
|
||||
}
|
||||
|
||||
' ── Связи: страницы → модули ─────────────────────────────────────
|
||||
r_login --> f_auth
|
||||
r_predict --> f_pred
|
||||
r_predict --> f_ws
|
||||
r_predict --> f_wind
|
||||
r_predict --> f_time
|
||||
r_predict --> f_set
|
||||
r_track --> f_track
|
||||
r_user --> f_pred
|
||||
|
||||
' ── Модули → ядро ────────────────────────────────────────────────
|
||||
features --> l_api
|
||||
features --> l_map
|
||||
features --> l_ui
|
||||
features --> l_state
|
||||
features --> l_i18n
|
||||
f_auth --> l_auth
|
||||
|
||||
' ── Ядро → домен (чистые типы) ───────────────────────────────────
|
||||
l_api --> l_dom
|
||||
l_map --> l_dom
|
||||
l_state --> l_dom
|
||||
|
||||
' ── Ядро → внешние сервисы ───────────────────────────────────────
|
||||
l_api --> be_rest : HTTP /api/*
|
||||
f_track --> be_ws : WebSocket
|
||||
f_wind --> pred_wind : HTTP (без CSRF)
|
||||
|
||||
' ── Использование внешних библиотек ──────────────────────────────
|
||||
spa ..> lib_svelte
|
||||
l_map ..> lib_map
|
||||
l_ui ..> lib_bs
|
||||
core ..> lib_misc
|
||||
|
||||
@enduml
|
||||
62
docs/diagrams/dfd-telemetry.puml
Normal file
62
docs/diagrams/dfd-telemetry.puml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
@startuml dfd-telemetry
|
||||
title Диаграмма потоков данных (DFD) подсистемы слежения (телеметрия)
|
||||
|
||||
skinparam shadowing false
|
||||
skinparam defaultFontName Helvetica
|
||||
skinparam rectangle {
|
||||
BorderColor black
|
||||
BackgroundColor #F5F5F5
|
||||
}
|
||||
skinparam usecase {
|
||||
BorderColor #2C5AA0
|
||||
BackgroundColor #E8F0FE
|
||||
}
|
||||
skinparam database {
|
||||
BorderColor #6B6B6B
|
||||
BackgroundColor #FFFFFF
|
||||
}
|
||||
|
||||
' ── Внешние сущности ───────────────────────────────────────────────
|
||||
rectangle "Стратосферный зонд\n(тестовый клиент)" as sat
|
||||
rectangle "Оператор\n(браузер)" as op
|
||||
|
||||
' ── Процессы ──────────────────────────────────────────────────────
|
||||
usecase "1.0\nПриём телеметрии\n(WebSocket onmessage)" as p1
|
||||
usecase "2.0\nЗагрузка истории\n(REST fetchHistory)" as p2
|
||||
usecase "3.0\nНормализация\nparseTelemetry" as p3
|
||||
usecase "4.0\nОтрисовка на карте\n(MapLibre $effect)" as p4
|
||||
usecase "5.0\nАнализ отклонений\ncomputeDeviations" as p5
|
||||
|
||||
' ── Хранилища данных ──────────────────────────────────────────────
|
||||
database "D1 | БД телеметрии\n(Backend)" as d1
|
||||
database "D2 | points[]\n(TelemetryStore, in-memory)" as d2
|
||||
database "D3 | result\n(прогноз рабочей области)" as d3
|
||||
|
||||
' ── Потоки данных ─────────────────────────────────────────────────
|
||||
sat --> p1 : пакет телеметрии\n(JSON: lat, lon, alt, ts)
|
||||
p1 --> d1 : сохранение пакета
|
||||
p1 --> d2 : TelemetryPoint\n(unix-сек → ISO 8601)
|
||||
|
||||
op --> p2 : UUID спутника
|
||||
d1 --> p2 : RawTelemetryPacket[]\n(новые первыми)
|
||||
p2 --> d2 : история (reverse → хронология)
|
||||
|
||||
d2 --> p3 : points[]
|
||||
p3 --> p4 : Telemetry\n{flight_path[lat,lng,alt], launch}
|
||||
p4 --> op : трек + маркеры + анимированный\nмаркер текущего положения
|
||||
|
||||
d2 --> p5 : фактические точки
|
||||
d3 --> p5 : прогнозная траектория
|
||||
p5 --> op : профиль высоты,\nгоризонтальное отклонение (Хаверсин),\nΔh, макс./текущее отклонение
|
||||
|
||||
' ── Текущие показатели (геттер latest) ────────────────────────────
|
||||
d2 --> op : широта, долгота, высота,\nсчётчик пакетов
|
||||
|
||||
legend left
|
||||
Нотация DFD (Йордан/ДеМарко):
|
||||
▢ прямоугольник — внешняя сущность
|
||||
◯ овал — процесс
|
||||
▭ database — хранилище данных
|
||||
→ — поток данных
|
||||
endlegend
|
||||
@enduml
|
||||
41
docs/diagrams/seq-auth.puml
Normal file
41
docs/diagrams/seq-auth.puml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
@startuml seq-auth
|
||||
title Аутентификация: проверка сессии, вход, выход
|
||||
autonumber
|
||||
|
||||
actor "Пользователь" as user
|
||||
participant "LoginForm /\nguard.ts" as ui
|
||||
participant "authApi" as authapi
|
||||
participant "client.ts\nrequest<T>()" as client
|
||||
participant "Backend\n(Django)" as be
|
||||
|
||||
== Проверка сессии при открытии защищённой страницы ==
|
||||
ui -> authapi : requireAuthenticated()
|
||||
authapi -> client : session()
|
||||
client -> be : GET /api/session/
|
||||
be --> client : { isAuthenticated }
|
||||
client --> authapi : SessionInfo
|
||||
alt не аутентифицирован
|
||||
authapi --> ui : goto('/login')
|
||||
end
|
||||
|
||||
== Вход ==
|
||||
user -> ui : ввод логина и пароля
|
||||
ui -> authapi : login(username, password)
|
||||
authapi -> client : post('/login/', {username, password})
|
||||
client -> be : POST /api/login/
|
||||
be --> client : 200 { detail } | 400/401 ApiError
|
||||
client --> authapi : результат
|
||||
authapi -> client : whoami()
|
||||
client -> be : GET /api/whoami/
|
||||
be --> client : { username }
|
||||
client --> ui : WhoAmI
|
||||
ui --> user : переход на рабочую страницу
|
||||
|
||||
== Выход ==
|
||||
user -> ui : «Выйти»
|
||||
ui -> authapi : logout()
|
||||
authapi -> client : post('/logout/', {})
|
||||
client -> be : POST /api/logout/
|
||||
be --> client : 204
|
||||
client --> ui : сброс состояния, goto('/login')
|
||||
@enduml
|
||||
41
docs/diagrams/seq-prediction.puml
Normal file
41
docs/diagrams/seq-prediction.puml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
@startuml seq-prediction
|
||||
title Запуск прогноза траектории
|
||||
autonumber
|
||||
|
||||
actor "Пользователь" as user
|
||||
participant "ControlPanel /\nWorkspacesPanel" as ui
|
||||
participant "workspacesStore" as store
|
||||
participant "predictionsApi" as predapi
|
||||
participant "client.ts" as client
|
||||
participant "Backend\n(Django)" as be
|
||||
participant "parsePrediction\n(domain)" as parse
|
||||
participant "WorkspaceRenderer\n(карта)" as render
|
||||
|
||||
user -> ui : «Выполнить прогнозирование»
|
||||
ui -> store : run(workspaceId)
|
||||
activate store
|
||||
|
||||
store -> predapi : run(params, launchDateTime)
|
||||
activate predapi
|
||||
predapi -> predapi : buildLaunchDateTime(date, time)\n→ ISO 8601 (UTC, Z)
|
||||
predapi -> predapi : getLatestDataset()\n→ актуальный слот GFS
|
||||
predapi -> client : post('/predictions/', payload)
|
||||
client -> be : POST /api/predictions/\n{FlightParameters, launch_datetime, dataset}
|
||||
be --> client : { result: PredictionStage[] }
|
||||
client --> predapi : PredictionResponse
|
||||
predapi --> store : RawPrediction
|
||||
deactivate predapi
|
||||
|
||||
store -> parse : parsePrediction(stages)
|
||||
parse --> store : Prediction\n{flight_path[lat,lng,alt], timestamps[], launch/burst/landing}
|
||||
store -> store : setResult(workspaceId, prediction)
|
||||
deactivate store
|
||||
|
||||
store -> render : реактивное обновление
|
||||
render -> render : линия трека + маркеры\n(старт, разрыв, приземление)
|
||||
|
||||
alt ошибка сервера/сети
|
||||
store -> store : lastRunError = message
|
||||
store --> ui : toast «Ошибка прогнозирования»
|
||||
end
|
||||
@enduml
|
||||
60
docs/diagrams/seq-telemetry.puml
Normal file
60
docs/diagrams/seq-telemetry.puml
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
@startuml seq-telemetry
|
||||
title Слежение: загрузка истории и приём телеметрии в реальном времени
|
||||
autonumber
|
||||
|
||||
actor "Пользователь" as user
|
||||
participant "TelemetryPanel" as ui
|
||||
participant "TelemetryStore\n(.svelte.ts)" as store
|
||||
participant "telemetryApi" as tapi
|
||||
participant "client.ts" as client
|
||||
participant "Backend REST" as rest
|
||||
participant "Backend\nWebSocket" as ws
|
||||
participant "track/+page\n($effect)" as page
|
||||
participant "MapLibre\n(карта)" as map
|
||||
|
||||
user -> ui : ввод UUID, «Подключиться»
|
||||
ui -> store : connect(id)
|
||||
activate store
|
||||
|
||||
store -> store : проверка UUID регулярным выражением
|
||||
alt UUID невалиден
|
||||
store -> store : status = 'error'
|
||||
store --> ui : (выход без соединения)
|
||||
end
|
||||
|
||||
store -> store : disconnect()\nзакрыть прежний WS, очистить points
|
||||
store -> store : status = 'connecting'
|
||||
|
||||
== Загрузка истории (некритическая) ==
|
||||
store -> tapi : fetchHistory(id)
|
||||
tapi -> client : get('/{id}/telemetry/')
|
||||
client -> rest : GET /api/{id}/telemetry/?from&till
|
||||
rest --> client : RawTelemetryPacket[] | { results }
|
||||
client --> tapi : массив пакетов
|
||||
tapi --> store : RawTelemetryPacket[]
|
||||
store -> store : reverse() → хронологический порядок,\npoints = [...history]
|
||||
note right of store : сбой сети → console.warn,\nподключение не прерывается
|
||||
|
||||
== WebSocket-соединение ==
|
||||
store -> tapi : buildWsUrl(id)
|
||||
tapi --> store : ws(s)://host/api/ws/satellite/{id}/telemetry/
|
||||
store -> ws : new WebSocket(url)
|
||||
ws --> store : onopen → status = 'connected'
|
||||
|
||||
loop каждый новый пакет
|
||||
ws --> store : onmessage(JSON)
|
||||
store -> store : RawPacket → TelemetryPoint\n(unix-сек → ISO 8601),\npoints = [...points, point]
|
||||
store --> page : реактивное изменение points (Svelte $state)
|
||||
page -> map : scene.clear()
|
||||
page -> map : addLine(трек) + addMarker(старт)\n+ plotAnimatedMarker(текущая точка)
|
||||
page -> map : fitBounds() — однократно (fittedBounds)
|
||||
end
|
||||
|
||||
== Отключение ==
|
||||
user -> ui : «Отключиться»
|
||||
ui -> store : disconnect()
|
||||
store -> ws : close()
|
||||
ws --> store : onclose → status = 'idle'
|
||||
store -> store : points = [], satelliteId = null
|
||||
deactivate store
|
||||
@enduml
|
||||
54
docs/diagrams/seq-wind.puml
Normal file
54
docs/diagrams/seq-wind.puml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
@startuml seq-wind
|
||||
title Визуализация поля ветра: статический режим и синхронизация с траекторией
|
||||
autonumber
|
||||
|
||||
participant "predict/+page\n<WindRenderer>" as render
|
||||
participant "WindRenderer\n(.svelte)" as wr
|
||||
participant "WindCache\n(in-memory)" as cache
|
||||
participant "windApi" as wapi
|
||||
participant "Сервис\nпредсказателя\n(127.0.0.1:8080)" as pred
|
||||
participant "createWindInterpolator\n+ ParticleField" as pf
|
||||
|
||||
note over wapi, pred
|
||||
Запросы идут НАПРЯМУЮ через fetch к сервису предсказателя,
|
||||
минуя client.ts: без CSRF и сессионных cookie.
|
||||
end note
|
||||
|
||||
alt Статический режим (ветер по умолчанию)
|
||||
wr -> wr : параметры из активной области\n(высота старта, дата старта)
|
||||
wr -> cache : get(ключ = JSON параметров)
|
||||
alt промах кэша
|
||||
wr -> wapi : field({ altitude, step, time })
|
||||
wapi -> pred : GET /api/v1/wind/field?altitude&step&time
|
||||
pred --> wapi : WindField [C_U, C_V]
|
||||
wapi --> wr : WindField
|
||||
wr -> cache : put(ключ, field)
|
||||
end
|
||||
wr -> pf : установить поле → анимация частиц
|
||||
end
|
||||
|
||||
alt Режим синхронизации с траекторией
|
||||
note over wr : активен прогноз И timeline.max > 0
|
||||
wr -> wr : bbox траектории + проверки\n(F ≤ Fmax, сторона ≤ Dmax)
|
||||
loop δ ∈ {0, ΔT, 2ΔT, …, F}
|
||||
wr -> wr : T = t0 + δ; высота a_{k*} (бин. поиск)
|
||||
wr -> cache : get(ключ кадра)
|
||||
alt промах кэша
|
||||
wr -> wapi : field({ altitude, step, time, min/max lat/lng })
|
||||
wapi -> pred : GET /api/v1/wind/field?…(bbox)
|
||||
pred --> wapi : WindField кадра
|
||||
wapi --> wr : WindField
|
||||
wr -> cache : put(ключ кадра, field)
|
||||
end
|
||||
end
|
||||
loop при воспроизведении (timeline)
|
||||
wr -> wr : fieldAtFlightTime(δ):\nлинейная интерполяция [u,v]\nмежду соседними кадрами
|
||||
wr -> pf : currentField → плавная адвекция частиц
|
||||
end
|
||||
end
|
||||
|
||||
note over wapi, pred
|
||||
windApi.meta() → GET /api/v1/wind/meta
|
||||
(опорное время / параметры сетки, при необходимости)
|
||||
end note
|
||||
@enduml
|
||||
Loading…
Add table
Add a link
Reference in a new issue