diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..ca16720 --- /dev/null +++ b/PRD.md @@ -0,0 +1,166 @@ +# PRD · ПУЛЬС — Неотложка + +**Игровой тренажёр клинических кейсов по неотложной медицинской помощи** + +Версия документа: 1.0 +Статус: реализовано (MVP), в итеративной доработке +Платформа: статический сайт (HTML + CSS + vanilla JS), публикуется через Git Pages + +--- + +## 1. Концепция + +**ПУЛЬС** — образовательный игровой тренажёр для студентов-медиков 6 курса. Студент работает с «говорящими» видео-пациентами: выслушивает жалобы голосом, самостоятельно назначает обследования и лечение, ставит диагноз и оказывает экстренную помощь на время. За точность начисляется опыт (XP), открываются достижения и уровни, результаты попадают в рейтинг. Алгоритмы решения основаны на клинических протоколах диагностики и лечения МЗ РК. + +**Ценность:** превращает сухую зубрёжку протоколов в тренировку клинического мышления в условиях, близких к приёмному отделению, с мгновенной обратной связью и игровой мотивацией. + +--- + +## 2. Целевая аудитория + +- **Основная:** студенты 6 курса медицинских вузов (интерны, ординаторы) — отработка неотложных состояний. +- **Дополнительная:** врачи СМП и приёмного покоя для повторения алгоритмов; преподаватели — как материал для семинаров. +- **Контекст применения:** Казахстан (протоколы МЗ РК, русскоязычный интерфейс). + +--- + +## 3. Цели и задачи + +### Цели продукта +1. Дать практику клинического мышления по неотложным состояниям в безопасной среде. +2. Закрепить алгоритмы клинических протоколов МЗ РК через повторение. +3. Повысить мотивацию к обучению за счёт геймификации. + +### Метрики успеха (MVP) +- Завершение полного экзамена (6 кейсов) за один заход — основная конверсия. +- Доля повторных прохождений кейсов (replay) — вовлечённость. +- Средний балл по кейсу/экзамену — уровень усвоения. +- Количество разблокированных достижений — глубина погружения. + +--- + +## 4. Функциональные требования + +### 4.1. Профиль игрока и уровни +- Создание профиля: имя + аватар (emoji). +- Система опыта (XP): начисляется за результаты кейсов (пропорционально приросту лучшего балла). +- 8 уровней: Стажёр → Студент → Интерн → Ординатор → Врач → Специалист → Эксперт → Профессор. +- Шапка профиля: аватар, имя, текущий уровень, XP-бар до следующего уровня. +- Сохранение профиля и прогресса в браузере (localStorage). + +### 4.2. Карта прохождения тем +- 3 темы: «ОКС и инфаркт», «ОДН · астма · ХОБЛ», «Шок и травма». +- По каждой теме: прогресс-бар (кейсов пройдено), заработанные звёзды. +- Клик по теме фильтрует кейсы и переносит к списку. + +### 4.3. Кейсы +- 6 клинических кейсов (по 2 на тему). +- Карточка кейса: портрет-аватар (фото), тема, звёзды (1–3), лучший результат, счётчик прохождений, статус «Пройден», кнопка «Играть/Повторить». + +### 4.4. Видео-пациент (говорящий аватар) +- В шаге «Пациент» — видеоплеер с крупным портретом пациента. +- При нажатии «Прослушать»: синтез речи (Web Speech API), портрет «оживает» — звуковые волны, лёгкая анимация говорения, индикатор LIVE. +- **Субтитры** синхронно с речью (по фразам). +- Голос подбирается под пациента: пол, возраст, состояние (pitch/rate/громкость), паузы-«дыхание» между фразами (у астматика — рваная речь с длинными паузами, у шокового — тихая и замедленная). + +### 4.5. Пошаговое решение кейса (экзамен) +Каждый кейс = 4 шага + результат: +1. **Обследования** — множественный выбор из перечня (нужные vs лишние). +2. **Лечение** — множественный выбор препаратов (нужные vs противопоказанные). +3. **Диагноз** — выбор одного верного варианта. +4. **Экстренная помощь ⏱** — за 60 секунд выбрать правильные действия; таймер с обратным отсчётом, автозавершение при истечении. + +Требования к выбору вариантов: +- Варианты кликабельны и доступны для нажатия на всех шагах. +- После «Подтвердить» варианты блокируются и подсвечиваются: верно / лишнее / пропущено / верный диагноз. + +### 4.6. Оценка и обратная связь +- Балл за шаг множественного выбора: `(TP + TN) / всего × 100%`. +- Диагноз: 100% / 0%. +- Вес шагов в кейсе: обследования 20%, лечение 20%, диагноз 30%, экстренная помощь 30%. +- Экран результата кейса: кольцевой индикатор %, грейд (Отлично/Хорошо/Удовл./Неуд), разбивка по шагам, награды (звёзды, XP, новые достижения), разбор: правильный диагноз, **протокол МЗ РК**, ключевые данные, типичные ошибки. + +### 4.7. Геймификация +- **Звёзды за кейс:** ≥85% → 3, ≥70% → 2, ≥55% → 1. +- **Достижения (11 шт.):** «Первый шаг», «Точный диагноз», «Безупречный кейс», «Чистая работа», «Молниеносная реакция», «Мастер ОКС/ОДН/шока», «Великий диагност», «Полное прохождение», «Профессор». +- **Тосты** при разблокировке достижений и повышении уровня. + +### 4.8. Рейтинг игроков +- Топ-10 результатов полного экзамена (имя, аватар, %, дата). +- Сохраняется локально в браузере; результат текущего игрока подсвечен. + +### 4.9. Полный экзамен +- Последовательное прохождение всех 6 кейсов. +- Итоговый экран: средний %, грейд, разбивка по темам и по кейсам. +- Результат добавляется в рейтинг. + +--- + +## 5. Содержание кейсов (по протоколам МЗ РК) + +| # | Тема | Кейс | Протокол МЗ РК | +|---|------|------|----------------| +| 1 | ОКС | Острый инфаркт миокарда с подъёмом ST (STEMI) | «Острый коронарный синдром» | +| 2 | ОКС | ОКС без подъёма ST (NSTEMI) | «Острый коронарный синдром» | +| 3 | ОДН | Острый тяжёлый приступ бронхиальной астмы | «Бронхиальная астма» | +| 4 | ОДН | Обострение ХОБЛ с ОДН 2 типа | «Хроническая обструктивная болезнь лёгких» | +| 5 | Шок | Травматический геморрагический шок | «Шок», «Острое кровотечение» | +| 6 | Шок | Септический шок | «Сепсис и септический шок» | + +Ключевые положения протоколов, отражённые в кейсах: +- **STEMI:** первичное ЧКВ (door-to-balloon ≤ 90 мин); при недоступности ≤ 120 мин — фибринолизис, в т.ч. догоспитальный; двойная антитромбоцитарная терапия + антикоагулянт. +- **NSTEMI:** тромболизис НЕ показан; стратификация по GRACE; ранняя инвазивная стратегия. +- **Астма:** β2-агонисты + ипратропия бромид + системные ГКС + магний; седативные/опиоиды противопоказаны. +- **ХОБЛ:** контролируемая О₂-терапия (целевой SpO₂ 88–92%); NIV при гиперкапнии. +- **Геморрагический шок:** damage control, остановка кровотечения, гемотрансфузия 1:1:1, транексамовая кислота; борьба с триадой смерти. +- **Сепсис:** hour-1 bundle (лактат, гемокультуры, антибиотики в 1-й час, инфузия 30 мл/кг, норадреналин). + +--- + +## 6. Дизайн + +- **Стиль:** выдержанная классическая палитра — графит-navy, кремовый «бумажный» фон, приглушённое золото (акцент), бордо (ОКС), тёмно-бирюзовый (ОДН), кирпичный (шок), хирургический зелёный (верно). Строгий медицинский вид, без неон-яркости. +- **Типографика:** Manrope. +- **Адаптив:** desktop / планшет / мобильный. +- **Иконки:** emoji и inline-элементы. + +--- + +## 7. Технологии и ограничения + +- **Стек:** статические `index.html` + `style.css` + `app.js` (vanilla JS). Без бэкенда, БД, сборщиков, аутентификации. +- **Озвучка:** Web Speech API (SpeechSynthesis), русские голоса браузера. +- **Хранение:** localStorage (профиль, прогресс, рейтинг). +- **Публикация:** Git Pages, ветка `pages`. +- **Ограничения песочницы:** только статика; реальные фото/видео пациентов не используются (этика) — применяется анимированный видео-аватар. + +--- + +## 8. Пользовательский путь + +1. Открывает сайт → видит шапку профиля и hero с уровнем/XP. +2. Создаёт профиль (имя + аватар) через ⚙️. +3. Заходит в «Карту прохождения» или сразу в «Кейсы». +4. Выбирает кейс → видеопациент рассказывает жалобы голосом. +5. Проходит 4 шага (обследования → лечение → диагноз → экстренная помощь на 60 с). +6. Получает результат: %, звёзды, XP, достижения, разбор с протоколом МЗ РК. +7. Идёт к следующему кейсу или запускает полный экзамен → попадает в рейтинг. +8. Растёт в уровне от Стажёра до Профессора. + +--- + +## 9. Roadmap (возможные доработки) + +- Расширение тем: кома и неврология, острый живот, отравления, анафилаксия. +- Режим «Экзамен с таймером на весь кейс». +- Экспорт результата / сертификат прохождения. +- Импорт своих медиа (фото/видео пациентов) для преподавателя. +- Поддержка казахского языка интерфейса. +- Синхронизация прогресса между устройствами (требует бэкенда — вне песочницы). + +--- + +## 10. Ссылки + +- Сайт: https://pages.git.vibe42.kz/saniya_akpanova_91/emergency-cases/ +- Репозиторий: https://git.vibe42.kz/saniya_akpanova_91/emergency-cases diff --git a/app.js b/app.js index 6685bb2..5e80c13 100644 --- a/app.js +++ b/app.js @@ -469,22 +469,24 @@ function pickVoice(preferMale){ return ru[0]; } -let speakState={playing:false, queue:[], idx:0, timer:null, btn:null, avatar:null, opts:null}; +let speakState={playing:false, queue:[], idx:0, timer:null, btn:null, avatar:null, opts:null, player:null, subsEl:null, onPhrase:null, onEnd:null}; function stopSpeak(){ if("speechSynthesis" in window) window.speechSynthesis.cancel(); if(speakState.timer){ clearTimeout(speakState.timer); speakState.timer=null; } speakState.playing=false; - if(speakState.btn){ speakState.btn.classList.remove("is-playing"); speakState.btn.textContent="▶ Прослушать"; } + if(speakState.btn){ speakState.btn.classList.remove("is-playing"); speakState.btn.textContent="▶ Прослушать жалобы"; } if(speakState.avatar) speakState.avatar.classList.remove("is-speaking"); - speakState.queue=[]; speakState.idx=0; + if(speakState.player) speakState.player.classList.remove("is-speaking"); + if(speakState.onEnd){ const cb=speakState.onEnd; speakState.onEnd=null; cb(); } + speakState.queue=[]; speakState.idx=0; speakState.player=null; speakState.subsEl=null; speakState.onPhrase=null; } function splitPhrases(text){ return text.split(/(?<=[.!?…])\s+|(?<=…)\s*/).map(s=>s.trim()).filter(Boolean); } -function speakPatient(text, opts, btn, avatar){ +function speakPatient(text, opts, btn, avatar, ext){ const synth=window.speechSynthesis; if(!synth){ return; } if(speakState.playing){ stopSpeak(); return; } @@ -492,9 +494,14 @@ function speakPatient(text, opts, btn, avatar){ const phrases=splitPhrases(text); if(!phrases.length) return; speakState.queue=phrases; speakState.idx=0; speakState.btn=btn; speakState.avatar=avatar; speakState.opts=opts; + speakState.player=ext&&ext.player||null; + speakState.subsEl=ext&&ext.subsEl||null; + speakState.onPhrase=ext&&ext.onPhrase||null; + speakState.onEnd=ext&&ext.onEnd||null; speakState.playing=true; btn.classList.add("is-playing"); btn.textContent="⏹ Остановить"; if(avatar) avatar.classList.add("is-speaking"); + if(speakState.player) speakState.player.classList.add("is-speaking"); playNext(); } @@ -502,6 +509,8 @@ function playNext(){ const synth=window.speechSynthesis; if(!speakState.playing || speakState.idx>=speakState.queue.length){ stopSpeak(); return; } const phrase=speakState.queue[speakState.idx]; + if(speakState.subsEl) speakState.subsEl.textContent=phrase; + if(speakState.onPhrase) speakState.onPhrase(phrase, speakState.idx); const opts=speakState.opts||{}; const u=new SpeechSynthesisUtterance(phrase); u.lang="ru-RU"; @@ -869,10 +878,18 @@ function patientBlock(c){