# Этап 3. Пресеты болезней для часов сердца

Статус: принята (PR #57) · 11.10.2026 · issue #51
План MVP: этап 3 «Болезни», PR 2 и PR 3. Опирается на спеку часов сердца ([2026-10-11-stage-2-heart-clock.md](2026-10-11-stage-2-heart-clock.md)) и на список источников ([2026-10-11-stage-3-sources.md](2026-10-11-stage-3-sources.md)).

## Цель

Монитор часов сердца (ЭКГ, тоны, диаграмма Виггерса) показывает не только норму, но и шесть болезней MVP. Читатель переключает «норма / болезнь» и видит, что меняется. Статья о болезни подключает монитор своим пресетом через поле `preset` во frontmatter.

## Что входит

- Пресеты в `packages/heart-clock`: `normal`, `atrial-fibrillation`, `stemi`, `heart-failure`, `hypertension`, `aortic-stenosis`, `atrial-septal-defect`. Пресет это набор параметров часов поверх значений по умолчанию.
- Два новых параметра часов: градиент на аортальном клапане и расщепление второго тона.
- Переключатель пресетов в острове `HeartClockDemo`, описание ЭКГ для каждого пресета.
- Поле `preset` в схеме статьи и монитор на странице статьи.

## Что не входит

- Встраивание `/<locale>/embed/ecg/?preset=…` делает тред этапа 0. Остров уже принимает пресет и параметры через пропсы и не зависит от шапки и подвала.
- Синхронизация 3D-модели с часами (этап 2, PR 3 плана MVP): после того как тред «3D-модель сердца» закончит вьюер.
- Шумы сердца (систолический шум при аортальном стенозе и ДМПП), третий тон при сердечной недостаточности, правые камеры сердца и несколько отведений ЭКГ. Модель показывает одно отведение, похожее на II, и только левое сердце.
- Значения `preset` в самих статьях: их проставляет тред «Схема контента» после PR со схемой.

## Правило для чисел

Каждое число пресета либо взято из источника из [спеки источников](2026-10-11-stage-3-sources.md) или из спеки часов сердца, либо помечено как допущение модели (комментарий `assumption` в коде и строка в таблице ниже). Если источник даёт только порог («≥40 мм рт. ст.»), пресет берёт значение на пороге или явно за ним и так и пишет. Если источник не даёт числа, а нарисовать признак без числа нельзя (амплитуда волн f, величина расщепления S2), число помечено как допущение. Числа, которые не нужны для картинки, в модель не попадают. Дословные цитаты и способ проверки каждого числа лежат в [2026-10-11-stage-3-presets-sources.md](2026-10-11-stage-3-presets-sources.md).

## Пресеты

Все остальные параметры берутся из значений по умолчанию (норма). ЧСС пресеты не меняют: источники не дают типичной ЧСС ни для одной из шести болезней, а ЧСС читатель меняет слайдером.

### Норма (`normal`)

Значения по умолчанию из спеки часов сердца: синусовый ритм 75 в минуту, давление 120/80 мм рт. ст., ФВ 65%.

### Фибрилляция предсердий (`atrial-fibrillation`)

| Параметр | Значение | Откуда |
|---|---|---|
| `pAmplitude` | 0 | Нет зубцов P: ESC 2024 AF, §3.1 («absence of discernible and regular P waves»); ACC/AHA 2023 AF, табл. 4 |
| `rrIrregularity` | 0,2 | Интервалы RR без закономерности: ESC 2024 AF, §3.1. Величина разброса это допущение. RR каждого удара ограничен диапазоном ЧСС 40–180, поэтому при средней ЧСС выше 150 разброс становится односторонним и ритм выглядит ровнее |
| `fWaveFrequency` | 6 Гц (360 в минуту) | Волны f (ACC/AHA 2023, табл. 4). Частота предсердий 300–500 в минуту (ACC/AHA 2023, §2.3) и 350–700 в минуту (Минздрав 2025, по статье); 360 входит в оба диапазона |
| `fWaveAmplitude` | 0,05 мВ | Допущение: амплитуду волн f источники не дают |
| `atrialContribution` | 0 | Предсердия перестают эффективно сокращаться: ESC 2024 AF, §3.1. Доли в процентах источники не дают, 0 это выражение «нет сокращения» в модели |

### Инфаркт с подъёмом ST (`stemi`)

| Параметр | Значение | Откуда |
|---|---|---|
| `stShift` | 0,2 мВ (2 мм) | Подъём ST в точке J ≥1 мм (0,1 мВ) в отведениях, кроме V2–V3: ESC 2023 ACS, §3.2.1; StatPearls «Acute Coronary Syndrome». Отведение модели похоже на II; 0,2 мВ вдвое выше порога и достигается уже в точке J (подъём ST начинается за 15 мс до неё, при узком QRS за 15% его ширины, чтобы не задеть зубец S) |

Сверхострые зубцы T ESC называет без амплитуды, поэтому `tAmplitude` не меняется. Источник требует подъёма в двух соседних отведениях, а модель показывает одно отведение, похожее на II, то есть нижнюю стенку и без реципрокных изменений: это сказано в описании пресета.

### Сердечная недостаточность (`heart-failure`)

| Параметр | Значение | Откуда |
|---|---|---|
| `ejectionFraction` | 0,30 | Сниженная ФВ, 40% и меньше: StatPearls «Heart Failure»; Минздрав 2024 (по статье). 30% это пример внутри группы. ESC 2026 пересмотрела классификацию по ФВ, её числа не сверены, поэтому на неё не ссылаемся |

Пресет называется «Сердечная недостаточность со сниженной ФВ», чтобы его не принимали за любую сердечную недостаточность. Конечно-диастолическое давление источники упоминают без числа, поэтому оно не меняется. Третий тон в модели не синтезируется.

### Артериальная гипертензия (`hypertension`)

| Параметр | Значение | Откуда |
|---|---|---|
| `lvSystolic`, `aorticDiastolic` | 160 и 100 мм рт. ст. | Стадия 2 по NICE NG136: от 160/100 до 180/120 не включая. Выше порога ESC 2024 (≥140/90, §6.1) и Минздрава 2024 (≥140/90) |

Допущение: систолическое давление ЛЖ равно систолическому артериальному давлению (без стеноза так и есть в модели).

### Аортальный стеноз (`aortic-stenosis`)

| Параметр | Значение | Откуда |
|---|---|---|
| `aorticValveGradient` | 40 мм рт. ст. | Тяжёлый стеноз: средний градиент ≥40 мм рт. ст. или пиковая скорость ≥4 м/с (ACC/AHA 2020 VHD, табл. 13) |
| `lvSystolic` | 180 мм рт. ст. | Стеноз повышает систолическое давление ЛЖ (StatPearls «Aortic Stenosis»). Само число это допущение: при нём пик давления в аорте около 120 (в модели 119) |

Источники дают средний градиент, а не пиковый. В модели градиент во время изгнания равен 0 в начале и конце изгнания и максимален на 35% изгнания, там же, где пик давления ЛЖ (форма `sin(π·x^p)` это допущение); он отнормирован так, что его среднее за изгнание равно параметру, а пик в 1,52 раза больше среднего. Давление аорты при этом сохраняет обычную форму с одним подъёмом и пиком около 120, а давление ЛЖ выше него на градиент. Признаки, которые модель не показывает: медленный подъём пульса (pulsus parvus et tardus), ослабленный A2, систолический шум.

### Дефект межпредсердной перегородки (`atrial-septal-defect`)

| Параметр | Значение | Откуда |
|---|---|---|
| `qrsDuration` | 130 мс | При ДМПП бывает неполная или полная блокада правой ножки пучка Гиса (Минздрав 2023, §2.4). Полная блокада это QRS шире 0,12 с (StatPearls «Electrocardiogram»). Ширину неполной блокады источники не дают, поэтому берём ширину полной. Модель только расширяет QRS и не рисует форму блокады, поэтому тексты говорят «широкий QRS», а не «блокада», и упоминают, что блокада бывает неполной или полной |
| `s2Split` | 50 мс | Широкое фиксированное расщепление S2: StatPearls «Atrial Septal Defect», Минздрав 2023, §2.2. Величина в мс это допущение |

Отклонение оси вправо и комплекс rSR′ в V1 одно отведение не покажет. Давления в левых камерах при ДМПП источники числами не описывают, поэтому диаграмма Виггерса не меняется, и описание пресета об этом говорит.

## Пакет `@heart-atlas/heart-clock`

### Новые параметры

| Параметр | По умолчанию | Диапазон | Что делает |
|---|---|---|---|
| `aorticValveGradient` | 0 | 0–100 мм рт. ст. | Во время изгнания давление ЛЖ выше давления аорты на градиент (форма выше, среднее равно параметру); в начале и конце изгнания градиент 0, поэтому кривые не рвутся. Пик ЛЖ остаётся `lvSystolic`, пик аорты `lvSystolic` − 1,52 · градиент, закрытие аортального клапана считается от пика аорты. Проверка: пик аорты не ниже `aorticDiastolic` + 10, иначе `RangeError` |
| `s2Split` | 0 | 0–100 мс | `soundEvents` добавляет событие `P2` через `s2Split` после `S2` на каждом ударе (фиксированное расщепление, дыхания в модели нет) |

### API пресетов

- `presetIds`: список id в порядке показа; `type PresetId`.
- `presets`: замороженные частичные наборы параметров.
- `presetParams(id)`: полный набор параметров (значения по умолчанию плюс пресет). Переключение на другой пресет через `setParams(presetParams(id), t)` сбрасывает всё, что менял прошлый пресет.
- `isPresetId(value)`: проверка строки из frontmatter или query string.
- `heartRateLimits(params)`: самый широкий непрерывный диапазон целых ЧСС, при которых параметры помещаются в цикл (проверяется каждое значение). Широкий QRS при ДМПП не помещается при самых высоких ЧСС (предел 169), и слайдер предлагает только допустимые значения.

Тесты: каждый пресет проходит `checkParams` на всём диапазоне `heartRateLimits`; у всех пресетов, кроме ДМПП, диапазон полный 40–180; переключение пресетов сбрасывает параметры; при `aorticValveGradient` 40 средняя разница ЛЖ и аорты за изгнание 40, пик ЛЖ 180, у аорты один подъём с пиком 105–125, кривые непрерывны; при STEMI ЭКГ в точке J не ниже 0,18 мВ; `P2` идёт через `s2Split` после `S2`.

## Веб-часть

### Остров `HeartClockDemo`

- Новые пропсы: `presets?: readonly PresetId[]` (варианты переключателя) и `preset?: PresetId` (с чего начать, по умолчанию первый вариант). Без `presets` переключателя нет, как сейчас. `params` остаются и применяются поверх пресета.
- Переключатель это `fieldset` с подписью «Состояние» и радиокнопками с названиями пресетов. Статья передаёт `['normal', <свой пресет>]` и начинает со своего пресета, страница часов сердца передаёт все пресеты и начинает с нормы.
- При смене пресета часы получают `setParams({ ...presetParams(id), ...params, heartRate }, now)`. `heartRateLimits` считается по этому же объединённому набору, ЧСС читателя сначала ограничивается им, а потом уходит в `setParams`; у слайдера меняются `min` и `max`, у `<output>` значение. Если `params` из пропсов несовместим с пресетом (`RangeError`), этот пресет не показывается в переключателе. Запланированные звуки после смены отменяются, как при смене ЧСС.
- Под переключателем короткий текст о том, что показывает пресет и чего модель не показывает (`heartClock.preset.<id>.summary`), связанный с группой через `aria-describedby`. Описание ЭКГ не объявляется через `aria-live`, чтобы смена ЧСС не зачитывалась на каждом шаге слайдера; смену пресета читатель и так слышит по выбранной радиокнопке. Описание ЭКГ для экранных дикторов берётся из `heartClock.preset.<id>.ecg` с подстановкой `{rate}`: при фибрилляции нет зубцов P и интервалы разные, при ДМПП широкий QRS и так далее.
- Звук: голос `P2` чуть ниже и тише `S2`.
- В тексте интерфейса бывают только числа из таблиц выше (значения пресетов и пороги из источников), с тем же смыслом, что в таблице; источники перечислены под монитором. Для нормы текст говорит о давлении в аорте, а не об «артериальном давлении в норме»: ESC 2024 относит 120–139/70–89 к повышенному давлению.

### Страница часов сердца

Переключатель со всеми пресетами. К списку источников под графиками добавляются все документы из таблиц пресетов выше: ESC 2024 AF, ACC/AHA 2023 AF, Минздрав «Фибрилляция и трепетание предсердий» 2025, ESC 2023 ACS, StatPearls «Acute Coronary Syndrome», StatPearls «Heart Failure», Минздрав «Хроническая сердечная недостаточность» 2024, NICE NG136, ESC 2024 по гипертензии, Минздрав «Артериальная гипертензия у взрослых» 2024, ACC/AHA 2020 VHD, StatPearls «Aortic Stenosis», StatPearls «Atrial Septal Defect», Минздрав «Дефект межпредсердной перегородки» 2023. StatPearls «Electrocardiogram» уже в списке. Список живёт в одном модуле рядом со страницей, чтобы новый пресет добавлял источник в одном месте.

### Страница статьи

- В схеме статьи (`apps/web/src/lib/content.ts`) необязательное поле `preset`: id любого пресета, кроме `normal` (норма ничего не показала бы, поэтому схема её не принимает). Поле согласовано с тредом «Схема контента»; значения в статьях проставляет он.
- Если `preset` задан, страница показывает монитор с переключателем `['normal', preset]` после текста статьи и перед списком источников: заголовок «Как это выглядит в модели», под ним заметка `heartClock.schematic` (та же, что на странице часов сердца), потом монитор. Поставить монитор внутрь текста нельзя без правки MDX, а статьи меняет тред «Схема контента».
- Остров грузится через `client:visible`, поэтому на статьях без `preset` и до прокрутки до монитора его код не загружается.
- В `docs/plans/2026-10-11-stage-3-diseases.md` убираем фразу, что схема пока не принимает `preset`.

### Строки

`heartClock.condition` («Состояние»), `heartClock.inModel` («Как это выглядит в модели»), `heartClock.preset.<id>` (название), `heartClock.preset.<id>.summary`, `heartClock.preset.<id>.ecg` для семи пресетов, в `en.json`, `ru.json` и тестовой RTL-локали. Заметка о схематичной модели переиспользует `heartClock.schematic`. Тексты описаний говорят только то, что есть в источниках из таблиц выше, и называют то, чего модель не показывает (форма блокады при ДМПП, второе отведение при STEMI, шумы).

### e2e

На странице часов сердца: переключение на фибрилляцию меняет описание ЭКГ и не ломает отрисовку, максимум слайдера ЧСС при ДМПП равен `heartRateLimits` из пакета (e2e берёт число из пакета, а не из теста), axe без нарушений. На тестовой статье с `preset`: монитор есть и начинает с пресета статьи, axe без нарушений; на статье без `preset` монитора нет.

## План PR

| PR | Что | Готово, когда |
|---|---|---|
| 1 | Эта спека | Спека в `main` |
| 2 | Пакет: `aorticValveGradient`, `s2Split`, пресеты, `heartRateLimits`, голос `P2`, тесты | Тесты зелёные |
| 3 | Переключатель пресетов в острове, строки, источники на странице часов сердца, e2e | Переключатель работает на двух языках, e2e и axe зелёные |
| 4 | Поле `preset` в схеме, монитор на странице статьи, правка спеки статей, e2e на тестовой статье | Тестовая статья с `preset` показывает монитор |

## Риски

- Пресет может выглядеть как «настоящая» ЭКГ болезни. Защиты: заметка о схематичной модели рядом с монитором, описание того, чего модель не показывает, и правило «нет источника, нет числа».
- Числа, помеченные как допущения (разброс RR, амплитуда волн f, величина расщепления S2, давление ЛЖ при стенозе), могут не совпасть с тем, что ожидает врач. Они собраны в таблицах выше, чтобы рецензент проверил их в одном месте.
