108 lines
4.4 KiB
Plaintext
108 lines
4.4 KiB
Plaintext
@startuml hld
|
||
' ============================================================================
|
||
' Феникс 6X — high level design (диаграмма развертывания).
|
||
' Нотация: A -> B — зависит/использует; A <>-> B — содержит/управляет.
|
||
' Цвета (d-drg-4): жёлтый #FFF4D6 — внешнее; серый #BFBFBF — не в v1.
|
||
' ============================================================================
|
||
|
||
title Феникс 6X: часы + телефон атлета + личный сервер\n(v1.0; серым — v2, жёлтым — внешние системы)
|
||
|
||
skinparam shadowing false
|
||
skinparam defaultTextAlignment center
|
||
skinparam backgroundColor #FFFFFF
|
||
skinparam defaultFontName Segoe UI
|
||
skinparam defaultFontSize 12
|
||
skinparam ArrowColor #444444
|
||
skinparam component {
|
||
BackgroundColor #F5F7FA
|
||
BorderColor #7A8899
|
||
}
|
||
skinparam database {
|
||
BackgroundColor #F5F7FA
|
||
BorderColor #7A8899
|
||
}
|
||
|
||
together {
|
||
|
||
node "Часы атлета\n(Garmin, Connect IQ)" as Watch {
|
||
component "FenixDataField\nполе данных «Феникс»:\nпульс, зоны, подходы, вибро" as DataField
|
||
}
|
||
|
||
node "Телефон атлета\n(Android, Kotlin)" as Phone {
|
||
component "VoiceInput\nофлайн-STT + грамматика команд" as VoiceInput
|
||
component "TrainingUi\nэкран тренировки, таймеры, история" as TrainingUi
|
||
component "WorkoutCore\nлогика тренировки и программ" as WorkoutCore
|
||
component "LocalDb\nRoom/SQLite" as LocalDb
|
||
component "SyncQueue\nочередь + идемпотентность" as SyncQueue
|
||
component "WatchChannel\ncompanion-канал Connect IQ" as WatchChannel
|
||
}
|
||
|
||
cloud "Личный сервер\n(C# / .NET 10)" as Server {
|
||
|
||
package "WebApp\nASP.NET Core" as WebApp {
|
||
component "RestApi\nsync, workouts, programs\n(X-Api-Key атлета)" as RestApi
|
||
component "AdminWeb\nконсоль владельца\n(логин/пароль)" as AdminWeb
|
||
}
|
||
|
||
package "FitPipeline\nзабор и парсинг" as FitPipeline {
|
||
component "FitFetcher\nклиент Garmin Connect\n(+ папка импорта)" as FitFetcher
|
||
component "FitParser\nGarmin FIT SDK" as FitParser
|
||
}
|
||
|
||
component "MetricsEngine\nadherence, тоннаж, 1ПМ" as MetricsEngine
|
||
component "ProgressionRules\nправила прогрессии → draft" as ProgressionRules
|
||
component "ProgramStore\nверсии программ (JSON)" as ProgramStore
|
||
|
||
database "PostgreSQL\nслои src | ref" as Db
|
||
}
|
||
|
||
component "LlmPlanner\nеженедельный контур LLM" as LlmPlanner #BFBFBF
|
||
|
||
cloud "Garmin Connect\n(облако, .fit)" as GarminConnect #FFF4D6
|
||
}
|
||
|
||
' — зависимости / использование
|
||
DataField --> WatchChannel : пульс, зоны,\nтаймер отдыха
|
||
WatchChannel --> TrainingUi : live-данные\n(если связь есть)
|
||
VoiceInput --> TrainingUi : команды
|
||
TrainingUi --> WorkoutCore
|
||
WorkoutCore --> LocalDb
|
||
LocalDb --> SyncQueue
|
||
SyncQueue --> RestApi : HTTPS\nPOST /sync
|
||
FitFetcher --> GarminConnect : .fit по расписанию
|
||
FitParser --> Db : workout_facts
|
||
MetricsEngine --> Db
|
||
ProgressionRules --> ProgramStore : draft-версия
|
||
ProgramStore --> Db
|
||
RestApi --> Db
|
||
AdminWeb --> Db
|
||
RestApi --> ProgramStore : активная\nверсия
|
||
LlmPlanner ..> MetricsEngine : сводка недели\n(v2)
|
||
LlmPlanner ..> ProgramStore : предложение\n(v2)
|
||
|
||
legend bottom
|
||
|= Связь |= Значение |
|
||
| ""A -> B"" | A зависит от / использует B |
|
||
| ""A ..> B"" | связь версии v2 (в v1 не реализована) |
|
||
| <back:#FFF4D6>жёлтый</back> | внешняя система |
|
||
| <back:#BFBFBF>серый</back> | задумано, не в v1 |
|
||
endlegend
|
||
|
||
note bottom of Watch
|
||
Инвариант: часы автономны — запись .fit ведёт
|
||
платформа Garmin, поле — только отображение.
|
||
Один код поля — сборки под модели Garmin.
|
||
end note
|
||
|
||
note bottom of Phone
|
||
Инвариант офлайн-first: все данные сначала в LocalDb;
|
||
сеть — только SyncQueue. Каждой записи — client_guid.
|
||
end note
|
||
|
||
note bottom of Db
|
||
Инвариант изоляции атлетов: ключ → athlete_id,
|
||
все выборки — по атлету.
|
||
end note
|
||
|
||
@enduml
|