# Часы сердца: паузы, частота ниже 40, дельта-волна, отведение V1 и трепетание

Дата: 13.10.2026
Статус: принята автономно (владелец 12.10.2026: три параллельных потока, «Все автономно»). Issue: #192.
Связано: спека часов сердца `2026-10-11-stage-2-heart-clock.md`, пресеты `2026-10-11-stage-3-presets.md` и `2026-10-12-more-ecg-presets.md`, инструкция `docs/adding-a-disease-preset.md`. Часы сердца и 3D «после лечения» — issue #179, после этого этапа.

## Зачем

Поток «Интерактив для статей» трижды не смог сделать ЭКГ-пресет, потому что модель не умеет нужный признак:

- **Синдром слабости синусового узла (СССУ).** Нет синусовых пауз и остановки синусового узла, а частота не опускается ниже 40 в минуту, то есть ниже нижнего края ползунка.
- **WPW.** Нет дельта-волны: модель может укоротить PR и расширить QRS, но начало QRS всегда крутое.
- **Трепетание предсердий** (статья #195). Модель рисует волны f фибрилляции (две несоизмеримые синусоиды), но не пилообразные волны F трепетания и не проведение 2:1 или 4:1.
- **Синдром Бругада.** Признак виден в правых грудных отведениях V1–V2, а модель рисует одно отведение, похожее на II. Подъём ST в модели — плато, как при инфаркте, а не «сводчатый» спуск в отрицательный T.

Этот этап добавляет механику. Сами пресеты (СССУ, WPW, Бругада) с числами и источниками делает поток интерактива по `docs/adding-a-disease-preset.md`, как раньше.

## Принципы

- Каждый новый параметр — общий механизм модели, а не «режим болезни». Его значение в пресете требует источника или пометки `assumption:`, как и любое другое число пресета.
- Значения по умолчанию не меняют ни одной кривой при частотах 40–180: ЭКГ, давления, объём и звуки всех существующих пресетов остаются прежними. Это проверяет тест, который сравнивает ЭКГ и гемодинамику каждого пресета со снимком, сделанным до изменений, и тест, что `heartRateLimits` каждого пресета в пределах 40–180 тот же.
- Меняются только тесты, которые читают нижнюю границу модели как границу ползунка (список в разделе 1).
- То, чего модель не рисует, пресет называет в `summary`, как сейчас (например, «модель не рисует волну r′» или «выскальзывающих сокращений в паузе нет»).

## 1. Частота ниже 40

- `paramRanges.heartRate` становится `[30, 180]`. Тайминг фаз (регрессии Weissler для QS2 и LVET) при 30 в минуту экстраполируется за пределы выборки; это `assumption:` в комментарии. Фазы при 30 укладываются в цикл (проверяет `beatTiming`).
- **PR не растёт ниже 40.** PR масштабируется с √RR, и без ограничения стандартные 160 мс при 30 в минуту стали бы 226 мс, то есть картиной AV-блокады I степени. Поэтому масштаб PR берёт RR не длиннее 1500 мс (40 в минуту): `√(min(RR, 1500) / 1000)`. На частотах 40–180 ничего не меняется. `assumption:` источники не дают зависимости PR от частоты ниже 40. QT по-прежнему масштабируется по всему RR.
- **Нерегулярный ритм не замедляется сильнее, чем раньше.** Сейчас `clampRr` не даёт RR длиннее 1500 мс, потому что граница модели — 40. Чтобы фибрилляция предсердий при 40–47 в минуту не изменилась, самый длинный RR — `60000 / min(40, heartRate)`: при среднем ритме ниже 40 удар не бывает медленнее среднего.
- **Ползунок.** Новая константа `defaultRateLimits = [40, 180]` — пределы ползунка для пресетов без своей нижней границы. Пресет, которому нужен ритм ниже 40, задаёт её в `presetRateFloors` (сейчас там только повышения для СВТ и ЖТ). `presetRateLimits` берёт `presetRateFloors[id] ?? 40` вместо нижней границы модели. Так ползунок, конструктор встраивания и `?rate=` во встраивании получают значения ниже 40 только у такого пресета.
- **Что меняется в тестах и коде, который читает границу модели:**
  - e2e часов сердца ждёт `min` ползунка равным `paramRanges.heartRate[0]`; теперь — `defaultRateLimits[0]`.
  - Правило в `presets.test.ts` «диапазон пресета — весь `paramRanges.heartRate`, если пресета нет в `narrowedLimits`» сравнивает с `defaultRateLimits`. Так же меняется текст в `docs/adding-a-disease-preset.md`.
  - Конструктор встраивания (`embed/index.astro`) берёт нижнюю границу по умолчанию из `defaultRateLimits`.
  - `?rate=35` во встраивании: раньше значение вне модели игнорировалось, теперь оно в модели и прижимается к пределам пресета (к 40 у обычного пресета). e2e встраивания проверяет оба случая.
- Тест: `presetRateLimits` всех существующих пресетов не меняется; тестовый набор параметров с полом 30 получает 30.

## 2. Синусовые паузы и остановка синусового узла

Два параметра:

| Параметр        | Значения                 | По умолчанию | Смысл                                                                                      |
| --------------- | ------------------------ | ------------ | ------------------------------------------------------------------------------------------ |
| `pauseEvery`    | целое: 0 или от 2 до 20  | 0            | Пауза после каждого N-го удара; 0 — пауз нет.                                              |
| `pauseDuration` | 0–6000                   | 0            | Длина паузы, мс: столько времени после удара нет ни P, ни QRS, линия ЭКГ идёт по изолинии. |

`pauseEvery` и `pauseDuration` проверяются вместе: оба 0 или оба больше 0. Пауза после каждого N-го удара — модель (`assumption:`): настоящие паузы нерегулярны, а выскальзывающих сокращений модель не рисует. Пресет говорит это в `summary`.

**Данные.** У удара появляется поле `pause` (мс, 0 у обычного удара):

- `timing.rr` остаётся RR ритма: по нему считается весь тайминг фаз (PR, QT, механика) и все формы кривых в `hemodynamics.ts`, как сейчас. `beatTiming` проверяет только его, поэтому удар с паузой не выходит за диапазон частот.
- `beat.rr = timing.rr + pause` — расстояние до начала следующего удара. Его читают расписание ударов в `clock.ts` (начало следующего удара, история, длинный прыжок во времени) и стекание крови из аорты (`aorticRunoff`).
- Пауза добавляется после `clampRr`. Какой удар с паузой, решает номер удара (`index % pauseEvery === pauseEvery − 1`), поэтому картина воспроизводима, как и `rrIrregularity` с `seed`.

**Гемодинамика в паузе** (время в ударе больше `timing.rr`):

- ЛЖ и ЛП держат значения конца диастолы, то есть те, с которых начинается следующий удар. Быстрое наполнение и диастазис занимают столько же, сколько без паузы, а не растягиваются на всю паузу. Предсердного толчка в паузе нет: он приходит с P следующего удара.
- Аорта продолжает стекать до открытия аортального клапана в следующем ударе: модель растягивает стекание на весь `beat.rr`, поэтому в паузе давление в аорте падает медленнее и к открытию клапана приходит к обычному диастолическому. Более низкого диастолического давления после длинной паузы модель не показывает (`assumption:`, ограничение модели); тест проверяет, что давление в аорте в паузе монотонно падает и непрерывно на границах.

**Остальное:**

- ЭКГ: в паузе изолиния (с хвостом зубца T предыдущего удара, если он туда заходит). Волны f (фибрилляция предсердий) — активность предсердий, поэтому при `fWaveAmplitude > 0` они идут и в паузе.
- 3D: в паузе синусовый узел не возбуждён, клапаны и камеры в состоянии диастазиса (`cycleState` берёт фазу «наполнение» из тайминга).
- Звуки: в паузе тоны не звучат (S1 и S2 привязаны к ударам).
- Диаграмма Виггерса: ось времени удара по-прежнему масштабируется по `timing.rr`, а не по `beat.rr`, иначе на ударе с паузой систола сжалась бы в полоску. В паузе курсор стоит у правого края диаграммы, а подпись говорит «пауза».
- Длина паузы до 6 с: этого хватает для пауз, которые описывают рекомендации (пресет берёт число из источника). Полоса ЭКГ на телефоне шириной 320 px показывает около 3,4 с, поэтому длинная пауза может занять всю полосу; это видно и честно.
- Частота на экране остаётся частотой ритма между паузами. Описание пресета для экранного диктора (`heartClock.preset.<id>.ecg`) говорит, что после каждых N ударов пауза.
- Тесты:
  - в паузе ЭКГ равна изолинии;
  - удар с паузой длиннее на `pauseDuration`;
  - гемодинамика непрерывна на границах паузы, ЛЖ и ЛП в паузе постоянны, аорта падает;
  - нижняя граница пресета 30 даёт ползунку 30 (через общую функцию `rateLimitsFrom`, которую использует `presetRateLimits`);
  - `cycleState` в паузе: фаза «наполнение», синусовый узел не возбуждён;
  - `beatsBetween` через паузу возвращает удары в правильном порядке, звуки в паузе не планируются;
  - `setParams` во время паузы применяет новые параметры со следующего удара и не обрывает паузу;
  - при `pauseEvery: 0` расписание ударов то же, что сейчас.

## 3. Дельта-волна (предвозбуждение)

Параметр `deltaWave`, мс, 0–80, по умолчанию 0: длительность пологого начала QRS.

- Укороченный PR и широкий QRS пресет задаёт существующими `prInterval` и `qrsDuration`. `deltaWave` меняет только форму начала комплекса.
- **Раскладка QRS** при `deltaWave = d > 0` (`qrs` — `qrsDuration`, `rest = qrs − d`, отсчёт от начала QRS):
  - зубца Q нет; с начала QRS идёт пологий линейный подъём, который за d мс доходит до 0,3 от R и продолжает расти с тем же наклоном, пока его не обгонит крутой восходящий R; дальше рисуется R. Так дельта-волна переходит в R без ступеньки, а R сохраняет свою высоту;
  - R в центре `d + 0,35·rest` с шириной `0,1·rest`: его крутой подъём начинается сразу после дельта-волны, а не после плато;
  - S в центре `d + 0,75·rest` с шириной `0,07·rest`;
  - при d = 0 раскладка прежняя (Q, R в середине QRS, S на 0,8).
- `rPeak` (по нему модель закрывает митральный клапан и даёт S1) сдвигается на новый пик R. Так в модели S1 идёт за пиком R; это свойство модели, а не признак WPW, и пресет не описывает его как признак.
- Поздний пик R сокращает время до открытия аортального клапана, поэтому `heartRateLimits` пресета с дельта-волной может сузиться сверху, как сейчас при широком QRS. Инструкция для пресетов говорит об этом.
- `assumption:` высота пологого подъёма — 0,3 от R. Источники описывают форму («пологий», «смазанный» подъём), а не амплитуду.
- Проверка: `deltaWave` не больше половины `qrsDuration`, иначе крутой части R не остаётся, и не меньше `0,15·rest`, иначе продолженный пологий подъём перерос бы R.
- Вторичные изменения ST–T пресет задаёт существующими `tAmplitude` и `stShift`. Дополнительного пути в 3D-модели нет (в модели проводящей системы его нет); это отмечено в `summary` пресета.
- Тесты: при `deltaWave: 0` ЭКГ та же; при `deltaWave > 0` наклон начала QRS заметно меньше наклона восходящего R, Q нет, пик R там, где сказано выше, и его высота прежняя.

## 4. Отведение V1 и «сводчатый» ST

### Отведение пресета

Модель по-прежнему рисует одно отведение, но пресет может сказать, какое: `presetLeads: Partial<Record<PresetId, Lead>>`, где `Lead` — `'II' | 'V1'`, по умолчанию `'II'`.

- У каждого отведения своя базовая форма комплекса, `leadBaselines: Record<Lead, Partial<HeartParams>>`. Для II — значения по умолчанию (как сейчас). Для V1 — небольшой r и глубокий S, по источнику о нормальной ЭКГ, с `assumption:` для амплитуд, которых источник не даёт. Параметры пресета накладываются поверх базы его отведения: `defaults → leadBaselines[lead] → preset`.
- `presetParams(id, lead = presetLeads[id] ?? 'II')`. Все, кто сейчас вызывает `presetParams(id)` (`presetRateLimits`, `switchedHeartRate`, сборка параметров в `HeartClockDemo`, конструктор встраивания), получают отведение пресета без изменений в вызове.
- **Норма в отведении пресета.** В статье часы показывают два пресета: норму и болезнь. Если у пресета болезни своё отведение, норма тоже рисуется в нём, иначе читатель сравнивает V1 с II. `ArticleClock` передаёт `HeartClockDemo` проп `lead` из `presetLeads` пресета статьи, и `HeartClockDemo` строит норму как `presetParams('normal', lead)`. На странице часов сердца (все пресеты) пропа нет: каждый пресет в своём отведении, норма в II.
- Подпись полосы показывает отведение: «Синтетическая ЭКГ, отведение V1» вместо «одно отведение» (строка `heartClock.ecgLabel` получает `{lead}`). На странице часов сердца при смене пресета подпись меняется вместе с ним.
- Свободного переключателя отведений для читателя в этом этапе нет. Чтобы каждый пресет правдиво выглядел в каждом отведении, нужна многоотведённая модель с источником для каждого признака, а это отдельная большая работа. Пресеты без своего отведения остаются в II.

Новые параметры формы QRS, чтобы задать V1:

| Параметр     | Диапазон   | По умолчанию | Смысл                                   |
| ------------ | ---------- | ------------ | --------------------------------------- |
| `rAmplitude` | 0,1–2      | 1            | Амплитуда R, мВ (сейчас константа 1).   |
| `sAmplitude` | −2–0       | −0,25        | Амплитуда S, мВ (сейчас константа −0,25). |

### Форма подъёма ST

Параметр `stSlope`, 0–1, по умолчанию 0.

- 0 — плато от точки J до зубца T, как сейчас (инфаркт с подъёмом ST).
- 1 — «сводчатый» ST: подъём максимален в точке J и выпуклой дугой спускается к зубцу T. Вместе с отрицательным `tAmplitude` это рисует картину типа 1 при синдроме Бругада. Промежуточные значения — доля подъёма, которая теряется к центру T.
- Волну r′ (картина, похожая на блокаду правой ножки) модель не рисует; пресет говорит это в `summary`.
- Тесты: при `stSlope: 0` ЭКГ та же; при `stSlope: 1` ST в точке J выше, чем на середине сегмента, и сегмент монотонно спускается к T; ЭКГ с V1, дельта-волной и `stSlope` вместе проходит `checkParams` и рисуется без разрывов.

## 5. Трепетание предсердий: пилообразные волны F

Два параметра:

| Параметр            | Значения                | По умолчанию | Смысл                                                                                     |
| ------------------- | ----------------------- | ------------ | ----------------------------------------------------------------------------------------- |
| `flutterConduction` | целое: 0 или от 2 до 4  | 0            | Проведение N:1: на каждый QRS приходится N волн F. 0 — трепетания нет.                     |
| `flutterAmplitude`  | 0–0,3                   | 0            | Размах волн F, мВ. 0 ровно тогда, когда `flutterConduction` равно 0.                       |

- Волны F привязаны к удару: внутри каждого удара их ровно N, фаза — `(t − начало удара) · N / RR ритма`. Поэтому частота волн F всегда N × частота желудочков, и ползунок частоты меняет обе вместе, сохраняя проведение. При регулярном ритме фаза непрерывна на границах ударов.
- Форма — пила: медленный спуск примерно 80% периода и быстрый подъём остальные 20%, отрицательная в отведении II. `assumption:` доли спуска и подъёма; источники описывают форму «пилы», а не её доли.
- Волны F идут и во время QRS и T (складываются с ними), как на настоящей ЭКГ. Пресет трепетания убирает P (`pAmplitude: 0`) и вклад предсердий (`atrialContribution: 0`) существующими параметрами, с источниками.
- Частота предсердий при трепетании ограничена (пресет берёт диапазон из источника), поэтому пресет может ограничить ползунок и сверху: рядом с `presetRateFloors` появляется `presetRateCeilings`, и `presetRateLimits` учитывает оба. Например, при 2:1 и частоте предсердий из источника пресет задаёт пол и потолок частоты желудочков.
- Трепетание и фибрилляция (`fWaveAmplitude`) вместе не допускаются, как и трепетание с синусовыми паузами (волны F начинаются заново с каждым ударом); `rrIrregularity` при трепетании тоже 0 (проведение в модели постоянное, `assumption:`, пресет говорит это в `summary`).
- При смене параметров волны F переходят плавно, как волны f сейчас.
- Тесты: в каждом ударе ровно N впадин пилы; при 0 ЭКГ та же; при смене частоты отношение частот сохраняется; комбинация с `fWaveAmplitude` отклоняется; `presetRateCeilings` ограничивает ползунок.

## Встраивание и доступность

- Встраивание `/embed/ecg?preset=<id>` берёт отведение и нижнюю границу частоты пресета, как и страница часов.
- Подпись полосы и описание для экранного диктора называют отведение. Новые строки — во всех языках и тестовой локали справа налево.
- e2e: страница часов с тестовым пресетом в V1 показывает отведение в подписи; ползунок пресета с полом 30 доходит до 30; ЭКГ с паузой рисуется без ошибок в консоли. Тестовые пресеты живут только в тестовой сборке, как схема `demo-treated`, чтобы e2e не зависел от пресетов потока интерактива.

## Инструкция

`docs/adding-a-disease-preset.md` получает раздел о новых параметрах: паузы, частота ниже 40, дельта-волна, отведение и форма ST, трепетание, с правилом для чисел (каждое — из источника или `assumption:`).

## План PR (issue #192)

1. **Спека** (этот файл).
2. **Частота ниже 40 и паузы.** `heartRate` от 30, нижняя граница пресета по умолчанию 40, `pauseEvery` и `pauseDuration`, гемодинамика в паузе, тесты пакета.
3. **Дельта-волна.** `deltaWave`, сдвиг пика R, тесты.
4. **Отведение и форма ST.** `rAmplitude`, `sAmplitude`, `stSlope`, `presetLeads`, `leadBaselines`, норма в отведении пресета, подпись полосы с отведением, строки, тестовые пресеты в тестовой сборке, e2e.
5. **Трепетание.** `flutterConduction`, `flutterAmplitude`, `presetRateCeilings`, тесты.
6. **Инструкция** в `docs/adding-a-disease-preset.md`. После её слияния координатор передаёт механику потоку интерактива.
