# Спецификация 03: ProgramPackageImporter — пакет программы Источники: `1-docs\3-tech\program-import-format.md`, Ф-8.3, О-4-Б, 🛡3 module_reqs (неизменяемость версий). ## 1. Сигнатура ```csharp public Guid Import(int athleteId, string packageJson); // → version id ``` ## 2. Валидация пакета (ошибка → ImportPackageException) - корень — объект с `weeks` (массив, ≥ 1) и `program_id` (непустая строка); `author` ∈ user|admin (иное → ошибка; `rules` через пакет не задаётся, `llm` — v2); - схема не валидируется глубже (body хранится как есть, v1). ## 3. Маппинг 1. `src.program` по `external_id == program_id`: нет → `ProgramStore.EnsureProgram` + запись `ExternalId` (ProgramRepository.Insert с параметром, З-35); есть → используется (title не меняется). 2. `ProgramStore.CreateDraft(athleteId, body, author, rationale, basedOnVersion = null)`: - `body` — объект пакета целиком (`program_id`, `weeks`, `rationale`, `profile` — переносится как есть; в v1 профиль сервером не интерпретируется, format разд. 1); - `version` считает БД (max+1) — **повтор пакета с тем же external_id → новая версия** (формат разд. 3.2, 🛡3); - `based_on_version` = актуальная максимальная (гарантирует ProgramStore); - `rationale` — из пакета (поле `rationale`, иначе `""`). 3. Упражнения: `weeks[].sessions[].exercises[].exercise_id` — имя (`UNIQUE(athlete_id, name)`); отсутствующее → создание (`ExerciseRepository.Insert`): группа — по словарю фокусов (`focus` содержит push/pull/legs/cardio, иначе `core`) + запись в лог; существующее — не изменяется. ## 4. Результат version id (для лога). Статус созданной версии — `draft` (🛡 авто-активации нет; активация — ProgramStore.Activate вручную).