Как начать новый проект
1. Скопируйте build_infographic_v2.py из /var/www/mdwrt/egis_ezetimibe_infographic/ как образец структуры. 2. Заведите свой estimates.py — все числа только там. 3. Соберите страницу из блоков ниже. 4. Прогоните audit_numbers.py и публикуйте через publish_pages.py.
import sys; sys.path.insert(0, '/var/www/mdwrt/mdwrt_infographic_kit')
from theme import BASE_CSS, GLOSS, BASE_JS, num, esc
from clinician_blocks import *
html = f'<!DOCTYPE html><html lang="ru"><head><meta charset="utf-8"><style>{BASE_CSS}{CLINICIAN_CSS}</style></head><body>'
# ... sidebar + hero + секции ...
html += BASE_JS.replace('__GLOSS__', GLOSS['ru']).replace('__DSEP__', ',') + REVEAL_JS + '</body></html>'
Блоки
Иконки разделовЗаголовок разделаПлашки с числамиПиктограмма «на каждые 100»Дорожки-сравненияКарточки-стратегииРазвилка решенияКольца долейСвёрнутая статистикаИтоги и приглушённые плашкиВыноски и таблицы (из BASE_CSS)Карточка эффекта: исследование → конечная точкаФорест-плот (kol_blocks.py)Вкладки: РКИ vs наблюдательныеГрафик падения во времениОценка риска систематической ошибкиPRISMA-воронка (prisma_funnel.py)Пересчёты (вместо ручных чисел)Переключатель языка и ссылка на другую версию
Иконки разделов
Только инлайн-SVG: эмодзи (🛡, ⏳) рендерятся «тофу»-квадратами в headless Chromium и в PDF.
from clinician_blocks import icon, ICONS
icon('shield') # → инлайн-SVG 24×24, цвет наследуется от родителя
Заголовок раздела
Эффективность
Цель по ЛПНП достигается чаще
Ограничения
О чём важно помнить
section_head('target', 'Эффективность', 'Цель по ЛПНП достигается чаще', 'g')
# tone: '' синий · 'g' зелёный · 'r' красный · 'a' янтарный
Плашки с числами
Варианты cls: hero (зелёный, по умолчанию) · blue · warn · '' (нейтральный). Число в pct_card анимируется при прокрутке.
−48%
мышечных нежелательных явлений
рандомизированные исследования
+27%
чаще достигается цель по ЛПНП
рандомизированные исследования
−0,21 ммоль/л
дополнительное снижение ЛПНП
(7,95 мг/дл)
1,8
целевой ЛПНП, ммоль/л
cards(
pct_card(48, '−', 'мышечных нежелательных явлений', 'рандомизированные исследования'),
pct_card(27, '+', 'чаще достигается цель по ЛПНП', 'рандомизированные исследования'),
card('−0,21<span class="u"> ммоль/л</span>', 'дополнительное снижение ЛПНП', '(7,95 мг/дл)'),
card('1,8', 'целевой ЛПНП, ммоль/л', cls='blue'),
)
Пиктограмма «на каждые 100»
Флагманский блок: это ровно RR×100, поэтому честен без абсолютных частот событий (которых в мета-анализах обычно нет). extra_from подсвечивает «лишние» точки при RR > 1.
100
пациентов достигают цели
на высокой дозе статина
на высокой дозе статина
127
пациентов достигают цели
на комбинации
на комбинации
На каждые 100 пациентов, достигших цели на высокой дозе статина, цели достигают 127 пациентов на комбинации — жёлтым выделены «дополнительные» 27%.
pictogram('пациентов достигают цели', 'на высокой дозе статина',
per100(1.27), # = 127
'пациентов достигают цели', 'на комбинации',
'На каждые 100 … цели достигают 127 …', extra_from=100)
Дорожки-сравнения
Замена форест-плота: сравнение = 100% (штриховка), вмешательство = RR×100%. Полосы вырастают при попадании в экран.
bars_block([
bar_row('Мышечные нежелательные явления', 0.52, 'на 48% реже'),
bar_row('Госпитализация по поводу СН', 0.94, 'без значимой разницы', significant=False),
], 'фиксированная комбинация', 'высокоинтенсивный статин = 100%',
'чем короче зелёная полоса, тем реже событие')
Карточки-стратегии
Стратегия 1
Добавить эзетимиб
- Второй механизм: блокада всасывания холестерина
- Доза статина остаётся низкой или средней
Стратегия 2
Поднять дозу статина
- Тот же механизм, просто больше препарата
- Прирост эффекта по ЛПНП скромный
strategy_cards([
('Стратегия 1', 'Добавить эзетимиб', ['Второй механизм', 'Доза статина не растёт'], True),
('Стратегия 2', 'Поднять дозу статина', ['Тот же механизм', 'Прирост скромный'], False),
])
Развилка решения
Пациент на статине низкой или средней интенсивности, цель по ЛПНП не достигнута
▼
Добавить эзетимиб
−15…22%
дополнительное снижение ЛПНП сверх текущей дозы статина
Удвоить дозу статина
≈ −6%
прирост эффекта при каждом удвоении дозы («правило шести»)
fork('Пациент на статине <b>средней интенсивности</b>, цель не достигнута',
[('Добавить эзетимиб', '−15…22%', 'дополнительное снижение ЛПНП', True),
('Удвоить дозу', '≈ −6%', 'правило шести', False)])
Кольца долей
Кольцо закрашивается при прокрутке. dsep — десятичный разделитель, для англоязычной версии '.'.
продолжают приём через 6 месяцев
продолжают приём через 2 года
Доля пациентов после инфаркта миокарда, продолжающих принимать высокоинтенсивный статин.
rings([ring(58.9, 'продолжают приём через 6 месяцев'),
ring(41.6, 'продолжают приём через 2 года')],
'Доля пациентов, продолжающих терапию.')
# для EN: ring(58.9, '...', dsep='.')
Свёрнутая статистика
Ключевой приём для «версии для врача»: статистику не удаляем, а прячем под клик.
Точные цифры (RR / HR, 95% ДИ) — для тех, кому нужна статистика
| Показатель | Оценка [95% ДИ] | Данные |
|---|---|---|
| Мышечные нежелательные явления | RR 0,52 [0,32; 0,85], I²=0% | РКИ |
| Госпитализация по поводу СН | HR 0,94 [0,87; 1,01], I²=0% | наблюдательные |
stat_details('Точные цифры (RR / HR, 95% ДИ) — для тех, кому нужна статистика',
[('Мышечные НЯ', 'RR 0,52 [0,32; 0,85], I²=0%', 'РКИ'),
('Госпитализация по поводу СН', 'HR 0,94 [0,87; 1,01], I²=0%', 'наблюдательные')],
('Показатель', 'Оценка [95% ДИ]', 'Данные'))
Итоги и приглушённые плашки
- Если пациент не дошёл до цели — добавление эзетимиба даёт больше, чем удвоение дозы.
- Мышечные жалобы — на 48% реже: прямой аргумент при непереносимости высоких доз.
Различий не выявлено: рабдомиолиз, повышение КФК, желудочно-кишечные симптомы.
takeaways(['Первый вывод…', 'Второй вывод…'])
nodiff('Различий не выявлено: рабдомиолиз, КФК, ЖКТ…')
Выноски и таблицы (из BASE_CSS)
Нейтральная выноска. Контекст, определение, PICO.
Подтверждающая. Главный вывод в пользу вмешательства.
Предостережение. Ограничения, оговорки об источнике данных.
Мелкая подпись под блоком — источник, пересчёт единиц, сноска.
<div class="callout">нейтральная</div>
<div class="callout good">подтверждающая</div>
<div class="callout warn">предостережение</div>
<p class="cap">подпись под блоком</p>
Карточка эффекта: исследование → конечная точка
Для «главного результата исследования». Относительный эффект убеждает, но решение врач принимает по абсолютным числам, поэтому рядом считаются абсолютное снижение риска и NNT — из числа событий, без выдуманных допущений. Доверительный интервал показан НА шкале, а не спрятан в подписи.
Семаглутид 2,4 мгФаза III
SELECT — первичная конечная точка
3-компонентная MACE: сердечно-сосудистая смерть · нефатальный ИМ · нефатальный инсульт
−20%
относительное снижение риска
HR 0,80 (95% ДИ 0,72–0,90)
p < 0,001
1,5 п.п.
абсолютное снижение риска
NNT ≈ 67
пациентов нужно пролечить,
чтобы предотвратить 1 событие
чтобы предотвратить 1 событие
Пациенты с установленным ССЗ без диабета, медиана наблюдения 40 месяцев.
from clinician_blocks import effect_card
effect_card('Семаглутид 2,4 мг', 'Фаза III',
'SELECT — первичная конечная точка',
'3-компонентная MACE: сердечно-сосудистая смерть · нефатальный ИМ · нефатальный инсульт',
est=0.80, lo=0.72, hi=0.90, p_text='p < 0,001',
arms=[('Семаглутид', 569, 8803, True), # (название, событий, всего, вмешательство?)
('Плацебо', 701, 8801, False)],
footnote='Пациенты с установленным ССЗ без диабета; медиана наблюдения 40 месяцев.')
# ARR и NNT считаются из чисел событий; если рук не передать — блок просто не рисуется
Блоки расширенной (KOL) версии
Эти блоки — для аудитории, которая читает статистику (КОЛ, научный совет). Для практикующего врача их заменяют плашки с процентами и пиктограммы выше.
Форест-плот (kol_blocks.py)
Одна строка = один объединённый исход (построчных данных по исследованиям в статьях мета-анализов обычно нет). Размер маркера ∝ точности. Наведите курсор на строку — появится понятное пояснение, значимость учитывается честно.
from kol_blocks import forest
rows = [dict(label='Мышечные НЯ', est=0.52, lo=0.32, hi=0.85, i2=0,
ev='RCT', metric='RR', cls='green'), ...]
forest(rows, scale='ratio', lang='ru', fid='safety') # scale='md' — линейная шкала
# cls: green значимо «за» · red значимо «против» · navy незначимо
# fav='right' — если в пользу вмешательства значения > 1 (доля достигших цели)
Вкладки: РКИ vs наблюдательные
Правильный способ показать раздельный анализ РКИ и наблюдательных данных (рекомендация Cochrane Handbook) — не смешивая их в одном пуле.
Скорректированные отношения рисков из наблюдательных исследований.
Объединённый анализ РКИ: интервалы широки из-за малого числа событий.
from kol_blocks import tabs
tabs([('Наблюдательные (HR)', forest(obs_rows, 'ratio', 'ru', 'obs')),
('РКИ (RR)', forest(rct_rows, 'ratio', 'ru', 'rct'))], group_id='mace')
График падения во времени
zone_label — качественная зелёная зона «здесь лучше» без выдуманных чисел: если для второй схемы точных значений в источнике нет, показывать их нельзя.
from kol_blocks import decay_chart
decay_chart([('6 месяцев', 58.9), ('2 года', 41.6)], lang='ru',
series_label='Высокоинтенсивный статин (монотерапия)',
zone_label='Зона лучшей приверженности комбинации',
drop_label='падение приверженности')
Оценка риска систематической ошибки
Cochrane RoB 2 для РКИ; для наблюдательных — шкала Ньюкасл–Оттава (NOS).
B. Kim et al. · низкий риск
Choi et al. · низкий риск
Oh et al. · некоторые опасения
Tan et al. · некоторые опасения
Ran et al. · высокий риск
from kol_blocks import rob_chips
rob_chips([('B. Kim et al.', 'low', 'низкий риск'),
('Oh et al.', 'some', 'некоторые опасения'),
('Ran et al.', 'high', 'высокий риск')])
PRISMA-воронка (prisma_funnel.py)
Для «версии для врача» этот блок обычно УБИРАЮТ — он отпугивает практикующего читателя. Оставляйте в расширенной (KOL) версии.
from prisma_funnel import prisma_funnel
prisma_funnel(stages=[dict(count=9173, label='Идентификация', sub='записей · 4 базы'), ...],
exclusions=[dict(after=0, count=3589, label='дубликатов удалено'), ...], lang='ru')
Пересчёты (вместо ручных чисел)
Функции принимают либо число, либо dict вида {est, lo, hi} — можно передавать строку оценок целиком.
48
pct_less(0,52)
27
pct_more(1,27)
52
per100(0,52)
-0,21
mmol(−7,95) → ммоль/л
pct_less(0.52) # → 48 «на 48% реже»
pct_more(1.27) # → 27 «на 27% чаще»
per100(0.52) # → 52 «на каждые 100 … приходится 52»
mmol(-7.95) # → -0.2056 мг/дл → ммоль/л (K_CHOL=38.67); для ТГ: mmol(x, K_TG)
num(1.27, 2, 'ru') # → '1,27' (theme.py)
Переключатель языка и ссылка на другую версию
Ставится внутрь .title-bar (позиционируется абсолютно в правом верхнем углу).
lang_switch(URL_RU, URL_EN, lang,
ext_url='/infographics/ezetimibe-statin-fc/',
ext_label='Расширенная версия',
ext_title='Полная версия со всей статистикой')