docs: документация переведена на канон av-dev-pm

- беклог и план переехали в docs/tasks (38 задач, 11 целей), слаги
  переименованы с транслита на английские, 85 ссылок поправлены
- conventions.md разобран в docs/conventions/, local-research.md — в
  docs/research/, review-journal.md — в docs/review.md с разделом настройки
  конвейера; заведены security.md, adr/ и .pm.json
- шаг docs.py check добавлен в task gate; поведение в architecture.md помечено
  девятью маркерами долга, database.md получил настройки с числовым значением
This commit is contained in:
av
2026-08-03 17:14:53 +03:00
parent de7b15d48c
commit d79189be18
94 changed files with 1234 additions and 566 deletions
@@ -0,0 +1,37 @@
# Идентичность тренировок при импорте родного экспорта
**Секция:** ядро · **Хук:** В export.xml у тренировки нет id — импорт снапшота задвоит тренировки, приехавшие от HAE · **Теги:** goal:native-export-import
Тренировка в витрине адресуется своим `id` из HealthKit — его шлёт HAE. В
`export.xml` этого идентификатора **нет вовсе**: у элемента `Workout` только
тип, источник, даты и статистика. Значит `healthlog import` не сможет сопоставить
тренировку из снапшота с той же тренировкой, уже приехавшей от HAE, и они
задвоятся.
Это ровно та дыра, что была у точек, и там её закрыли ключом `start + end`
(находка 47): `HKObject.uuid` в выгрузку не попадает, поэтому модель
идентичности обязана выражаться через интервал.
Prior art: `dogsheep/healthkit-to-sqlite` адресует тренировку **хешем
содержимого** (`hash_id` в sqlite-utils) — ровно потому, что идентификатора в
экспорте нет. Нам это не подходит в лоб: у нас половина тренировок уже лежит под
настоящим `id`, и хеш содержимого дал бы третий ключ рядом с двумя.
Варианты, которые надо будет сравнить:
- **Второй уникальный ключ `(start_utc, end_utc)`** у тренировки: импорт ищет по
нему, HAE — по `id`. Цена: индекс и вопрос, что делать при столкновении двух
разных тренировок с одним интервалом (бывает ли такое — неизвестно).
- **Сопоставление на стадии импорта**, без изменения схемы: импорт читает уже
сохранённые тренировки за период и приписывает найденным их `id`. Цена: логика
сопоставления живёт в импорте и не проверяется ничем, кроме него.
- **Считать тренировки из экспорта отдельным родом** и не сопоставлять вовсе.
Цена: потребитель видит две тренировки вместо одной и обязан схлопывать сам —
ровно то, чего проект старается не делать.
Решать до появления формы `healthlog import` значит угадывать: неизвестно,
понадобятся ли тренировки из экспорта вообще (у HAE они полнее — с маршрутом и
рядами, а в экспорте маршрут лежит отдельными GPX).
Связано: `docs/architecture.md` → «Тренировки и прочие секции», задача
`apple-export-import`.