From 5e50fd567983562b945d235b669cbdc3c4039bcc Mon Sep 17 00:00:00 2001 From: AR 15 M4 Date: Thu, 23 Apr 2026 08:28:00 +0500 Subject: [PATCH 1/3] Add priorities meeting doc (23 Apr 2026) with PPTX deck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MEETING_2026-04-23.md — product roadmap proposal: gentleman's set (Phase 1 transactional base + Phase 1.5 communication overlay), 10 business segments matrix (primary/returning lens), four segment groups A/C/B/D. build_deck.py — python-pptx builder generating 18-slide 16:9 deck styled to the clinic's design system (teal, PT Sans). Also expands README with design system tokens and screens overview. Co-Authored-By: Claude Opus 4.7 (1M context) --- MEETING_2026-04-23.md | 285 +++++++++++++ MEETING_2026-04-23.pptx | Bin 0 -> 71785 bytes README.md | 26 ++ build_deck.py | 913 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1224 insertions(+) create mode 100644 MEETING_2026-04-23.md create mode 100644 MEETING_2026-04-23.pptx create mode 100644 build_deck.py diff --git a/MEETING_2026-04-23.md b/MEETING_2026-04-23.md new file mode 100644 index 0000000..9a8e670 --- /dev/null +++ b/MEETING_2026-04-23.md @@ -0,0 +1,285 @@ +# Приоритеты развития мобильного приложения — встреча 23 апр 2026 + +Клиника УГН (ЛОР, сурдология, аллергология, хирургия). Цель встречи — определить порядок развития функций: что закрываем как базу для всех пациентов, какие сегменты углубляем и в каком порядке. + +## 1. Контекст и критерии приоритизации + +**Текущие функции:** список врачей, запись на приём, ближайший приём, чат с оператором, профиль, семейный профиль, контакты. + +### Ключевые факты о потоке + +- **2 из 3 пациентов клиники — повторные.** Значительная доля — **повторные внутри одного лечения**: после первого визита (острое заболевание или обострение хронического) ещё 1–3+ приёма, процедуры, контроль. +- **Бизнес-сегментация (из аналитики клиники)** — 10 сегментов, лидеры по выручке: амбулаторный поток (~120 млн), взрослая хирургия «заблокированный нос» (~30 млн), детская аденоидная хирургия (~20–30 млн), сурдология (~20 млн). + +### Продуктовая логика: что делает приложение + +**Приложение — инструмент удержания и углубления, а не привлечения.** Первичного пациента клиника получает через сайт, SEO, рекламу, сарафан. Приложение устанавливают уже пришедшие — в момент записи или на первом визите. Поэтому приоритет развития — **повторные во всех сегментах**. + +Это совпадает с фактом «2 из 3 — повторные» и означает, что: +- **Первичные сценарии в приложении** закрываются универсальным минимумом (запись + контакты + цены + AI-помощник, который знает услуги). Нет смысла строить специализированные первичные потоки для каждого из 10 сегментов. +- **Повторные сценарии** — специфичны по сегменту и требуют отдельных модулей (бегунок, аудиограмма, АСИТ-дневник и т.п.). +- **Исключения** — сегменты, где «первичный в приложении» ≠ «первый визит»: после первого визита пациент уходит, и вернуть его в клинику может только приложение. + - **Сурдология** — после визита и демо пациент уходит думать 2–3 месяца о покупке аппарата. + - **Хирургия (FESS, детские аденоиды, вазотомия)** — после пред-операционного приёма у хирурга пациент часто уходит **думать, решаться ли на операцию вообще**. Эти «сомневающиеся» — отдельная работа по возврату. Вторая часть — те, кто решился, уходят в 6-недельную подготовку (бегунок). + - **АСИТ** — после назначения впереди 3–5-летний курс, есть сценарии «не начал» и «бросил в первые месяцы». + +### Три слоя работы + +1. **Фаза 1 · Транзакционная база** — детерминированные функции для любого пациента: запись, ближайший приём, чат с оператором, **план лечения, результаты/медкарта, заказ справок**. Без LLM и прямого чата с врачом. Минимум рисков, быстрый запуск. +2. **Фаза 1.5 · Коммуникационная надстройка** — чат с медицинским консьержем (дежурным врачом/фельдшером) и AI-помощник в shadow-mode. Отделено от Фазы 1, чтобы регламенты и безопасность не задерживали релиз базы. +3. **Фаза 2 · Сегментные модули** — углубление по приоритетным группам сегментов (A → C → B → D). + +### Критерии приоритета + +| Критерий | Что меряем | +|---|---| +| **Охват** | Сколько пациентов клиники затронуто (в абсолюте) | +| **Глубина пользы** | Насколько закрывает реальную боль (есть ли альтернатива без нас) | +| **Частота касаний** | Как часто функция возвращает пользователя в приложение | +| **Бизнес-эффект** | Влияние на возвратность, средний чек, удержание, вклад в выручку | +| **Сложность** | Вторичная ось: усилия (вкл. зависимости от МИС и контента) | + +--- + +## 2. Текущие функции — утилита и приоритет + +### 🟢 Высокий приоритет (ядро ценности) + +**Запись на приём** — главный транзакционный поток. Экономит 3–8 минут разговора, 24/7, ближайшие окна. Единственная функция, которая приводит новых пациентов через приложение. Усилить: запись из плана лечения в 1 клик (§3), запись из маршрутной карты пред-опа (§4). + +**Ближайший приём** — «что со мной сейчас». Снимает тревогу «когда, куда, к кому». Перед визитом открывается 3–10 раз. Усилить: чек-лист подготовки, маршрут до кабинета, тригеры 24ч/2ч/15мин. + +### 🟡 Средний приоритет (поддержка) + +**Список врачей / карточка врача** — инструмент выбора при первом визите. Повторный пациент почти не открывает. Усилить: соцдоказательства, фильтр «по моему диагнозу». + +**Чат с оператором** — замена звонка в регистратуру. Ограничен одним собеседником. Основное развитие — в §3. + +**Профиль** — точка идентификации. Редко открывается, но критичен для персонализации. + +### 🔴 Низкий приоритет (ниша) + +**Семейный профиль** — высокая польза для 20–30% (родители с детьми, взрослые с пожилыми), становится критичным для сегмента «дети с аденоидами» и «сложные хроники — ЧБД». + +**Контакты** — функция «галочка», 1–2 открытия за всё время. + +### Вывод + +Ядро «Запись + Ближайший приём» работает. Слабое место — **нет причины открывать приложение между визитами**. Это и закрывает джентльменский набор. + +--- + +## 3. Джентльменский набор — базовые функции для всех пациентов + +Минимум, который должен быть у любого пациента клиники, вне зависимости от диагноза и сегмента. **Приоритет до любых сегментных модулей.** + +Набор сознательно разделён на две подфазы по риску и зависимостям: **транзакционная база** (детерминированные функции с понятным MVP) и **коммуникационная надстройка** (LLM и асинхронные каналы, требующие регламентов и безопасности). Это позволяет выпустить базу быстро и безопасно, а надстройку строить поверх уже стабильного фундамента. + +### Фаза 1 · Транзакционная база + +Функции, где пациент **что-то получает или делает** — без LLM и без диалога. Минимизируют риски, быстрее в запуск, дают те самые «не удалю это приложение»-причины. + +| Функция | Статус | Что в ней | +|---|---|---| +| Запись на приём | ✅ есть | Запись 24/7, ближайшие окна. | +| Ближайший приём | ✅ есть | Что, где, когда — снимает тревогу перед визитом. | +| Чат с оператором | ✅ есть | Справки, переносы, счета, расписание (человеческий канал). | +| Статьи / база знаний | ✅ есть (Спринт 1) | Уже есть. Источник правды для будущего RAG. | +| **План лечения с приёма** | ❌ нет — фундамент | После каждого приёма структурированный чеклист: диагноз, назначения (препарат + доза + курс + календарь), ссылки на процедуры самопомощи (Группа A), контрольный приём, запись на следующий визит в 1 клик. Живой объект с напоминаниями. Источник правды для всех следующих надстроек. | +| **Результаты обследований и медкарта** | ❌ нет | Пациент видит свои анализы, аудиограммы, снимки, заключения **без звонка в клинику**. Статус каждого результата: готов / в работе / годен до [дата]. Критично для Группы C (пред-оп): срок годности анализов виден в одном месте. | +| **Заказ справок и финансовых документов** | ❌ нет | Справка для налогового вычета (13%), справки работодателю, копии заключений, счета. Заказ в 1 клик, готовая в приложении или с самовывозом. Снимает нагрузку с администраторов и даёт сильный retention-якорь даже у разовых пациентов. | + +### Фаза 1.5 · Коммуникационная надстройка + +Функции, где идёт **живой или AI-диалог** по поводу лечения. Строятся поверх уже работающей базы (плана лечения, медкарты, статей) и требуют отдельных регламентов — по ответственности, SLA, проверке качества. + +| Функция | Статус | Что в ней | Ключевой риск | +|---|---|---|---| +| **Чат с медицинским консьержем** | ❌ нет | **Не прямой чат с лечащим врачом.** Дежурный врач / фельдшер / медсестра отвечает по протоколам на 80% рутины («можно ли совмещать с X», «нормально ли, что третий день болит», «когда повторно сдать анализ»), эскалирует клинически значимое лечащему. Асинхронно, SLA — X часов в рабочее время. | Перегрузка врачей и SLA-хаос, если пустить пациентов напрямую к хирургу. Консьерж-слой — основной буфер. Нужен регламент тарификации/компенсации врачам за эскалированные вопросы. | +| **AI-помощник (RAG 24/7)** | ❌ нет | Знает базу знаний клиники, статьи, план лечения пациента, историю приёмов. Объясняет назначение, ищет ответ в статьях, оценивает «норма или срочно» и эскалирует к консьержу/врачу при тревожных признаках. | Галлюцинация LLM в медицинском контексте = юридический и репутационный риск. **Запускаем в shadow-mode**: ответы сначала идут через консьержа для валидации, метрики точности собираются, и только после набора статистики — переход в прямой режим для безопасных категорий вопросов. | + +### Почему это ядро, а не сегмент + +- **Работает на 100% пациентов** — без разделения по сегменту. +- **Усиливает любое сегментное направление**: бегунок, слухопротезирование, АСИТ — всё опирается на план лечения, медкарту и тот же чат-канал. +- **Закрывает главный пробел транзакционной модели** — «что делать между визитами». +- **Частично закрывает самый массовый сегмент** (амбулаторный + хроники). Полностью закрыть их может только отдельный модуль «Процедуры самопомощи» (Группа A, §4.3) — это первый сегментный модуль после Фазы 1.5. + +### Порядок работ внутри набора + +**Фаза 1:** +1. **План лечения с приёма** — фундамент. Без структурированных назначений не работает ничего поверх (ни AI, ни напоминания, ни процедуры самопомощи). +2. **Результаты / медкарта** — следующий retention-якорь. +3. **Заказ справок** — быстрый win для большой части пациентов, снимает нагрузку с администраторов. + +**Фаза 1.5** (после стабилизации Фазы 1): +4. **Чат с медицинским консьержем** — инфраструктура канала + регламент ответов. +5. **AI-помощник на RAG (shadow-mode)** — сначала ответы валидируются консьержем, потом постепенный переход в прямой режим для безопасных категорий. + +### Критическая зависимость от МИС + +«План лечения» — это структурированные данные (препарат, доза, частота, курс, процедура). Два сценария: +- **МИС отдаёт назначения по API структурированно** — план лечения собирается автоматически из данных приёма. Предпочтительно. +- **МИС отдаёт только PDF-заключение** — план лечения MVP стартует с **ручного ввода врачом в виджет** (шаблоны по нозологиям + чеклисты + автоподстановка из предыдущего приёма). Интеграция с МИС — отдельной задачей позже. + +Ответ на этот вопрос определяет сроки и стоимость Фазы 1. Добавлен в §6 как первоочередной. + +--- + +## 4. Сегменты пациентов через призму приложения + +### 4.1. Фундаментальное деление: первичный vs повторный + +| Линза | Первичный в приложении | Повторный в приложении | +|---|---|---| +| **Главный job** | «Куда мне обратиться и как записаться» | «Что мне делать сейчас по моему лечению» | +| **Источник** | Попадает через сайт/рекламу/сарафан | Приложение уже установлено, уведомление/сам открывает | +| **Частота открытия** | 1–3 раза до визита | Ежедневно в активном эпизоде | +| **Что нужно в приложении** | Базовый минимум (запись, цены, контакты, врачи) | Специализированный модуль сегмента | +| **Приоритет развития** | Низкий (веб и реклама работают лучше) | ● Высокий (2/3 потока) | + +**Ключевое следствие.** Специализированных «первичных» модулей под каждый сегмент строить не нужно. Исключения — сегменты, где «после первого визита» начинается длинный путь: сурдология, хирургия, АСИТ. Там «первичный» в терминах клиники ≠ «первичный» в терминах приложения. + +### 4.2. Матрица 10 сегментов × первичный / повторный + +Из 10 сегментов бизнес-аналитики выделяем: объём × чек × что нужно в приложении для первичного × что нужно для повторного × итоговый приоритет приложения. + +| # | Сегмент (из аналитики) | Объём / Вклад | Первичный в приложении | Повторный в приложении (главная работа) | Приоритет в App | +|---|---|---|---|---|:-:| +| 1 | **Взрослые «заблокированный нос»** (полипы, перегородка, FESS) | 300 оп/год · 100т · 30 млн | Джентльменский + материалы о методах (FESS/лазер), кейсы пациентов, калькулятор, **возвратные push для сомневающихся после пред-оп приёма** | Пред-оп бегунок (6 нед) → восстановление → контроль 3/6/12 мес | ● **Высокий** (Группа C) | +| 2 | **Амбулаторный поток** (острые и хроники ЛОР) | Тысячи/мес · 120 млн | Джентльменский | План лечения + эпизод лечения + дневник симптомов (§3) **+ модуль процедур самопомощи: библиотека техник (промывание носа физраствором, полоскание горла, ингаляции), напоминания, трекер комплаенса** | ● **Высший** (Группа A — отдельный модуль) | +| 3 | **Родители детей с аденоидами** | 400–500 оп/год · 60т · 20–30 млн | Джентльменский + семейный профиль + подготовка ребёнка + **материалы «нужна ли операция» и возврат сомневающихся родителей** | Пред-оп бегунок (детская версия) → восстановление. Далее — редко. | ● **Высокий** (Группа C) | +| 4 | **Потеря слуха (сурдология)** | 20 млн/год | **Нестандартный первичный:** аудиограмма в профиле, аудио-демо, каталог моделей, калькулятор, шеринг близкому, возвратные push | Паспорт аппарата, сервисный календарь, расходники, калибровка раз в год, дневник адаптации | ● **Высокий** (Группа B — уникальный модуль) | +| 5 | **Сложные хроники** (иммунология/аллергология, ЧБД) | Высокий, часть 120 млн · длинный LTV | Джентльменский + семейный (ЧБД) + запись на консилиум | Модуль процедур самопомощи (Группа A): промывания, полоскания, ингаляции у ЧБД. Плюс специализация: **АСИТ-трекер** (дневник симптомов + пыльцевой календарь + навигатор побочки) | ● **Высокий** (Группа A + Группа D) | +| 6 | **Зависимость от капель (вазотомия)** | Высокий объём · входной в хирургию | Джентльменский + материалы «как слезть с капель» + **возврат сомневающихся после пред-оп приёма** | Пред-оп бегунок (компактная версия) → восстановление | ● **Высокий** (Группа C) | +| 7 | **Пульмонология** (кашель/астма) | Средний · сезонный | Джентльменский | Дневник астмы, напоминания об ингаляторах, контроль триггеров (пересекается с АСИТ) | ◐ **Средний** (частично Группа D) | +| 8 | **Социально активные храпуны** | Низкий · высокая платёжеспособность | Джентльменский + образовательный контент о сомнологии | Сомнологический чек-up, СИПАП-трекер (если назначен) | ○ **Низкий** | +| 9 | **Фониатрия** (голос) | Очень низкий · срочный | Джентльменский + срочная запись | Короткое окно, низкая повторность | ○ **Низкий** | +| 10 | **Check-up и Второе мнение** | Единичный · высокая маржа | Джентльменский + позиционирование бренда Оленевой | Разовая услуга, минимальная повторность | ○ **Низкий** | + +### 4.3. Четыре группы сегментов для повторных + +**Группа A. Амбулаторный поток + хроники — процедуры самопомощи** — сегменты 2 + 5 (повторная часть). Самый массовый сегмент × ежедневная повторность. + +Джентльменский набор (план лечения + чат с врачом + AI) закрывает коммуникацию и напоминания, **но не закрывает саму регулярную работу пациента между приёмами**: +- Промывание носа физраствором (при хр. рините, синусите, после операций) +- Полоскание горла (при хр. тонзиллите) +- Ингаляции +- Гимнастика слуховой трубы (при евстахиите) +- Голосовые упражнения (фониатрия) +- Туалет уха (при хр. отите) + +**Что нужно в модуле:** +- **Библиотека техник** — короткие видео и пошаговые инструкции «как правильно». Ошибки в технике (сильный напор при промывании, не тот раствор) — частая причина, почему «не помогает». +- **Напоминания** — утро/вечер по схеме, привязаны к плану лечения. +- **Трекер комплаенса** — ежедневные отметки, стрики, сводка «сколько дней подряд». +- **Дневник симптомов в связке** — «промываю 5 дней, насморк уменьшился с 4 до 2» — главный мотиватор продолжать. +- **Сводка для врача** — перед контрольным приёмом врач видит, что пациент делал, как часто, с какой динамикой симптомов. + +**Это отдельный модуль, не продолжение Фазы 1.** Он опирается на план лечения как на источник назначений, но требует собственного контента (библиотека техник) и UX (трекер). + +**Группа B. Сурдология** — сегмент 4. Уникальный набор функций (аудиограмма, аудио-демо, каталог, паспорт аппарата). Пожизненная повторность. Высокий чек. **Отдельный модуль, не пересекается с другими.** + +**Группа C. Пред-операционная подготовка + восстановление** — сегменты 1 + 3 + 6 (одна механика для трёх сегментов). **Две фазы, как у Группы B:** +- **Кандидаты на операцию** — после пред-оп приёма ушли «думать». Возврат через материалы, кейсы пациентов, чат с хирургом, возвратные push-триггеры (3/7/21 день), прозрачность по цене/рассрочке. +- **Решившиеся** — бегунок → чек-лист дня операции → операция → восстановление. Окно 6–12 недель. + +**Один модуль закрывает три сегмента бизнес-аналитики** (FESS, детские аденоиды, вазотомия). + +**Группа D. АСИТ + контроль астмы** — часть сегмента 5 + сегмент 7. Ежедневный трекер, дневник симптомов, навигатор побочки. **Самый сложный, требует верификации врачом.** + +### 4.4. Что остаётся за кадром + +Сегменты 8 (храпуны), 9 (фониатрия), 10 (check-up) — низкая повторность и/или низкий охват. Закрываются базовым джентльменским набором, специализированных модулей в ближайшем горизонте не требуют. + +--- + +## 5. Порядок внедрения + +### Фаза 1. Транзакционная база (§3) + +План лечения → Результаты / медкарта → Заказ справок. + +**Эффект:** пациент видит свои назначения, результаты и может получить документы без звонка в клинику. Это и есть базовые retention-якоря: «не удалю это приложение». Массовый амбулаторный + хроники покрыты в части информирования и документов. + +**Критический параметр сроков** — структурированность назначений в МИС (см. §6 вопрос 1). От этого зависит, собирается ли план лечения автоматически или врач заполняет его вручную в виджете. + +### Фаза 1.5. Коммуникационная надстройка (§3) + +Чат с медицинским консьержем → AI-помощник (shadow-mode). + +**Зачем отдельная подфаза:** эти функции несут организационные и юридические риски (SLA, выгорание врачей, галлюцинации LLM) и могут надолго задержать релиз, если класть в Фазу 1. Выделение в 1.5 позволяет: собрать транзакционную базу быстро и безопасно, отработать регламент консьержа отдельно, запустить AI сначала под валидацией человека. + +### Фаза 2. Сегментные модули — порядок + +Рекомендуемая последовательность: + +**1. Группа A (Процедуры самопомощи хроников) — первым** +- **Самый массовый охват** — тысячи пациентов в месяц (сегмент 2 + 5). +- Напрямую опирается на план лечения из Фазы 1 — продолжение той же инфраструктуры. +- Средняя сложность: контент (видео-инструкции, 10–15 техник) + простой трекер + дневник симптомов. +- Бизнес-эффект: рост комплаенса → меньше обострений → меньше экстренных приёмов и операций, на которые хроники срываются, когда дома не помогает. +- Эффект заметен пациенту сразу — ежедневная польза. + +**2. Группа C (Пред-операционная подготовка) — вторым** +- Закрывает 3 сегмента (1, 3, 6) одним модулем. +- Быстрый MVP — маршрутная карта с чекбоксами, сроки годности, автозапись. Восстановление уже есть в прототипе. +- Прямой измеримый бизнес-эффект: снижение переносов операций из-за просроченных анализов + возврат сомневающихся кандидатов на операцию. +- Вклад сегментов в выручку: ~50–60 млн (хирургия), плюс косвенно вазотомия как конвертер в большую хирургию. + +**3. Группа B (Сурдология) — третьим** +- Изолированный сегмент с уникальными функциями. +- Пожизненное удержание × высокий чек × растущий рынок (старение). +- Две половины: возврат кандидатов после демо (высокая конверсия в деньги) + обслуживание после покупки (лояльность и LTV). +- Можно запускать параллельно с C после Фазы 1, если есть ресурс. + +**4. Группа D (АСИТ + астма) — четвёртым** +- Самая высокая глубина пользы (влияет на медисход), но самая высокая ответственность. +- Требует верификации контента аллергологом/пульмонологом клиники. +- Можно начать готовить контент и интеграции параллельно с A/C/B, выпустить позже. + +### Что не берём в план сейчас + +- Храпуны (8), фониатрия (9), check-up (10) — специализированных модулей в горизонте планирования не делаем. +- Отдельные «первичные» модули под сегменты — не делаем. Первичный путь = джентльменский набор + базовые функции (запись, врачи, цены, контакты), которые уже есть. + +### Что нужно сделать вне Фаз, уже сейчас + +- **Добавить аллерголога-иммунолога в список врачей** (есть специализация, нет конкретного врача в данных). Предусловие для Группы D. + +--- + +## 6. Вопросы к обсуждению на встрече + +**Первоочередной (определяет сроки Фазы 1):** + +1. **МИС и структурированные назначения.** Отдаёт ли МИС по API назначения структурированно (препарат, доза, частота, курс) или только PDF-заключением? От этого зависит: план лечения собирается автоматически или врач заполняет вручную в виджете. Там же: API для результатов анализов, сроков годности, расписания. + +**По процессам клиники:** + +2. **Медицинский консьерж — кто в роли?** Дежурный врач / фельдшер / медсестра? Кто держит SLA? Как компенсируется эскалация вопроса лечащему врачу? +3. **SLA чата** — целевое время ответа в рабочее время (1ч / 4ч / день)? +4. **Справки и финдокументы** — текущий процесс заказа через администратора; что готовы автоматизировать в первую очередь (ФНС-справка, выписки, счета)? + +**По метрикам (для оценки эффекта):** + +5. Подтвердить **«2 из 3 — повторные»** — уникальные пациенты в год или визиты? Меняет оценку охвата Группы A. +6. **Процент повторных, не доходящих до контроля** — метрика успеха Фазы 1. +7. **Сурдология** — кандидатов/мес, % возврата за аппаратом. От этого зависит приоритет Группы B. +8. **Переносы операций** — частая причина просроченные анализы? Усиливает Группу C. + +**По ресурсам Фазы 1.5 и 2:** + +9. Готов ли **аллерголог** верифицировать контент для АСИТ-трекера? Без этого D не запускаем. +10. База знаний / статьи — достаточно ли материала для RAG или формировать отдельно? +11. Политика по **AI shadow-mode**: кто утверждает категории вопросов для постепенного перевода в прямой режим? + +--- + +## Приложение. Короткая сводка для слайда + +**Что предлагаем:** +1. **Фаза 1** — транзакционная база для всех: план лечения, результаты/медкарта, заказ справок + уже существующие запись, ближайший приём, чат с оператором, статьи. Быстрый MVP, минимум рисков. +2. **Фаза 1.5** — коммуникационная надстройка: чат с медицинским консьержем (не прямой с врачом) и AI-помощник в shadow-mode. Запускается после стабилизации базы. +3. **Фаза 2** — четыре сегментных модуля в порядке: A (процедуры самопомощи хроников — массовый) → C (пред-оп подготовка — 3 сегмента разом) → B (сурдология — высокий чек) → D (АСИТ + астма — с верификацией врачом). +4. **Первичные в приложении** = базовый минимум, который уже есть. Новых первичных модулей не строим — они приходят через веб. Исключения — сегменты, где после первого визита пациент «уходит думать»: сурдология, хирургия, АСИТ. diff --git a/MEETING_2026-04-23.pptx b/MEETING_2026-04-23.pptx new file mode 100644 index 0000000000000000000000000000000000000000..bdd1e55efbdc58ada3aed0753180ea9507d2288c GIT binary patch literal 71785 zcmdqIV{~TSwzeBPso1uYif!ArZQGS6thi#^M#Z*m+eRfhdEd3abJkjCx3<65_P_OG z&i2gl>zRG@e%<%zV@w5UP%u;=ARs89qw-{(%jz4E4`3i5Q#2qTl&?{3VS76lQ#%)Z z6;B6KXI**^TbqerBX(ZjaYT0uwbLnrHF(_0qHMq3+|=GW)gv`-GRMux}D^1=q^rA4U< zT?6hnLTMzW1(hzb+lz7fGI%Xs$9n%{l`GJ>Hs3hJfC3B))|3(xR|d9)G<;-%V>ueQ zV{vTm{H94<@&eBzxIZ&#B8*g`Xv}GuA{esOP8cW~1mIanq~o{S19cLVcQB2w-exAHf%WQGXdZXdf@BT5jYlt;ko$cR&A`WMs z3qnLL5y}We3qdsJ11IXo5*LJWERshQ$}mLv8fpUj8X_Zt>U-M1rsMPxN7(w=_q2C? z!QsO`=ox)Ra{WiGw^3i(9`gYHR!y?sczgdmAL1BvF24x28;{aM&=tFqfJ2~E!e%Bjln;$$ zqNWmQU>XU~J^eb!>qz6@1Jaq?-&8~gN3N^#GHX+(PRHln=Ryus(j3|k9Ak2g=x!JM zVYSH8O#N=EasjMNTUj+bD@RkiijvhAbJocGj5x7qyd8)Bi5v=7D!hn>L?iWU>`=Xp zeqSmYc488Pi?8B#*dS2QvFo84c>UViINwvMHpI{BA*_a*+{IUu2n9v1XM$FD+NPhb{$(Cj{OFLsp6uSdZ$Eg$LNKtQC2BXB5c3oCIpC zF~3>zq~Cj~C-tCLBxsou;SCuAMOYK5#)VFey;1S*`4E!M$*{Qg!wgqe~1-%utgT`l8?x>dObBTE)&lW+F?1d4g@remC6{MOkoZ((H{dPW8uqis1&K*Asp zXbMHV%;6v1&C;?+%>?02^>Sb^n)#D&#Z&4RPx) ze-eBkUBAnn(5C3Cm_IKBcmI~qS<^9|S6}mF3kC#)_|NlX=-}|zG-ayv#LO`wc97hB zOO|)ANDvBDI4}d(t@O%aQK}#Ftf7bwH5P9=v3v0%haM?XAQF_jn&9ccnp#F;mv1zr zxEJL9eHiIEiludu*fhl2#c*kM#R-d40kTJ1^@0W3HKNyv*y;}dTJoK-uEubp95Y=t z9!%2^-cZg`I(7C1n^@6bMn}^M&@AUyJu|p3o7O3PO_irwVm4th>^-zV^-*2wRCmF^ zgL)M9he@#os%V zo&U&+I@B_P>xBF^>+-O-raYHT8ltb-bnmeDK3>LXkp{zB$NhG7?`AnS9*qkN=0~oG zSBD4_e!}$u&@1v=yc%-#xbgQ)@g>k~m)0wK^51;nxtsQAZU#Hywnid2>g)znsMs?U zwq2obMGnn~j?25I3b@qQCC8dk+FyT28s1y@HFn zjo7oLmj!Ie)|Sh@DI-C*rwiMbt7VoqZj~N>M4ZK;uJw66l?m{(2$ka+qSj>EU8b*+ zm#V5BXSNP2h9nRE*geoMVj1Q?$e+!=1XE2V3EE?sEMl2dAUt}EA;JtD6MYtaeRwF9 z5XwBCH`Cvn?@5i_*kmS{TAv|~of|$_K3YELKk7eF&X(83f8%lU+`a2Rm_MrGIbX}K z;ypjhSNG4nif#J{zhZEGN7~XqeI@Dantu@Y@h?4#?eE)ka-V%Ap+qp_N8#ynF^fVQ z!kwWV$s3L;j-}g|KtmSj|Irz0!BWC^g={%z#u0Oo7fEf{=cdEoCs_Ru*XPu8-mxI{ zs4h=~Pw zdQ$YQh2Vv{>}=I@t){o-O*0aw%9b=8^bIT^Qy`ZwPzatYt0LxlH(y;Cg4G;4pMdE( z6~Rw#&FS7be)bIj$?uV7#0A5peF9DvlM`jKLV0Xch#>+g7-GVL4W3zfAEYF%fGt17 z{AyH)kq94Dk$Lm8Q{x1D0#}ela!B}V)H~@x|9jt={LF#>ziQFYz!65&7vuEcfq)49 zD~%9d3hA?lB-CsZQ#R;{SVPm9&`@5ciM6&s+-`z%Rh*z>MuYw}IA zMY-JPLhbh@{QL;7fp-V$%HojIVw9;AEEDliCb?2XlbQ0|K%56J~@UEg-dhK^GP-Au9XEvfd z9FwprM|l>@;|FIBmwWx|7VIcsJX8k6L1#BPf_`ZwUXMqEUl_Gkbsstwdo%_W(eslx z*Zz{PP=h@|&#D@!f;>+!FmBy z;l$o9Q+wqo1(<3jQOZHeP<2OQRFuTta!#ZODybdSOsE04!fR^zaZRiUB$><-Zc9o} zrTHwDL_VrhdGyh2R5VIhGKC22#84(mQM6GZs>D15(_CDsDP%JBt-JyDf1OC33~^ME zuZaZwm#O$aJo?*N`~{=N__4oCh3J#SAL0EDYi`Lqr37lrppgC`aG!uAj>^JkqBUQ_ z{w@?D7*w{6n)I3DO-^@~@&`${%|NWNq;X(PgYEAtWiey62wB;e1|U3VCFjREq+>m> z^#;1@n-wla@bxlWr(!4hH)14~%xS@y&RoL7P2A)qx|9?99A`;L^U;+nd6Uz7Edp@E zLmWauPS9o@tzy-$S*-Fd0GaM(g;<>ZB-ouIa@_Pg| zi)k+W7A^13K(TZ+5u@P~CG+T93Pj-S2127@>Ct6c1g3J_PhpfxKn@Gc?yW1c)=)LA ziy|gqfc`Kt;obOVXH>!Z$-r~(D|`Ru9pV13Avb3xk52yzc@XG7eB56lce6Bg|9?Xs zt=uEG_hk^O`}s9GgA<^H0BKHw4uV<*VI4T;Y1o`i2aeKXOIM_Jmax1I>Fj%+e)fY| zoAzFPi--!`+IN4b_uwj=Dw}jC^wWpNpv^H0Z3`XItvbQ?pXwv|mGvuGecHjqz|d(l z?@XgQkwF%ay#VTy!G){6p?YFLxhS?j2}IU$6iqa#yh`b|9)4`y{~i#c@o=zO2#yAj z2E@hn?)hPzN{K^q6K90Sh&DKmK+wzhUX@AI?4++vlhdXJwILGsfe`M zsT8`IFENcLvD8fM0huXl?u5b9&{uJir19XGDtLL5({;Z390Xl9n5L)cAF_M)thj|! zN*v7#IyMu7PGNQ#iFL3Tt%kv7XLyQRMYgX`kmE^?jF|Xe1&ZKK5n6I({&YpE36$l* zUY+6S!lE!gipnmU!kH4ATX7%Stq6%(WO-7Wkv5M=vp@Nc7`S@b*B{?Gbq-Ox1ayS=|B1Pag{iG6!{7Jse~Y=Mto<4rX6LtMe?k{~(-pN-FxQCX+O9zg@b>wSQECzG6I{_A!~x0p`rK0~aCF z3$mWxf(v{eRvr7NiIC!NTWmQ~tkH>UhkUUiZO<-q{7X%VN*&IP2aRW&$VIf4*!cf! zQ*SB|0iZx$HSD&V(5RDamYePku*SxCh`#gSI3gK>Drx zPK(B4q1||Hyppbd5g%Sk)_|?c|Ay)U9_X(ybu7Y23!Ulb{66Y7(v@|m-~Zs%VK+tf zVhVKbX;4}pVEd>oXFjS7deA4jxsj%At#*U`ISAs{F?Z5(hUQ}&W>3GZ-Pm}j))8bs zd3~Apky&ZyUK!+W{@e)e^EvveRUa`WK$L&e1Hu(;j+Khcw0ja}WFz9D@d%gjI4F$~)l9NbE;Y5y&+}&uW6tNCK>MDJ?0lAj(1$pB$q~ zKiuuG^!Vzr#Qgi=WB!XGOpRbSO~n`Sy+B~zl5@#Afh%8VtazI49K0s9FIPV zZ9~TNL=ST)P_qT|tc5q~n{3^0=bMXY6bW-HqZN2b(Ft7$q9I@aKrz49VMH8eqqrovbcPI+OT{b?hb5>*Mg~Txou+f8^#@l*>au-8A2o6r2s4UwFST8J z%n*#YShQom%RXI&P(u70%8n{7J(>s?VN`yWd;!bMI#NPJ8+)$zufEPcaz!*MR9;3R zd&5cBeK!8%Fb=6Gt~rkRT7nT!3B@S4RwnA6a`U*&lDmaRUF?uUFv;ww z&{9&U>czc~XhuLJxxVERQuNl-Ws>((a?apx>ecd?!<}#2Sg8Eg2U|z<&<%aysK-{} zmivi&tu#aVQRLM`y_?P1kIN|8hI=r+yr_$EXp&}=#bL5vM^A>|(@jJU0{Nj0HNwlc zK?3W)BkTAIskRX8g7tHxh6tcnfC7-|chU^$4l*}Xsg^S;TjyZOb81(2{5sdC8M4UI zZO|oj6-Z2L%4!Ca*ijDmyU_|ct2EQR%UHDU$mijEPprYsE3+cQM0%p0OShaM=4$># z5;<9EMG2jw^wpVV&poTRpJYh`A|0J8#wtPV1bm)42TEm7ade73F0ygPh{z)Tyl z(qhq?{45FOCD3BRMv|w~OqGo^_o)b9{`OA7Vq#qTL-5KhOP4Qc`s8d55U_5C4p66C z&tp5(-$G>#)fPsNo z$hq3oqCoV6J~3qe40id6Su+PCs7^vvh}IV?^Hd7K+?VDkj=hyo#hXPSNT^sjm(WBV z%`K~Ey!32sFHv#ET*}on{u5z@$S}CUYf0&i4pr{Ai zI2h|x{R?YjQME1jnSD_&HBJqAm0eTICH;oyVe=}O9Tw$#(4cJSklD~2@K*3~zEigN z&+rs2(U+u1;px^wtdzRfYB|A5vYXD~`uxN5L1go6`pbvR3v8c@tV=8JS`pg|{P+p< zkHpR~Xm7Xx(@h;Oa@RADlQbVTm`4Tbd{u~CQ*vBid&cNRC*@~x>? z2Ovso(!GN#A>e*ZfW-cXdVqAq30^hnq@5!TO} z@eC9EQh6yF=O!$mpWDF5ZLAvwkPxxn!NvlMjvJ1a4F)wZu%1m@g*=Y(p3dc{>SxnM z7U>QU`OeNl;kM$rExf-pk1rm~6xVHA;R>{$z2}lh1CQ_ky9p5UfUF+&vLM2|hVJ6A zgK$d;P#rIpK}VIvh1Sw{YFa;mx_=YOBDy@*%P*mvr3V5c`meWgwy`uZl{IvBF?Dig z_@{?|sHWDH-5wkAr=L90G2eb)4-J6$VMwDmJk!-a&;^I(1aunN_Do2cD?%=T7j@bF z80VgD_027|PTomas=;IkfKoA6bp=ODYsG;*Ic{$zA@_K}?hb3W!K7#^%ExiD*+PLn zmOwmlyRyAMbmqvwRZA^-mv~ET9-QEBuVlY)m`xc{2 z=G>ICke+A4IW7xZn}e)3FfIxH4lTma60F1SfX*^@ap--MaTR&OBE9SxM~gw5(uml@ zk(A@dIyE{fL6c2ViCO{j{q>ABNv@}JKce`QGj*Yhzos;)&>eKmS@#=9qTgF3W%a0ZuKKfsH7Ul;jE?K^H>|{W-?PNCM5W% zTf_i|(8E~E0936M+}$@Dsz}R43l)aaA`i0aeO~j`bES8SI-pRFP@iVa&9|G;rm|36 zN3lCqjo~?@sdyY|baZKEOIFWn!+=vD;sqfkhGNk=o{oZ`j~b#@$GFGn8-Ov$$&JIU z`c6U3sTj(tI13fzima8tq=F|R@33ojIJFES0QB=LwhRMOsq|;(L3#K~Z@1FsoTF+b zc(SpYw)U*|PP{rT)$RQ5dGd{UxHtor} z_lLXUttW-b%*zS9UdU;Cu|*{lgJB0uNDI`jF5I_R?`IwaZ}Yu?`@#Mp50jj~JDTBPC0Y5wUK(KKn>+G&-Y6A=#ii8Ev< z(cUtGGCjf5*d(_$B+>F5le}nh*fUAbdwqo$HH(8OYbG)CGml6#7)cg6S$f+7A}k2b zbYKrB!k%(jp(Ynmm?#Vx6K4Jw=hQ8ZLzvg{V#Bt7HME(^5@rc9#(k&8=#yoz$Pc9_ zJRaQL;cpIbt#j(AH+&EHJ{^P7>j(_1J!h^S?|AJceUa|huXuWO!5Hoy!FcTjeK?of z@LcUP2%9y1I9J=zZ5l1yzm@`+wTFba4>IZLlJLvv`%&H+z#mT1vyJ%^4*KruK&``(s&hkJ#uCvh6z?3MxuU`U7l;;oG*r3~+a#7RMiSggNoc z?ZCY4?m5s7EwVKde7{J@%k7zai*J~*bT2#jK=>^K!3`dSDIw24Z=R>RzM&w@_9D4# z6XGKvV8v7(oFZOBY4P+T;5#0Rit~Jc@${m{THMum_wU6Hkyzb?b9;y=I&81K1nwfG zM;aYjX*|Mmnj5()hA_}u&tsqAKW=CxKWHD66E52V@OzVt-r%P=c8;?z)af@Q_+O81 zXnqoSRjUwx!F274R4*j&_&A5c4%#c0m+Sb(*gO2)INI+N7ycAK@?IHoEg)owxWjMu z9G^b(aw{NX(qD}K{onQvu*Pm);J^9B-Q!odc4syZq*3VN@6^xT15j9_}dju{fa6}CbfoP;DQ zQ&X8HX{xmW6ICfqw1f-g(Z}ytV+nqZp7Y6Wa3V)QZ%cyP!gRDMTNM0f)xq?G_V3{T z>a3&z3hLcoEtg6-ARyv@4y%lzr@gDoe|eC9JbeG#gfyzp#HX{N`d+D990zA>07FFa z-k8DZab>QUxLZ+N!3#dy?JkEQ5C#;H@{OxHa&P$3`>eTBYI@Cdz)Unju)m|nZfAC~7rVaxv zLMCovLzh%0jyYx{DNSteAxq=Yyq!l1gFk>W3H|~$P1cvS7t%s1YT9@=zU6q&Z(FTs|dO4xR*LY(}7@w3Ze_H!V5=J8)budV~X;>QNiUUgZjl zY{HzJACbs-jV}}##b~mN1QmAZk}vbC;wVVTSH037<)Ok4)$DQYAzwhWYDNncwwYm- zab1Rq{n_GG*j$NRAY%@j1mVaFn2I0*Z7kTIFb?~v7go~_drf^8TW3o+x!_$NAM zCL^VZ`DDdAg);(SDFH^3nkibXl`Av!_(Ca6vW3ZSuIV+zZ{euoEDWe#!UcICSkMOQ zAO%GQaNj{C%m2hd-3}o+33sryb9c5lf0fXq{%Z-EmkDXU^rTbHu5OXh_N=&x z9}nSN5*xw|``(uykS{sg^_Iu2xI|zYl;-yI)NY-q`zczkgPyt_&oD1LIM#GD$oeVr zm9%qizvb2_s{tP#KdO#A-+OB6p<7(l`_S1t>Zp*&H-Ekw+C~(x9O)Q^Sv~7dFAh6H zZN6nYzWp0k159_qo4@Lw?F+4R|6Tq66ITD30)KNgda47Vmk<&1dZU<-Rz~Q|Ad2e3 zNCsCYimw~Iimn#CH+QBvWriFf=IP<>DD9F}{u}_%xz?JmLx$^sUVIUZTzhEQgcieU z52f;$-;E}1b6e1H2-2~$aEa3p2Sf!lv4& zda|~C(Q86c?#Z>1R)5C5%kOsVZGMe7ZY-AVFG87?F~F=asG=bOYBo{$=aM1WN|U(7 z3a8Pd`5owA2?+Fl4p05HDGmO0df;DHVgDTg|5|BIXz0eQu_5|hQ9(WQ$iqAcOEw_c z8;q3ab(^^(*&}o(S!hF%m62X7Q-SPjT{t?$Db6&syumUYcGi@4j54N9X6%s>?{u5eE^iq@reKCx>V@LyD-_5zks; zgHk1uAeJI_rw&HrdF4W}#uo!uqh|bIBrmMF8dY*J8v~F~w0U>5)K~9pdYlyEl;n9> zkPUhwGScMh{16k_@~z&|OE4Zf7_pnzyn!RyYo{%>YTl*+J!&Ix+sqHD{G6O*`abfQu*A_E_<>>3u?mTCbgzLfTyn32j z*XIdfXiFa~m(7=Wq*~eO<{g)LSyYAdU`B#4xajSlNJgdJ zB)4jzCyK29YG5eSQSZV+ZYq;G)Ew+SRI_&*6p%|HXajQ8J+SQgI5+f34G0Ey-~f8$!R1PU=D-CK7Y`uW*SDAu1vTISvYXbknArioeui?u z6v5DP*=r*p$iz42xHR>D{rH1I@IdjAK?!q-HFBI*uM>Pd<#>A}<^3+IUkk1MSlo5D zHN^iXPHPf-$!roQ z?%ILD7Ms%HHx)tl02#*^Z_>f)eR=K|Y-j(1?cK`qqPzMA{}9xgv3#*TE<_glAZ4$k zmxR`ehn}TN@!{!{z5n~```VWIu&T#;)hW(Xw8{pQjYuvYV9)WGjHAgIqmV5vmc%WykMPU&2TzFqpcx!?SV zLEsP`=T9(kFV|BWM&?f-DG7tL0$s>f8rM!_{L9}n{3O4QJ!KX#PbPK}5~-QHPvIB% zzF281*YpS>FOG|Uq_TswjA#m`y}cZ7*dG3cSt}S3&ETVayySd!jL-IcX30pJ24ib* z77-G2i%S)$8>|qfYKQ23ti6_*Eh%!Vg8TB8AE5#StS+*K+ny*IB(oeFevYGlYRRNS z(#h4<>m}$S1`&w}`f4F#_|e%xgW$>7gO_{=k{29V+~=1apcQe;$Ld?H)1F5(S^*FX zUOUN@5rO281r>_eBiIstG>mNZW_Q)wG=3!Ko$Tp2z!HWIZSM%@d@g1+8zvOjGmme6 z5oT*j^JeoV0T=5xxHZi37K_(vf$h5E1ZsDuuh47lZ9cu&j|kg=$_c7C>(wV&9=EY>~(5$3M^6=VzPP*&5KG&egIQKI0&M8L>KgeEF0YG=Mcej zk2;wUIV!Ic25#~R*=g>x9rFn-D?fV^5Anujrgg)Ma#mr|xghpr&qzgF1djkkIVIeN zU6#HHheg}a_1uOVE_-u3o$${cz#PeZ=A+s(@&faDkpHn2?LRr*GtV=(p;K{X@_E+r zP~rpdH8l0Wzn(psd<%9yjwPVZ1I6qlSJY34 z`hJIjuSSNOM_q_)tNEc%lFNIagmsOJog61QH5?HS63l+~dWpm^gV=t&C@IV@DSM3} zlK!g6rb%B^bu`eHE8V-C38B?FLJAXPiJLVf*@RKalFH82rrh|tch-!--|0snP<~zG zKFi*lq_P250X_pD9dSV}aNH&L`_;PRvvkr)&3%Gw|C|Ip18GhFRc{H&?q@4DxgW=4A7R9ArjzB@88O(k zk7=oph^26S`w9sw@2NsJz!^l#+;A{7k3|WCAISd}R)1IQe-5l74KB2dzigq#|Aj4N z{)a7Gv-@jHdrKMWG_)F33E36q6g3&w7Ic=hWDe4RbQN6+Pi(fTBUeD*+B<`9L_(?# zm)&w>64r@CY2N?&1N7x?>5M%+9^1OtLs5M%&KE))qO^{Sp=`k8{)4`;H?9Y~%1P!d zB1&AlSKOZ7&vPTgIZc1ZwgWi~Rq@7|hb={jNHH{cCFov4E;8VPGIs{ zoT{y$J8&A3jGnrF&wS{5>CQ7Q&N8!7N(rC_MS=R&R4G~^FblW#PEQ{28Fx(o3S1P`(am`Y(6L@20wp>e_1(XujIwY}0db2*OrgqN| zHf=pVA$Tuv1NqXrGVrHHPC5HYT_Vcw=b5U!dYd{6E0A_7jSb?dTWQVt7Pe}}_2)Va ziIny3!LLw{Nq0lMNE|WNzgI;BY>d z=$MXanUz|YD$$!6NdvN~OE)Ny+e!w>Sr@eA4zKo+Kb4IFrH(ofe8LJL84EL4W9l=n zByHw}(}xQXBM#hHBuzWAQy~~cg`lh43{f$l>FU0PmB7bi^m;_L5{^&PC#I`Qsmz8h z=8DEscMZUGasON=wrareIuj&#F7CU!?5$q+k#-bIW=%@(agd9H9(^lPX}}uxrzZMZ zK?Y{{lMwMhT`)fHgtYi`5lM|#-Ct6d>1453m4R1O9azF3%=Rl<=5?sd5zE6$Jw$SK zNeHFZ4yu@K4KA|em5BUC_CS2ZaTot`j!pvACx(kBrObq)dfVM3JF7d)y{-@~mq6@w zNL<)epHU7Q!1UPLt9ChxxIZv>N%UB@A0Ds6>0P=A=_cjZ{99AZ^A$~ExuJ>fipOFiOMhfgz6!mN4)vBgO&&tR|Ea&!P_=J z|_Gx@u?Gsyrr)3ivSsn(=}#KBleKh<6DSxih4$r(u~(J|ms#(Kpa8kK*gdbHBo4 zaoxQ?-z3wQEl=es;=m0ly>Br?)%J()qkX3w^AN!eGcMzkX~t*(m&-cuw=Y-f`h1_x zK|0`X8`V7CRV~M6gAr)6KRIH2D4>mLniQ>aEm1WR=ASC&ospz=9pYP3vQ;o_^Y5aN zHqt(`o@6_sJFU*fh>cmRk9KLx$1XpCEKXxXSY31dk(O($Paiz-UZ%x!*B*SawfL=S zaxN}i!xSFT+1af#NecNCP&(#O7xJXz0>RGTkz1cJ{!iD-mv<342 z>?c|NLiB~khSMqsYUft*bPI+KXI-=nobqfB`R2-(pG18Hil7T>t~*Eq3%qnGs&0Ja z@QqIji6+VgZ_+u0fPI;B|A@V~d)z3?F_3H-*5o{IQI~`K$z-aj$A`e3T?D{$3;5YG z&S*-8<+do|vZ?#|*n|z0wz12qEs;Yh^}Sozn%61s6%{gYd89>uB`Y<|C^8b&cq^IaOvT-%5U>v=;UMDZP(X45sm=Qj@feoBd#^4WKlU zfu?Uph+FzTF*hZ^Bv7Xl^rRut8>lFj6p%N+88;{%7N5And-%M^d>D;ElkbvGWTW$J zEbfLQzPJK*NhLRuF(X5@^PZ+XfT0TiYm%1Cav{indks`G>`xXoLIR)NHiuV7io@_p zSfDZm@-1%{flGFALW$@f7Av!k0=r5vF|*YUEq?}=G`J1ml0tW&NVi(O$P4^#uHVac zk)jN>mstoU1Fu6hev$AohQa7-++i1u%h#A}Hns1@%kV&#kb^49g>$}=&8Bye3x&+X zs42_k^ve=!C?{D|PC`m*W&!bJxfIjh=Ckrgocx&`-sIlB=sJ_~MdC-ZNAO0LUo;CVOV`UBJ*5{ll+pARs!9#wFFJiqM$Llbd&DI1o{r*lQ*XSZE< zq=tnO=jn z{jW3Au)N=L`A!!MmD*U<^Xb;3xMd;~KJeAur4vX)Jtqgu%16hBxWu)f*0GRuKOz6O z9Q_+6|2Z5zDd>;g`{F1Y?*GhD*1tKbxe=fDH%D`pVXHBrz@QeFmFYI@Y*ppmmyUS^ zu#LtL5ZM4tlZz@7))#z}JR{^#c}=SvUbU)c@cju;X54eZ_Hq+c`FZNQcw%qufM>$U3hlQ?dcTT<=UJ!m zr2T}ub8jGQ^ZMFpODQ3b!QjF|&@ug^nBS!m)r^@F{+`CtpGC%)ZYVVa z%2LfB*&!`O-s8JbNQolfTQpkVZd?-VrFpE3v=O)h-TcIN=zb~i>$za?4OGM35Ta#PC4vc3%?Vf6t_KkY32ZdKE0v-n2H_LlVOfb_CR z2X26v?7$;ANGkeQ6hEErZ#G-I2#ixDDP~LE2ve+MQg*&?uF>YiGSHLimniCnX(IY8 zf~?3CLT}-%gfcX$DWW7uXl;~FlthyF{6=B?wnPl9)KVzc-s7t~ZyT2t>s3Y{zjniA zEU=~F&z(gCUX)XCCU9d#~6RpByf!wYi?Uj(>lulNlSX%iwB=!&CJ9oT5QuUXvf z1z*txaVA{s+V9r$3QucgfM)jg^CCbL-lhiPto1{Z2=H>rXX%#8hYOI4XWw2d*lMalX(&n{DC!-;fsL&^m;X`E;=z??qP2mIMqJ zyBWSV^^uZ~j>A}@c>32AFSb%bYB~#yWCEe=-*f4 z*Z!of+0_u@^dDyC>qmxmzPqDp6#H$ls9)k0J6aCt+wr$|*8a3!&$FHqfNB%_gxJJ3 zBnaDSJb(yB^egYOADUH(GcozeBah|z3#P+XCBX8H+#xMo0(_DHMP z+?Wt3=mQZGh<1_vma{Flz_j>#54B}$9Z$+?MKuJNEwM78Mx9w1OPR}WFxi%FdM0v@z#$CSj#V zB#dwMOyYI7tQg74qVfFdRI}l~tRel>KmXU=kiR+kpCb`;4df5VU#;5~sQ+0a*#44; zMzsyQRmLx?t*o&Eu3nmeNSMHUE0U8|!8o(V8gR<^^Yh+r}T>eN$LYTn$JZkH5gZE;-m7h>|39Lg$%%Xgl9 z#CIyQg6*A77kC^9N(xWF25VM>%+lO zvNJerdyR6Z`kO3&LutVcoPOHw92|djjiQOEjk&}kWFWsmV=8-wZZT!bVvh}o58A5U zs{1`tZvVYU*kinGp!^_-lFzyyeBXG^ewN#cg>y}{N*?f}g?&!M2VbRKH%^asunF-3 zR*n*nG-qb`7}bVhNq`Ur(jFzC?Vb4M4*Tt7)g%hpkF;NgNewMK4k&u*)sBG)vX(7MvIJ@Ie4IA$ZtHp7A(>mDfq#LVEiS%J~5 zZ7sy6dqTo_UbxAj8|2I2iCu4oiK2SKlb4i{hARrm^}9JupA&P-4AZ$B_VN1X&JIJe z0;{sL!v(ASV;|%8lw|7u8K55wA$9K+; zAtFqYM`HHRa>J_GB=Cl`_ruQ6)Y3P8zsy|qn-ibF*!Lv1A9$-JFvyZ~hWw-P^YVQh zPkSp4+wymQQ&Zj>9FwyqX|4=c7a1^nin%2?zE@*z7GXE~#`RS&WDOc=kwWu7ULp&h zrIJvSaC!_KFw?Yq2R8|^!Db8`YzzVxs}N}I%!d$__PVk2m&WOwaUuF4!jU{> z+X?zHBliDW)A@H<{pS$n3?tk9@aywB^RM$<|I=m=``<)Sov}-2MD^WLCw%-#VMRz9 zS|W)ikxK4r+t5I;9VDsH^JqgM&8B*4Z<{^Gu+kXFV$EIpZ3VW8;Yn}Hd2-=l<)x{r z0v@T(#AW?XZaquXV(>PeeyC$DeID97S>@MZi&0{fH#f2OQNlNKAJXza$XCl+nm>a^ zk?_}O3ZJ2fQY$5*51{u#db@%E*fbmwWTW682&HE*FIC(ktn{TDgON}D^?8)cOvWp1 zTIfg3mG0>-Wvv+t25Qe9oE3iRUz%hQ)WAU zo7s7fpk~~kFZH#%uG){=@`0L@BX;AANHLd+gOCyFz~$1*h{D3z|y| zT{RWu%u08wW4q8FPR{^c=#;(m=2Li%TrUm5d~Vor1CiVXH$-k<_-(K+@Jig!0ekSI z)3Q)ZF1PLJ@pNvlI#xcmD|Wj4*nze;sx7f>L(^Ij zClCN$DS{1`s#~%%H-Btg9a^C%CGB>@9U25Mcp00#97}Au^Who#KDW@q+6GdMNwVSw zz*^_*P?Kru$^Hmthy@_Qu?#9ETcOj6<6Ht09iX34$ad7z zPa~H$ZxJ~Unr}hwl7*r}!q7jNAE$gJFB$7?pc`YBPE_~e&U(a>Cr{uRSR&0#Y1xH` z9su7pda+4fsxU5pt64y07R(pp*GJZ0@F+85);tKF1BbFZ$EmAE(#rs~` zVldr#;8QeEk}Lz$cPbuHYoi*uWx-oTP=f8%?2)6DC0Uj%#J)zL^=k!A3EtWKxxqSb z>>wohmWnBl?#I{Cl;*{9l&^hXYt^MFebdFQ3o?Du?lEV=4#e472}`xUC1qz>mm`*w zp$1}6(e=#qfljiEsgjr&AIlGA6MLqgHv6GMmOn+sD+S^uzm=DBEgSmyRY(FY?}9t| zcLK^#9S9_cYz2^qTjX-guSVu;Q<1tP+;`F}VkMPAW@4C!(j8h%f;-hfRd2-rEujcY z)_GXnM^lemO_$oPekI!0qGI}M)=o?NoB3Pe0nveJ5P)yV-sm&aBCFbMOLvKu^-%RL zv3!?Rh6Tr1>m&5{n9`zKa<@u@#B-gATuR3zUcv~zD@JyjGSze4akJvbvo!A4NqP@T zg$VP4;eO)tCslzgI`*F*yWCY*QiA%*Ti`&?cG@R)+gt*PLh=v#2|nVM;%ZwlgJqUo z{i{7}i<<_Syc#Xhu521Dkk+=wI}m61eQh`u?r>+eH6PzAx_TgnYI-1W@cY85FNs>Q zzzsSyH(QHMz)R@{vM#9V(=S#MxBIDvILK6s&^!Vo@$Vl2_p$XEkLK=jCi24>5p&fH zygW&K+2^MG&FMW4(By@iO-x6)KT_`zb(QbG-2u;{?RORtOK_+zT0aUa@gh zirlMp5bUdx`{=PPl)PkmP_Vc^QUpN$sV+RtJ0376xE@Ys07HB{tVejZ7=y$m@bHE1 z1Jzr*0e3@e2iOnAYV)lj@SMr|aA$4YyiA|a{Xg8jQ;?^a!?ir=%YYpq+-qjyDzUusz={wCnr z-6!|FNAB16$g4vpYI_c<-jBFEE@a+tRMU@8MBwu}+re_5H5sOF||j+Hq_U7zS)(?BxX?to2`pBn+6 z2;g_hCxswYJhxC^aj9lFtdHKE;3+YQluahM@2Vto*^7VB|2QC-sdM9C$aA~f&-uYn zJqq@?y@6RWrOYY0DC&eDKjFtuVRWeqJT`SbSADq+oHfD8-xFn3-~VA7@&Egh&_A;G zKS%Mk7n%}1|0+Jye?jrN{!Q__{wjXfKW3x>Ahb5(1(}H1l^bG&hbM_VgWMMg+$4+2 zpJptsiznywuNNtwSDXkWl3i@5XJ#Km8^@n>on53Ld@XQsC%b_%KHrcoU(MoNm_S0G z)TAj#k#~&=k!cZgK+>nB6@O}?uzEN!WPe@doJywQ@zbu2oCwA(uIw%I{tOQYqgE7G(z9?@(@|mtczjB=vLq?Mfx>5Hz+q8DREBq8^;u%SeYKNvenmr` zb5q$`Z0`C1Z}@f5hOv_4xPn`2uU{9tlE$DJ>;^KMrP@%L<1Ig8ce+`|`ykvA#|HUV-f z?Dx56cW@k4qZv#$?1Tveq(DSjepIxFUX-+NIWf!d^$Y)x%C4zu=}eOXuX+4W3+zoZPV#eRnGn<` zEHWbXeEQOL*X1(wAoI2E>#t&jeA6)_Y;CAoGl7u@X}Xdo$&jYeQk>>?j63K!tNwaK z7J^D4wo3$e0>8UPAtTy+iV0xH;>cTZU;_dsBqoVj>6XAwT3yQ_cYc)*FklqYxLR(DCQx7_wOYn*tja7W$Q~;6y~H&#w{#?0AD3ZsHg+T{ZK5py_=B9`Y2s-8l$jZJbrHKOPZQ+G zs-BriVjU$3uCK?`m<(023Y6%1&Kw) z4WjSh`Bqv$!F4U_qxrchc#2S2WuYLX;xR5}aSvw&65ycuPg~xIPk^($Fz{-1HL4nCP_H56vAj zIGzdesR^NfkE`nWYMMD~)ei})jqWAm%E!_X8*Z&;h<``ysiT!I4PW|2P{C`anG5)(p*!ORX^j*(1 zP>;Jwsfo1Sra!iO6E(yF<=m8bx>!)U$b}x)L= zbbH>LQBG&IZbTKqt7S%d&BN9P5fr)-OREdWdl6!^=RL%;7t=#yvgJIusXlLSe>{lC zu`yha%2&gKL}>pS8`Q!6F@kk&Bt}zM8&b^XqXVOc3|f1JoEGH+0f;)FgbHgTseW4j zJWKvGYB%{epHOdZos}rc$ta*(pQ(a})e|ODRTVU5!&i3ff;7h?KU&L%@9<&~LS6V8 zLYU#WQcnZCZ9Bv~Y3yY>Gj8`Y71x&yGC-no)_EwG!{9 zpT(3z|y8 zir7lxXJgI;cT(xLUzG<{M>&-Ima4E3BVjust*sVDEk(M`RE!wo=O%T8w*}5yk)|yb zl9n9Ee#oMq(cFfaJ3Zqb>GG?

otWNPw^n?*4J$xc|s>yLKBS`Kj-Sj3ssCVV1v!E2)vAimf-6*U9;azgzD&N2)@aPo1Q#Q*tw@p z^02@snG5ho6ulDW{S4}|IdR(psC<~#rs@;}BPachN6~;ZerQK`6tltEBGB2ML14;) z-ABweYN(%XC4{V!yPGxd14ncyIpNlIQ=}k_L?9jW5>j=gCNw zhUimk2ikM*&0Vtc6Veey)_6~O%phR&c}YTPNxD$pFo=yPKS^k0iZYu%>d9evT{2!d z{7W>LowdZma!8z~rK7fMtA5S6VCxEF0@{Gy#4HQH3oIHLXTlp#M-hX--g;T(Ka{|Z zvJfDZSf%&RNvEF0s%#J0>Uf&}P0r3wu6Mq=1%K}k$iv!&@Yt?nYtujk7N1&RnM$3N z8D_uV+PGXQ-0$_yKz{z?O+MQ9tjE#CjD!}};D0n<;EXYT6{J*a7KC8PIIBfeUP{%Y z91mmVY~Yom5O+TrBO( zo&Q!8{>yJBdLv7_ORbr|gAc43FBPyx3$cfD`PNzvM z@8YhVD`ya3RK^heWzw?(HHQm^9)Ty!_ld|t%IlY%>*pvV-LLGnGel8P*-qHsl^U$* zp6rfw9Nn|x>b0AT^g3h*BF`J43)KQ*&uTJw;ZgK+bevlOn(&4cHVPxRmEQBgM+*}esA8j(iWz9?Prz&1uv&ye(i<(JLk5Rq zUUc5mjOn{BVk-O{1-w52MWi_fVYFJ+G7xW1C632YBzj$bSaUX=x7HWXC=uSc)q zXd{WFO&4MXvC_vkmbmtv$)tdyd-t12& zW361Hb(k;Dpb&>c(6aLH2d?4frWX9uEQ@TwrVs{5A+QnL96Zv9HyN9ZgG`;wgG14u6|$dsbL-Q#XPeXuX@rc#}3hRFaADrDJ*pvZ>hN zE9{$!+#~GX62N^IOd7??+6$+Vr{tXV#}7mdW{EPRm(*f1ZggsyvZ9yNQr8{#sH{rU zL$G`;&MwK^MfUgS=!P$KvJ&Mv>y)NuRQBgNb+8Hx_4(>gR~RaF?8PR@Bf$fFO%zMG zMW8v31ocx!?Lf zG5)sKPuPETK0gSlI>P_H2BzQu0sU>v{-^QfpKSO4N+kb#A4mlBLDpt=88z&zq z@gG*)sYhAJSByoo3N?j!4kS=AjxZeD6Hq--32jSfAto#ch$M9>xO*X1MMgbIP{!zH z7+15U)M5=2&`NB}zN1MwCRyiu5?i)$+*4tOxDk61tZ)Og5HL(E8uRGR!d<>XqNMG+ zO_pV>pXniWAGXr*d=JbEVwNHRl-dVvUS+aL>_?n1)BNs^%*tO6%4ou6<{SD3eTq=V zeu|+Tsf-e5(3qzpC=!xtI1u3*OsWD`enj)SO=wD6B&9jW>fy>;yO&+?<4TwdVfAXh z_Rh{#b^PSw866Fp&G&nq;Z54Rza3QM;l`U3q-vhin+%%0tSFiUn(s{j$l=5n4YtYb zLfTTzy;F*FIklRjzpgCGFD5kztSn}jaH%BlG48b>0HeGTE&*0X=z5K=qiodI^tD3 znTxsE5Z9ykxP$BlR~A7Co#3=oeVhWgn2>z)pu1K(tBXxdYpavrUSA#E4n#0dCiC78{)8X|)*j*RmPl^(c-@i+1 z967%Rf9Q7y>^Q%&B??T~AHk9(96No(Wu1|-Z$BCv>!`Ecq;AGI9$;*0I=9^}SE=pj zg=7&PwSS1^j`uXw9@3N}#{1;(DdEq)$RXt4t0G{KkL8h8czu$pS$C^Br;<*^2;NIp z+E=$*HzF2mke+5;Sh|4$;2IbXZ~J=}$pdfgiHx=Emb{Dsj=tbaIUwY1R^#hVXLgr# z0YbkRPj2@}IpU%Z>*KgUp@e7Rjk&)1AHb3x7Hc92W}fwCCT<;RH=ZaIc>T7jtbs!n zww^l05CPwGKAjuG5?{rQOYI$I5=cIA4GhSvaYxs8qV73nGZ@De{9`H(sJu(~{%2r( z!zxqFRqizY?O$>^A@m2QHv57P%DFR~D0-kOEo`6L`Q5AD!LW=e*GR(#)Gyd2Ek}QX z|9;xD*OU5YTI`74JmtHdj%}>|ux<3{oG#Ye@qGBENpOy4Q3K_kzNx%Yi1r8|fpr^4 z#Kv1Ee5esFA40<+G|ZTT4fvma$RmI7&6R<`2C|_|C^Rz`WgQTQA24lXQ)D=Tb_{w?UKF$NjTXC+oCur zU2J5{1qC1Yetzltdi(85=JRs^@*kHl-mL>wv7SYFF1 zNYAnON3xnIm-X5?&bwsvffHm5X)a9)cOI%exk3eb@^8o8dF6WfgYAu(SwrIzZ_Z^@ z;|AI-Ay3n977w^%uEH4s5;XYfeelwo$n|Hi8W}JRx!-2j?EfL#I7ch$ zcK^4^DwXR$m+ODRL6=!uc8lU@BiHX}KX$+tQM`wDka0zYgQ=SXB;SC{RCce?7QZE)?NlmXACCFjbWh#}r|HUA~+D*J;u z$cCBs)yHdZ-V*~j?|c782~aZ*8!+r>^SYZH(je;(Hq<0xABjoyyh|qN&%ruCrb&(Y z#gIk6j;Kc&f>}(Vn4}3ErE1D6%7_l&2*LQ;pw_r($v zwe-0{X9FBXOlc9>$T7v~y+HZ2li610E@|2HC5R+^&(X<@m7zxJ`(w6xaC06ZSfd7c z4$zbif^KFc^ig$!ekMOlSW(w4oAbb0@W^(>T;<9had7A) zg(gcCxoV?|qMv`?qF<{RPw3b?>~-=koA;I7p4-G@P!}HOA2y1ou{8?wE{SJY67zZI zw>LFDn7@m9H3Z2xzEGuBVW(miOv+*6)G#kp!7vpTrG+P8Hb9VQ3T)ItdJ%b9 zxzx3gT!x~W96d|#YgP@gO4Y5LXe%%|kR^J#w-C<-5pl%;F&OeYc0Wp?0;frBdLbWr zt>BeP$C5QDX^jw!q@Dvb0ue02_<_X(G>ori5XeJK#R+gVm>3ylTu44q$)A*rUw6ZP zqMl+Lq{`4yA{c0h?a6Hz-=l@xQ9aSM^{d&V;16c62vPQRlyQk`Q7`J2ZFR|I6Pt(~ zaQLXYmJtKKb==>%Jt5>5Q5ggDLQ(SKQ`0EKjRZR`j#%3 zN$u|}BMB&q&c^P1s6T^@Evg)0*j=d$AT6r!N?u%92FLd^4rt%;Q6ci4y)e-h9s=rfs$wfrx0jQ~2&;YFG=1jqvsvdo z`P)(fP5&xw z2{{T7o0^Jg(jgG(>R@JuZ>#}Ie8Ult&pY48X8L^DqO^n$90L@p?hBYOj`HlqTz96` zsP-2qj@~f!m@qjp1Z>?1m5DSY2J?0QYI)Yw6v@td1`V0F=_B$LxX<_(*E~HgNoC$62RXjFA0JF91Z>n zrCny}I4}G~gPnIOw~s_OoOm~l6}T{HDnsrC<*;HxK~AUr3fihfC@DfLXX~=0mObvA z0zp{W@bK<7OI=D#mIZ~nHyEG9Ts^6JW3E0?Cj%S-(o_v|hD3kvZ$5JkiMZd3_)RwN zLcjM?DQ*lsLDuX_>J^K{^6~+f)kP@1YqF4zy2n`7UNV~MLf zshzBQ`S<;YilYVoO1fm3wh-MayEGXxDz1cdDMvC#qDQr#7fjHdtBf10QdzmJ8vO^c z=pn6dyTg>afx`SjvKp2;fiF#^>Xm*u{{78A$_uE&N=M0>wCSm^Xu>9S>Pqu5RrlbH z)>P4|Qx(B!BWe^$Xp8McatT8yF2(V0gPq1&X$&LNbSCn^4Q(GFwQ-m;H2$PRtZAdXPr<`oe8A9iiL4BAslAU*(&JAoX&^dWJH zUkB>1&=gQ7{c0l^n5U8cvpGk!9Y=|%KRfE~3s_&obp@_NceLj01}f-oZlf8#_SdjS zBqmZpTaRBHP1hyurr25HlA`<7W@@z2U%(Y+inu1=QB(v7G(3{G44>Edp5nK{iXbnD z1+a(7Z~4&NX}eUEVJ4kFtCc4dro^;K#By3^8VvtG-{^PKuBhYj@>&0S>^ z`$r2kY9gMtH7IYlGo2d)DOi`_D$P63R73Cl)#r=FzHQW%2#pb78r2r6{F>7< zSj(?n&H7inA5caLYp9~m-t4~#v70?iC<`*zYSSuZWfMt2QyU!fBl@VA?)=7%XqKb8 z!L8OWN@j=P!RBA^VD$TUSf0?j?6y%Vp`Bn(ipW=gqvz*%2^g+JK`p?m4g5h46OyZU zt3xUhAt}YvP>Vgz82pAqYBr#LLL0RlXu5#B^~JB3>pkVdgENDzhv#gAQ)wZ;fi&kR z&=3CvUuXqLFhXKd!012;+D2Cp^y2(h7;5|z-I+|-Qt&0NjEvyH#*b3I9M}D;=Ep~e zXObUWYG)pR+q+LY>9!St7sa*(+So$eI_V=4a5EB|x0mL~$?+-fc3ggv$u(TVJZCgT z6t(t?uTNCIEDnZEHk-q3D8;18@f9O&5?J@=ejJ*tjC-K=I$c1 zv?^P>udSdW!$4<_wUV-HZ?5m|VxZLgCWfhtt#&=hyr=KWpr>Bqf+=KUWfpu^W$>!u z=PDm5v0KuXdFyPC0#evKj&t7D=p|46CSp1lNG}wb=p|%Ke<=0o<{r@T z_h@+{7j|z=AkH_o9t4&Bqb&T_>DbQU36%N#=pCh?va3i^?3Q4^S>h=dBhi@1w9r(B zB{yFdC~59&Nafmv=^9)+7lSy%l=l%AO+jId#Iw_@iC&IfOR}$!N6jpJgQ%^q>iH zL^5KeP)c-PPH!VSJLWiV&%&b=D5e94=BZ#cWHUSA*|j%H0cYtjE^e6P2zj-X7_qCI z2)v_(YXomHN3T^gKMQ6Wh_-UFVEsT`K>098>kNoQ^g`=oB{03!nn70W9Fr2OX|?^e z_>Ntmq8^B=H%6J0PQI#bLY^=6T~jbAgMe1X5{T}iQy+VS?9?c!{0Mc60yPB3tLuHK zc2g=gw#$Q(#Tro=>vDtdGIKvPwc;#ug$-|%ZcE{4M}v2m1+7I9Y7?Yz`CwwPCM_Bh z4&qM!N2O*Knsp)_)ZB}aXOcN-QS(Ou_Sj+lEhWm@Y2m2m;&y1cqgOzQJhvVS&6rIv z& zw#7+~^oRPqz5@p-d$%rzw~Kz=G--%~EMDakYV*R-g8~XJ}PAStZa+Z9VUKZ*96~>Hf73Ik|Gr zbb|!-Sr-I5A|rlB3v>yYFFf0T$@R``#e~F&Or$Ghx*m#2lT|4t?58W5cpjxx<(Kbp zoBa4IToW`YC|r?x64#xZ)D8w;4ZLsXiFYHdCXLph*(Y<+1Py1*gZC|ClE~-XUtj+r zdHG-Cp?^uO|M%x1bSEGow!b`-#r&W1(7z|ub#xRq#nJq(Yq2h28CcKX*C}PdSasB_ zP|*O0jQR{VM}M6XBmin!X+<(+KC4nux#hxP0iq{BSd&PX%&+f-;UVoI8{6BEfizif z9zH&NmB;yb^2q08@^yIN5w{UNd~#Jt^J&v3&qEIIqs7{DsDUTr`rWPbrhQwYf7B(q z^q}3X+kP71-44#rZHo{pha_;=j?>_nMMdcl(WJ3}%~WIZCi7&2elevp-$?w1C4CYD z^q!vbH>Z{qQj^UP?VpU|F8|CYKZEcV+ze#6rkuoGR_RXV3Y)c`9?LG^E{IH?zsTu}J8ziDWh+?{%x?uX!e8}s5QwAR$ zPp%jaim4#ex^)S!Ilch;O>8*d_VJeGdr?cBIT(S#_nsHb49p|$Q*e)27)ObGBj?j1 zABJ3hQz6DozaxAWoI-I+zny5;_d^%eTTIulk@`Lnx|nk(+3_FTRIs!INe>XNe$->? zYL%`ykBi|6yd%rX$8Z8?FyU)v?ird&Dx{VQ!R;L;T1>afl!9a6;Zqow;*u8v6Kb$Z ze&;isUOzzl%H=I++&rb%%=SQ|)y(eXTY#7R*bZZ$*6DXSZJ_d!ACBLQA+C7G8kb7U zUf2#V5Pd;0l$=+HB(5jIcDfQUh}dKf?)|d?Q{s_pH;3P$z+n-4)#n|1DAg&tVhLZ# zZjpldgzB*j#b5{*-W&)JjToBy0$9#?ca;)K+so=O~5R!uHZjV`c)1e;6lnvozEZZSw#5oR4-kpV=p`S?@k za{lYLOkx39z>=7VbzD_tyb9c@hi;eZdn;v*(>J?#*kOUNIoGa= z&vuCORQ_BCB92rt{BN>xe8?#>**AIE%qw!=TRzFR<%1)S1OswNF}4mVqUNf(rMUWH z4r3~J1kLfI7+scN3pl~Td|XT}kXt>s$<{Um>%}OP@==hFs_SZ5l8N<}VcOTu-Ibs} zKLglRfepQHLEqwhPUPT_=7E}%!pO6qXAyR0ZNNwtz*?ZULPmg9Y4d?j-ZCWH>wScv`pZQ24W!MGP zH%+Z-z@E+Vpz0?4-V7BXDoW8i$FH}RTP9OW_6pYDS%hs5TF=!_`Xh<&)E_?SHLo>~ z{^QzHoE~NhyMC@DMiUgJW?~+l;yO7g`p28%*7wZl*sTtnUxMct7Z=wiDGG4h7fk|B z+^@pbJ5vQtNH{&Ehui`He7mr9115ueT*Y$j(AJyoWt%_hOLNq@mt9+%4{pkFF*w+N)&u@tu`;;3k3&Ue}?$q`2ja!K)^x zDxi5#Fp#R{BuDmvZNo;wa6AUiTy-NvEbv&>8gzaGT|H;QsRsxX(P{4IHPz+T5j9OD z1rOi@45RT40~`3_2Vw$;N=fBbcjKZX!h1s{k}i!LFSSp)XOByhR@cV#70#e)oeQ?(_@9>qwFHp#n$~t zFsuC%u&Xm#C?jnk;tMN+ull>5Y8x~K9&zB7Lg+;#31Cv^yeKW3N+^F^rA{qY1fWf+ zU>NvEgTB#CVB$HI1klwRB>dSD_})ZdCZM~SJ~vooe-6R@;K4E!^IOX~Jol9)o%m`o zx%v686XgFIT>MLw!+(FcDB{ZVLHV1}Z5R8`;o{%x8+3i`7sRo?{qy$GEYHYoW3NB- zk&$evGMku0*B}`yR7kkw&9dgNYFp~+D`fGhZJ;7oHp*BV5~bOWDRIy5?W1&bPE>rSlEtS$I4%yOVz zdaDAP0FOIq2zsOqPTpEQ0V+tj8e8oT(z5+Unco1D-og_cB7IX9s=0vtS&DSCP2BQC zn$<3i!+tu3`u$Id44$)!tA4vFId!`Bn_2a;eo63Oz*E@E z#d_45eZQS~Af>miUp^bvSCfwX&edIm^aKPP$QfcZ5i*%+Jy-_@=B4O-Sn0EKTDK*Hv5BNad8cLB(@^YGtM z->7x z@ZR&x39Er^Xq*Fu!lYaXtU?-lhLD`7=v+Yie+Djcy3gDw1dC7_l>?Xi&D@mEnTfz3 zr!&w!Y!-MJ4SiGF7Ydc=MZQB|6BO#q)X^=>$_ihOqY6H+&@WZV?Q1$!f?IW4-Dy@T z-L_7Td)IBb>JYE^+s^5KYL7IEpQ(?u)?aY-S#O_vW8!qd${vx*mP{H@pKC@8s3wzX zp*I7S*1={LnRD`$AkqwQ$qRsGQ{P_cOqaheL(Qci)kcYv@`Y&>o>%5uOQ?S*_`2;F zfBeMxdd^H55KJRh^#W^$;6Q&|+zkQ0Q zox;XDIDff<720#5xqZQRq~M5YQx;bhg%nbPpl=6|SZb!CGr@yy z6;>gEHv#eAyMPx)U<}0ftRjoNMCXa^^}}qHR(+9x;Y>oowVX!v?D`9&<5+v)S!+LO zqDG1*d)TM3ECaJF!()Wae+RVYz6*VU>{fr3nSQpCFDAy0?L+8aTIK97Jdt*YU-5ge1T}bt^{-hTm}W_Wk7PSNGlj% zQ5;Cq7Q@gAd7|nqA&8lHO7lbxCGa+UAd$#q<7tmt*$5@NrU0}Fbe{y;I%gnJ?1Bcp z1^r9h0Su>9^h|{TjX25{WgWbAyl~8yhDhqdkR8CEROe&`=Qi^vr%I$`ilk25X|VZol|pLBtGbmf>5l z($&|9Z?LY(@<1AnH6O-cIXL_33*3R$b>k@y^5h0TtC_A20%?#oVZ2^7Cj%BywEVNx z)_d?H6Dg%lW+YiDnzxhIeZ_x%sj*iK^yCt7N2&@;Q?Kj*>;vclN{+@J253k5M=80O#^z zE=zdz_X?A;e+G$#Tie|2!3-vF1l^JVpgxC$M87Taq{8&SrozG43%=N(#L|A+ne}Sv zl|Cd*9zRTUl!jBkH(O_e6_zdSc}r@&E&?pvsj|Z3u5|$ESad{u>C1@gax36e5L$su z#ni2IY24ayR0KYZbyJ0s=elGc8IrzQ#r&+`9kc#?!>7{!Cx<4tmke?)}7>p znozubdzJnrzCf4A<}84lEd`Qne0p!vNJIlsX$W1}N4KZU03U)e{Yx%-&e{n zDrM=9#!>TT_Sm*17yFu^8TmgOQ~gWB$$x)x$}jz9k@`zcFw*}yIsH?=aH*?fzcGsb zZ?+6z=+CO$Uf zZ@H6WA!#?zC-{K>B8$j)gV-xdQcE)~dAtlRsF8XcO-0U~>qN_6^)z{htkH78#ml} ztWX(6O7D29zjUUk4fK|WW*M7>+BH;M|%BN*Ep&j zl0I~ro`jx>#%JKMjr(*}^5)BYN)BR4U+dYMDb3w9bq{{)Qv9a|I znRxBp;qnD@lEa;1+oo%#8-916jK&=L2JGKa)s=>aJTJ09l=prDE!=_1ZI_C>l|%SA zX#52{IyVe@qJXNbaQWC%u(Z(!vQX%ah%op{gxC~f0Lh=lHS!$QbLOVj6(%(P>RPSd&TE~YNd3z)U#Qixd#&3rHo z`O^S_tp+)y#&%7C_LaxL_+{$K?Fg4cINiw8?b3(_-?7qzh#2gvxNF-6if3;{vN9{7C2`Y^T|B%&nwY^eit^!EI4N7tr%ya8@jHdje{b zvoLLHKsL({v7`Fw1(-Q^q@J|QgF%`5gg?Jx<2ydvO?=GSJiWezqK`j!3DE~;v zeI`7yz`(kE* zTO~lo2oT?Z5idL+=#3e9H9T1D?Jrm_*1&BcE1^6lIPpe!;~mgmb|jnSLIJ5k;D9S? zlTR=Sdmy#zA|8}br|cjDAkYXM)LI}TK0%UXnhnBU=;R0vC!fZWFm?u@;OoON1^&PH zlr?LL%!Yv7sT!2 z6^IY5TgbNF{W?vy94!7pso@M3D#@oPck$?eFI1e%rfTB{)$l<0myIMBARS!nRV&A* zb_Ofw8-ES$lt>*@Mmbu+=<+}mJ1#8FtEhxJ@M-P6iH^&SP0SIZFhhu6%j^L;8L2e9 zI#<983P|$W1@+O)BAqqOHwYeD@0Tc(oYw=?gb&)$h@$Si^Qn7PstFp5S#om6XapKT zv$#vRrHmUDTPTWczPspfjQiF#L@MFgxX&h(Qt?P_C4no^j;z@p-oOl6N(aqdFwdke zq!H^muf+Tl*aT82Yy4ax9NMXD^HmXgw~1FDW{=LqNcHm#w(VRnW}0~5DR}fPwG)jJ z)5zHvMJ0VIGn1Dea5i+3n62=@Fs)mptBg%-iKQrhCZ1JT$lm`Nh6vT<;hs^H%_fki zym)l+Z6ha>2Cj~J-}hE5eiriL51sKJ^aTt5B^1h!Di8&^_Mjfv4Q{)-V-mWvCX z9GotC4A}*85GvTPp{H4g^z_%P~W0no0&mw#FGWP}~^ML29Wpzg>;R$a*44 z&~ZbA?Jp?pDFR593X*J%8YmSbhunZrMe!!&I)mtQj1odRZG@c9>1cb6CDy59hnK98 zp#Sn3=k>YOP&Xenw(;XCPU#R((0 zg&=8)FzLMV$&o6SM7hQv;l*6ZyRvxWj0>(t7P8TCi7q$*?+BX==2REeQE?Fg1ivsP2ZUz8D8;a0y4wDlE}C`hEV`beUbbtq44g; zJ^HNXyLsz#D(G&dC3yDMTurIWKTS4&flC2b3d3*br&0r}kvPbvMr-VFOh5=FN`_ePmA7$g-@4nz#ujm1RZ3fo znHjh?#HDV-+SoKBE)~Q5E-6w3u*CXL#ZS!Rvu!yypJ+9M_O_AZ;Z3@v27=o#px4AEX%)*j{ShwQDWOt{VT9Az3yEYtM;yIC(AeAw}mj3=33)wwaE!x@Ad+R8;EJ!Bq3Ay8^bp*FJA z3rkO`P5nFDCJ(g08T+y44<`(d@rrW`N`deAb3#`XN$cI*?xl6|yQj_bz zox!{ev27Gnl{3lDnNt5A6(+B`Y7Ig%wl9{44$E} zPTS}?&%ITLhc$E`9z=zgY{(jD%9}5r9hcu&jn~}z+z`PoMInS_$3-EhCK%B~E_1CK zIDf81Bn-a`)qD)Szp8tTbRH7O-axl9o`RC%*7g@7r$d#m86G}YVVzRf>DFIq0!p!xe?$hn15Dm#$29ZGR} zY-2_fV?#0DqbBVZYEw=;8BF1=C{{h7a~R_1&})6khu2r|$RNC7PFnj$!1^ZB4E=yk zy^+2l6F5{G&AD-^n|fgp^0IWWL~b(^bSrH%(2=zbS|Jl8tAZ9=&`}~Qf!nPHO39oB zKckPUfR%GE!;V#oOqmd}H{@`Q@QT1uvL7A?-bl4h7yM(o=J^uI_St%WH8r#4t9O%8UH1; zl5-;>srKqTA4t(hzGL2p*#}_~!ptj5);5E)se}kuF3N-iK4Ws~9#@VTURB z_fI}q3>zQoGv_5m(}K!olXai(_Xsi5)SkGuc%#a6Pw|UO>_$aocZ6rz>d{JPCzqcX zjjE({`U!?r3=CU7RNbpXImOHCM=O#SD8xRlC-b zmn;sFG*F*$@|M@5`yE#KIT(SRXB{vdIoYh_kLLnB9hE_LFiCkqELz}Q_aMT@z8>n7 zCflGg?;B^Wa3Dre@xY%*jVy5a#M6FptbGu&U{U+euLPodf^I;KAle5bddg}iDQm83 zY6?uWKp6#NNQG2IycV7%*YtWn^;}ibJ7rQ^;W|qD5h2gdPQVzuF>DY(9vtzJ1`dS8 z0Y6c%(CO$386KH_qY}(H9q`ZXM-gH_!XT%-&QbVERPoHir?IuX5J#`DcVm5UK_5L= zwoZ+Q(+70-JQAdTAZ5V4ozV!pgf6MB>(@nYU`RBb;@SxM{dnP_q}iNK$Nsb6UeQ5P zlh>;maA$Z!3TIw{$hQAGw|ZY7W`~-vqh(nd>`F&Z!QD zwszKTz$!dPyJ5W`9ey@>JGlYVPYdB zUGXW9>C-QXOX6)q7il_)XX><_!GA`Bw-9U3pDq*h$~}Yl6Riamh!jX5@I*c$`JgnI zd#8^Ci}o}0Wvp+ApNphPT?2-)`fn*bp!^~P5ZQ;Qag&yQLHu9by;E=~@w)9B+qP}n zwr$&7aZ>X&}$uC9I=|1rMt`+l|%dO9R| zgIU>Squ|jATo~fi7>CyR3Q1w`foIHm?zA1K_c2 z76inp;2Bnq;$*RHxZ4pO%CA^bhi0eN+Z^2_qk5;5lU&OI$(vt6`DbvC!bwW5s359?l9Z6NeOHBby}T=G;hZjk=I@(C z8TDN}R-b=xjtX*>`ZT3gLf?2+WP*&u@`~sgf}ZsCrPBtnmPb*i;Re(z{WrOvOmncG z5UI4dx*n7*)EmMWDk`z5La0o9(!ig^JeuaumH-z~jEb;?l6)GW-AC~Bb=5e`oyE~n zXG2oG@3I(=&t-O|Ca-ClPv3|&>a-mDR%8{GF>dIn2Ox$6%=h;wtkSIG}=dnTDJEUhVHnJ)gLOM+;sK*aZ%~p!v6=gf*Ze<8L zeELWxKq-JlqH300~Akir5SXmFEdV9qu$*RNTxia4L5e&u!M80yP%U z_o1fmy*{+R)W71sN4}?W@4#I?gSU^j@4FdWKA5|IwC!+V^fz{OuK$X1abBpx2_mxW z{94+S3$P;izSQMVGLwf+dSpSyP^JSabS|L|PfXs_Cpm5u`rq?C_9dh1mQd#MkC$v|^plbk* zBx}0PW(I3$nG}NZM=V~A&YU+|PUZQ?MT%BnZI)~*@b(zv&0-5Sl{i~1tAU<$zp91? z1J&=|DIK(Vp?}>z?3KNDH29wAQMBUH8`C`uGIgK+jSATUE%t0yT>!I3HRUCi=5^rR zH&guC5JrJ(g!Wfbmwkcc>PfrSEz@EJV>@shYC9zJIJ$f9{UMC1xj+>RbbDPklxS>eQ+c$7gWHAh6y%&xySa*0vO!scV)iQ5=)C`p(?IqhgkJ=vmMg z8cKpoOB_|>8>g+xKOodj5a8iADO;CV&Fvz%sdw+-xPd?SY^5~JE){!YUd>ZdH#EC(*WN5I(1oLBPX@csLS<d64zUNjB2^8;h$qmI0xaol9#>GVwiu1?%kxb|6fj1GuCM{w z8yYK+4=dRBC`+_5^3X7zGd5to(0+g;H0xeN-mo*NiM?9dDIhD2UZ*oOt}exWb^v~AoBl`$To-*%dHRAgkU^5f4K0pRK$KUdfiK*S4HApDhew7v3> z6Ra%$h+5b_lvPX#0MP;@%h`eVhSA)@H)=o&oZy3=; z9A%C}TsFIcz+4mM-K=3Jm^0LkCuzDvE6_A=aBFvNnLdpHcx{-zl+2bgWSA$wPAser z^o}kLo<|76LtG$T&6affSwW~6 zR0na)9=E;J{Pr50Kfu2r_7cQD_&*50bGFU9Ti6_bB~faveV97A+dcS8@WUa%D!+6R zaNVMOxg$VzMXE7U1fOJrI=gi>I#g?nagREhJnf8dfZ@R2KVGwQKjR)2R?Jp8%2@8N z7A$hWOO@dg@3dVz9?QeeX!#_I&IWyMA>eHi?@v& zpKSUIhN~ZAbh%uBI1>J2PkbVWiZ5EUhp<^M)q=T;uW4Av%z+klMSY4@oQi!BXyI0W z38-~@k;AH6!J^$yQCP3n5D{Dqk;7-U6Si=6%&$~OF4Zx1b-y<%HbY&z^J?J>-1VOI zPjSiDlibqvPkh8>!$xO6W<|$?yRr;1u*fz{7+U$JW#9{Fy>k9q+dP2-HFz|;dl^ff z)=Uw+z0=5WdagNH5Yge4S;6OOWb*`tb7AHiu3H?Nsu|2ATIZdWB?c<)Cqa>%{{@0b zteB>1Jjt6}byQbrk$oMZ{6W7b4CyuMQ%&@E+nO%Ir+z_&)s|m@`Dfl`v}fCdc>ZO} zT#S+Ayk+B()OsvyF6`>b*r7Rc<32>mQsAoTQlu+iyL|Rj)l@mzHF6HJF48osjwQv# z2)7pe`M#*e0z7|TKd->4Hxu=KlPsaw z&%p8P`~Sp0{coVu|NH$@t;C@)&7Z(plI?%5Isb_ly!^o#;|wEy<(61n;Bx_#wf-jU zV3CkQfuU$3V1j4J$|hw`&t83q_8@`iA+AT_h{qWR0a>p{ruO&a090#KEB3(LL4N26 z%pp1|A*?Cs2&10BTQo|!rJe+S{17Ht;d;8 zZ9q*Sh(~Wa7A+d}!VM7bbnbKXTTIpC(qltPjW)@OhQ;+HcE3iK%mk;pIAtdi2P!?n z)Wo^aBpD=`4g%L&`o=gBRlyZntx1vTU(~lMEka3R_!2UEsHFplF0RR=kuUR@`joR! ze7ozA4j65=XHKz|XijYfCr!~vS&k^SFogA)q+*y}3#X z6|g_!{rH~(GY*}EyLAR`@6p>B_%D^~4_@*z$|!y2NtEM8f`FMROVo)Tc}l3Sl@LP>OC?l#ht2$DtXlLh1NGIR06BqD3t;xaQkhsU(xj7fD;P}~#J4!alH z!`}Iz`g2+}%TtMPw=G^9BdQsn<k{}GoidojW4V@z`}|)LsPjxE0%&$!@KMNqOvS2;GmoQ; z=R>g0GRrQmHd0HMjO%G_eA&6J)iTzJen)_%$ua5%@|LOWmaPFo5wNXv4^5`7Tf1*{6vagkX;%878m$UZWWM9etnJjt8_Ao@e1N#!V7=%~^q} zzhTG(VT*_4>f=9nHRbV;nuJ&AR*X{#)OsQf$u-51X1yETO|$M`AQcw#BgAVDdTZB@>xXKG88{IPuAB*0J28kiYRxM# z_vhr1p6tV==@VlVnY{M*6@d^d;Six2O>!5-AmP<97QcX4!PQeZ(^tsk_Xn7SG4wM@@g;2n2z*UHNtP5J}P=52^`bE_|hyZ=NObn<b6rs~Ng-fnDXdldlQpAJc_Xr(|_A3ZC82G8reCG1V>~2ABGp`osY8AeMXFxhkgJRg-IO z-x-8($3k4VP6UFU+22Q2h@59Veh14Z>%M#^bZET?-h_-X8wQ&bJszqBP&TA*$bWHN zNPJ5-4;P7lVb(NEJEqbWRkWk_J2vlmEH`n{XQrFG ztWKyccBHqpSRO4_uSeAbDsM~aZq(B}GHtUV%kgQuhQK^GVV4ME?|B;Am7pLUU%~?V zY|3Xgk=y=4An<@;sD&a&4#(u01h7dd8|V3>2Y41Bjkl2K`um%Ug3TXsNR;|F$#4|= zS~V!UF+vHOF-=PX7SsiCmT93W>f8l!#HP)#x-QKun2eK$ zSt!r(ggYHGj^Ep=)hN+;@i{6kH#3nBQ7ppz6z6L}XB_71hb z#%YW<+iR5kIC4%hxx(rbS+tDN?DIHs;Lai1WWuh`UXm>K6)H}%$&14&9mKC?jQ}y} z6oRM%x}@lWY>4wzdZq4*j2z4NPTHxSoyd7;I{E~+wd ze`SP{NmxmZpoc&ngq&9LNQzZe=a5LiQJj=iO~eRUu&CN|{H9%`K;)SqmnU8!Y1mV) zz4*vuBx+aO%py|;bElwlVRQMqL*0*eA!-`-u<}5+>{DTuWf0Dt*T+eA2scj#e#;#d zBKJ?xauK*}HLK@n-Q^e7`fX8d)$;f)={LwcC73>ew=q|OawpclR4{X)OLcD7JrC=} zHB{;jQytyDu7_+8WA`tsYz)gKA;erQI!z)4ZJQe`Y$A%T+Fv(R9EB>M7GTIyJqXZWm&QO0#Yo^H{Z5yw09qPajqCnAO$xrnnWRI7i;8QLnm1<+5(=e-mcsZhXj}lf`YoD?P&V`pW*U+4{b3 z!k={JBrHBaS|l$Pk%UFo^lAoI#T54q+i-Onzkq2vMX5WC{UpT5&HVNu;5$0`5JAWJ z4ShH{zivSLoy7uF(_Z+d<-hdC$LfpAmG?*6-5(uQ6%TJM6=h>e8c(@bA0}`qf5vYE z02Q2B)%sxXLW=vGl(V2q8&Ua4wbDrS%qAXYP?YS-$|sSdN>;pc z%F6Gwn;-(nFZ;iKxMG&@+r&(%o>{-*GG82H`3}>|GXqRs9~|$bONDyzA4mhym$#%1ofhxsP=LH*!-Nx_Tz?j+p-zMS>Z%5P^p z=bnB;zr-W^)%}$PqOBp4cYydAfj3vEfs&0r*kFLW;sev2{AI?Dw?U_^p~!Q6N{?HR z9X{#V|JZ<1k}0(XdNp?qFsBb#ix1eb3}6f3o_D|?f(w#v5EMPNk)4gZA8^fAHM?){ zUZ_EXixNc?3F6@souVEv4{Y#mIB_cn7mS|wq1whhwE`y$lqK8qDU5@0@X78c3Jwtp z^kG+j8?bMJ@1uwTrz%$q9%V5+%C6I$nISo45d<&I4S-(*5Ki!56#+hj*1+?KhiTda(gMt0eqfBmV}=ks0>aiQ z9_lG^0Bo}jcEk_cWFfmF@tX}>pr%C3OQHqdk*Syj4l=_C0g!pPR~ZM`F91-f(B2T| zqL0+0l7S@9ex?unw?olRuso=U%RrVE2bf>M-(6I-<>&_$zk3gXn8vy7>RH-jIv==` z21#pwN)YBEn6Ov?UpLHw2MD#$;Ma1BE`7gZR?rD1v;jruXBK=JN{)<^Bt0e@Jkts~ z2dQJ-%_`H-H|rKy!UqKM^u5wwd9d|s+jcWQ51q;d+)?SB^^1)C;(Ph{Z3%h{*6CL} zE>3g!304@wA#i2kNdXt!UU-8#<2(Ub1)g~lTn{O&;5vb&I<#^(`(6*`)twjn`?rkpx-oey$Fz+JeEhE18TYw)kG*}>uJnB^;Vw;|K z!!cYGdojRi#C^`Ta^CEsuH>1wgz?16Jv9rvjJHB7JV40ON1?)H&ac9RQ}yDvTB3Pa zfSV)}=E$6jA$0cI0t-L7WAHY#PBzQ%nER68HGChD6~JpkOejf`%)J?S!E_sz2JgDW zWZ$zIoiHP+-GKog?O3*S)FbiW=}Edyk`3=V(Thozck||JPwBoTkltIt$mQgzk{Qw{ z2pd?DbO6RO*g!NHJGv<0gxLVz=$ZUpH!^F|@t{u;2m=Sv*fe`~fmR=BmN4)xgJHvJ zSf@XM^O;T9#nU_0SwJXhP##-w?Xvgn{_5y%<;g4N*xZKu$DjFh=cv#zbm;5fW5)># zjNw4KqPYJ3lFGrMJYy30!(UD~_A{=dp6WJ0Ym-XL1P*ntBn*NA>!j6Q7G>t@r_ShQ zZ&(iZ=x?%r?1>xrda|?Q^lFGQyQ03?fvk$>s%H4$G=VfyQ;{1@_gx3HQ_W=R9CUGD~x;5=jC&V=r8XH^TBR^cr13gTMc9n-49VTuyA^ zw|wuaG`A?$m$`9sQ!duf-G;>x&Ic`@{i#1~a_ ztJnA0mU^?wm0`23qia7qgT1PwyQ~zs)rHHyoyN8SsF9-F#-Wl~!Au=-E-Fnz+WL58=ixY!2|SYRJ;+ix4<(~44wteGUQyY`C`R{F2C#$6~& zWgY*wCKuZ2&Q3+zx-x2_2ob{Rg5@M~IT6d(d-%*t7zyY2XR_j#Q#z{#6VJ9wLuIK> zBRWTarUb|nzZRJv*uS?K|J%w+XZrt2cKxU9`_IX?4<8?w-+y=|Sd#xn=Kc%Ctz-Yw zDaP^bmUkS>WU4~gezoTUvVs;(WD{t72Ox8lA{idFndkRXHTaVe31&<{NHT;$?}0rB zCX`>*DDUv~18@7hnvz@NNK$I|vxpJ#;rzauI_7bI=6UAe^YX1WE5sasEO#y82%eS@ z9&wU-bXrWOgZt!RbzH*`%CYA%ov-FrbF|pH3!Tb+Ke90%Xc&ip;@%3^oY=hi9FpL> zPPWrvo#$4z*MQ5aSlG-s?H70F(s!HGENwJ9jv%po&CqmpGJGAf8)MfQZ@}4p*cAd- z-McDI@cg4htBHR0CzG{QT4hg*t!ANJ(bl8Yd8T2mNY_lfhVf7fO`4NAbz{h6zkFO? zKd1Gqz^XN^pYtG%wc=;Mvme<|Ll0#y!dw6Ll$KCW7-Xn|OmR_pDT`k%SGZ*z3|j09 z$E2P*oE3HP@V1bXN!`)<0>bD2CA>OWbfqaXsKG=m8pH}vmkv-3C@baCDn9!Jek1eNU5bvOEEMyw zZVF-K;2g=d*FbFm+1^>CWU=JZ!#ImPznh*-eW3gV|Kcl|J~4jz4(2lh?1Dgkj^F-! zVs}W~B#t!UByh;gXj11oi~8E{?aZ6?{qUFeo7lVjUD0h}PGZX9ac@ z^q0;!IXKZiZ3}TB#TK}%sUGsh8sOCKW!^5JL*28nU*}2m!GeXcVfH<}F$F1csI79G z8pXmJOQ!E!1;vnP+8!#LFVhWnC4ENS*P zg%3W0C`zRSH_;i0_1kn^Uf)Y8&l_ojJd6}P0Bwz6WM|1_&Q!tmj5~#f2}R*~qwul? zcs5bL1Z+x|Y;?I0e|dD0!ru_+k%V%IPZV?v1_4@-WGw{@Ao(943Z3Ae=(2J$a=^u0 z|MX>mGB|)Uwh1Nc#t01}4HO3~F$bGaXVtJ0LjD$h7)2XD0I0^G5W^C_b25^9ihZrBDHQoKMZkPwfIG9kl1 zm2;?5BmM~taw#}+jL&c-rrWk5%4CS}%0zNHSy>>Q5Cd+>oGt)4aJsN5nrO9}Z)_q9 z;9@(olswS~hDD^Q5gHMO(sGd6+L(gHDDbNEyg)F26>qSCm1=qV2>t@bS@kY}3PC!) zJ`kb=OzyxpRiV-ZRf5#Wdh~pWj;gREVrg*Q0r^k5kZwv+W)6B<%R}$+p{6MJV{Cj? z^$=MgT%R|pHG)TJ=!8^|EorT4fh_q2VH+s7kyiuya)Pa(PHV_k&*YP@A0*`6_oSWH zFxL5NHuZ7D{=#a=aDYdgE@wHf;##40KxXm4DIVqq*U@vxxEP(8$T!V1-i94d^5>Qe zLmo@+ccMW@ocC@x=5G`J=wTTKJkZ`3|1N-nJ9_q*7qgErlnybZ?>{@f9x>@gJSu}P zQUqiz!z-^|VwV9Y)c|3a3@o-#p5vu-Tz|l%`z7S7NhBvMNZt)u%#`jFLgtMT{4Tfd zi*%SCd2QMt)BWkbba;_7^2(Mtpwfd{#$1Th)h;X-(FmMKJ&m+@v~RHzq-E@UV43a= z)p6NYr)^g0WTU=$o>L?kRY$8-Fd`^RhXBclCC!*3FEM8#%XJ9Whf&B{{Qn9)2>Aoe zk`S-vy*;oy&Zf0L*JGRE&;OsaW#k{tu70#eC+NQ3h!2cL^847mIcL>^-eHefkCuiCw3zp205Rv9X9`a^R$gp4X$sa6H zK+C^*39^Z^;NyrhjJ|`wZU>U?dY;SizUJj4UH=@l(MjJ$U6Pq@-f+xWEajTt3$=$F zy;sN1e*a%c@c(uG?|%?U|6%-lUP%I%@E?(sL-}t+(!T)sTH5wM0Q`T{hhItaLSL@_ z60=c2!j`L8h|G0m>e|;QEW6nvC-9MK0#yZKRhNsjqEg)|B%_ znLXj5gyu&xo6a>>b14!rXJgs0`E%urRQ|WCxUdCIc@)h*5F>j@5(@sQTI1MJIoU!s$ zNQ^nG1?=9+_Yt^60mC5Ls?ud=j@Dc_P9^ag=RpvK4p>m5|Ae}MK3F};qkkZ^_H7_G zDxm5rgTPIiaEhX!>Jng0_@00sh6^b&popq)CILfI7LFh#@0>^S@I%mR*g?i;(j05I z#t|PLEw~Ggh$tf}U9=F3uA3D?C)CBy9-B*CqPFz8RKJq_&}~o4PqlICyIlimz=Iy< zGZ{Kt6SmUi?B+s5&3(YPb`}wSsBY$3k|DUk#=FtBvyz?<@GjUZ99(c>(&gg9 z-)wYt0lLC+chO%cKiXr!BvnU1(m=~PmAG#yRouX%H()Tz-u*`<60&BWfMkfgqXCFd z-$9}|h%lXwhyjA=(z~wpta~TtJ<#lj*&R@8h7LX0^h9oPs&`~NVz&F>t8Z&Kggj8~ z2c#+V2=+Xn#b01`Qn|r05`Zq5Dhp3#R?rf#^OgX6{>5@L42%b)X7(S=7(;kDeR(V5 zy<{O`rmcel{Rih3ZrPt8{BOGrYLCr4t9RUv$xma&nG-^UDD@1)lIX|(vw}T@XO}rR zCoX4A;oE3g3VgcYdK~&wu>W?6vhtXd&XdWT>6*ED7WPzY&2H1LbJAeCW!+CIlnbQ% z7qwVw*`OS&U8ob@DSlbTk*P9&3+F#~_DZqWL2{d5 zLPXjkcj{%GbWrrej#=YM2b}Zl)8EA6uKrZVzX&6EDcd&-f%e1%3V?)AD}YKel1wAU zdP@3$d^u7pqSrYF>W6fA29S*3Xom#|C+LEObx3FzZ3?hDcbIY=LER1vEbdY0osq_j z6)~sX^Awy>`Pe>9r?zBV8yPU2IPsY0U0MHAkTKNe@FoA9?uZ%zu0BQ7xo`5x^^Sgd&}boXPw+IwLyDj1ME z?+m|W_mpFW#zKAg8qy`{T<4$xdu`|nJtf4R$ayKsEIOdw#PEj(%d9e4w;5=r5MfKa zMB2_jTDM_8{~c9cFypyk=;E?a$DBnaCKmrpVpLFio-WsadKJZ1;OqS5?ZxHewB$H4 z#U)aA_%~&S7K-8&Yw~DR8v?L?-V7=YZ#27OWzDObQnIvhESrvRN;I&0kUtq*oymCofMLlhf5dFz;-b2KCd15WUYf z1S!H51TYcdG0C$Ax&E&J&$Ga3xUZDH)k9OnMgom7>~43S6|J5Kk%jjN2LN|4r!aT) z3h-ikb`j@=|MA}MLfJAzValpL_jn!WQlWI@0*fD?7n{2W!L`euejY)kW= ze!)>1T#Z)fEMdDtoPq_!Q-gL{WmZ+TqN7BsSp-&M*=<-!UMaMGQqo=UG0oS|32QQhvaQPJQ@AQBx)e8b1uR4p6fp&2Dgscs@IvcKrU2Y@PowEPvb|_nE&xilvj{ z-zb)Uarw3W3x`$^^y1dGvw{XO4nfsQ3B_VNdk~uXS7-Gl!ig~Gffg<7Kk0PL6`V(AMFJw+}>lq=4rQkM`-7Y(m>C6u#@H(%$4rMc@vg>DK zI!pR8CFVgIVS_R!N!Y-@Vkt=S;SOeuUH^pX5`u6^BDIrAksHQMH9*MYCuSYN|*KCKV{K3f2=4lA>zv zAqnZm?|XikHfRuNjF9LspOe>arM)iQ3(snRR9x;^Y}E^0cZ{$<_&UL9*YwlF92Z*y zKy~2TV^pUTEU`DGO8O_->D=u1J+>IEtez6lcv`GGhuXauP}JjF4T6o|4}Wrw9|XC) z@H3ceD7%pvh6o=!(JgrL-`*CYW@%P8TO5+l`hD%l>EhSvFd1twu=e+l#o@`yCSvsc zWR_XRN6|EuJ1ZcV>^AgBuEO0Pf=_9kCN(QiAD?1kwWIK$eV7OLsMCoE!lr&bBUS$K zZSZMDB%_j-1U({sB@he=z9Ky{ij<_}{6Gu`<-2WznYJyOkVl&7RnHArUejYYwOAXb zs2*#f-HQh40U zRGutWmB7wYkkZJF+Nzky`&J#4LvJ=L5{>_gwg=d*0V0Sce*Am* z6648(+6?J$K$lufQdK?#7Xqg?r&wb0G8Ym2xj{IAVZ8?aRp>rE#HXxI19<|&&urdH z49+sOgOlL^9Lvr7+aQ%F*|U$5q5?e z3Af?#sRo8oa09GEQL(RJEw@VqdTofL+r}xN`&IgjFXkSOV%!|qrXAy>Ac{9 zVRSGhaCj8WB8!RTy-tGoJKG-9+?S5_$tJ?Q;%51us(- zfB~~sWAjwqvTpd#d1xJ!iN#g&3d zuT%hH@0jyv4C=M4t#kApVUsxEVp(mNcTi`LpD>K6v{J2rStADK7Y}{K*!`wD=-(74 zQBpm^KGvgvnSZkQmM+OY)2n@Lkq{buisoBX6x+rSN>WjZhbO^LI`gcbN2upN6JL}@ zr@xxSnpS%I)|8UBT<6BE({2=>W6f#YL+yZ&)S$kMu+N#=I>YJMPBK(oJdo3vhMGB8 zzMz~rRL^4jjseNwCGXfJeFwLgZJ=YYSo;dB)wYMo>>;5pNK?{=6HrV|aPkI-EGW;d ze>lH)JVDQjv?CTQbBxs#b@G=nfKC&LApbChIB}9rnO;5Y&l^a`hO$eb=8ukd4nxG1 z3+4ERUIl#s9?VK6DBR65C$xh};k?ukTbhS{QWF?udUim4r7G7}~Ro5AcT_BAg>E)kqTzmqrr!t87PBelZt`)6vURJOqP9i@z@piPl6fKjlymAPO^%o%Im z{%5nIF{F3FDi3gN0K*S#PETfD$kM`Mji`?PpTQOHbapn2q&Ac!Vxj87T9?NS04iQYK{}t>Hj3t{qDnormBVOCt^YmCPRN z4Vm})5V!kN$90TXs<)nx<5Pk>RN^X%EaEcN+Nz3FB()KvB*iUunXHdhn8XfbuiU(p zk!N2Hw78UWFq?NC1LScY0`ZAM@(R?;B38!uyJ&~dI;6!(hNXp5j;IZ*KJB+Kr4(gkT{S#9pIj7BvSP zL|ug8&{_E@;~z&x$hrCr?Z=U^8@jthaqMylvVjVGNX%qtZB5wFu(N9e38M1`-@1{3 zh&B4|qf+A#l|`OtcLk3Hi|rF?{4QeBa^ox$Azc_ppJX~T4}=Vh+=k_kFvXssW4H{U(MrUj zWyK`{IxejUSFj7tM;e|o{9tFt04jw(`%7Nkj?|dKj8C96tf=!BXtlXlQ=1qAYW2g% zC;7FVylmH3zx3L0>BN5T6pSQc9q~cy3@MalEV*Cw=hEu9O~Nc-P~UOaCSc;Kg$=ij z!r(#Pp(O{bvTEptbZ8EjP$_iDzI3C~B2`&)q+dEn;R9tEWKg!X(Ps%9SRBHF%dwo6 za@ve-_MFN;fqO};Ob7ECz_02`aQPWOw~5IW<8mGu5{Rp!bn+hB2*ws*-zjnza-2v# zY*CtnyIcwFX^_scj^Hg99s&ZrlKezOz&Yo$P$o(1!J7fDRuP?{`pMC7mw^@Xm5PnI zweLK*dO^V+F{%ZF7U||QGr_hx_W6V>SQaN#)3y2W6$Fv34@8yym0|)_P!%_95y)46 zPta(F9W(=#Sbq8?&4=7ny??m0{MYa`Oyg?T3H z4S^_`g%XJ&2hHrjb_1y;UMOc69`*%3J72L0FNSfu0UKavdS#aPzkFC z-Ri@(02rIW1nF6l3!GEqPue)(g@ea9bPk92@&+c3OzE+gZP_7WjjjXo(L%;1#B+c_ z;L|#{Nq*N3zB9#;uqR6+D^E&6p%SyD*24UF5G)6fz~4oLe|Tu)=-xf%bhYscnQoHW zn34+AJu!)mVG^p7CBx*n0Em5M55yd?N!0vx!Ggh1!a%i zhZ9wGfl8tNkg)FNw?#bnd2|V5u+x=`JpmHaBO|MS)$oBf3B58fr zRT-i*FXP_+d;wd)__?v#(2hL1PxI}Ua;Us^Dyw?y{>lF)a`*y&NBP-Hpwsery;rXj&jv8DeAG$I zUK_wvS+Zgjh+@3$*T7xVZ}Be2Z@*E^8MN)D_?ZqObh0ke?=K(|aExsryA}dKo-43$ zn>gf9n8w5mCM23t{0s3_4|aVJtCzw!x}Y*z6*6*;s&WUmA$^2LVA;=P=UC72cH&*# z8F9H!HIVBStNKGK{+ynwE~qKAms8LLqX~FD2RjYy0TPtVLFF*lB1(kCK!P#}Nuw#C zsrU*KNw5`6+x+8c5iubPc#Yt-QKHg0)EI>O;Tc%nNs7yoY8K%bnS@R=6k#^_C;^;q zvX^Lx;HA2w3SO;y_YMjM7iHG9Zb-=r6?LqS0DA;+tHD(GmOb}r{HuE$O1Ujaud%Dz z?c0@9-aSkE?3%1wu9(BfF=XD6eiTi{HhsuPxM)k>K8<_k_)dkCyqkOQ)#{o8KCF>a zF%qFk-~dr8Ul4NE?x=CjFZGn?M;dmG#-EKr;=kh69ZE#Pt`Xv2N&-v%C%VM{AZGr< zsQwV#ToCR*VkV#J-*`3u!uo4%Dy@s5bX_Z7oP?TW6!>WtGh+Jg$(XOPAfEtJG{NNj zP>hg3JWcZ`vlHaz$Sk>EiDl`WW=9toIS}&9$mDB1LwF>oH6__rLgQ9U`lcDg>Rp~L za(K6M?z44syBU=0M;cFGMTe&zB0h!i885RE>DQ2da)+S?{S%)7co=SbjaXZ=H#6Dp z>%JZH<4wTrF=PvycVMy5Oc)?{|Krb%B2A_b-(gl;4|Ru*1jHV7`@m_hAy);hdJjni zmM#+%TROXIyrRzY^e#(qF#zDpq5ekVi%$ImV9->1MbAV zZi`9h5p=n_F#+j;>V4EJa(N-;CmBQzCT#tb1*IoI zRt#Wy=n29h1#xO9iiZ+#Bt=O?(lUYeR8GA7Ehb!~f$W%5%+7#e(=n$tp9Dy&{pSn2 zeB3Rl{A2gNh^t=u?tE6LNF_(g~cPmF+$c}rJzroFZntsgV4_s=DzxU1c zJ0h%!Q^bp5p^MEtG%)A(%T`H49qJ_+&ND!X!SOnnslF{D@o0m(V!D7FC%K~& zaSb3=Tp|&*1x<*P>opPMNML0JS_|tx2}-H%#P%OWZcy;w+Ox|jeL$p8Y_Bi_EVeBM z1fCEyD-BCdQ7o)`xEhKDWv3kN8 z6<-opbJs}HGl=0>s$NWi?iiVTud2{E5jH4q>Q_0%lv!+~XzWE{pt53=L1hKSDH!)? zW>=s;TUAlx1wC^FC|}CRUEDf0r>v4(>+(l+m0WHIy(r1d!>q`^pcljdS^`qlEp!h8r+TOeVD+56|EIgN4vT7QANJ7Qjg->TAf7;i7uF5lS@ZUX!>pog=d<7lANsIGVONTA*{f8Z1Y7P zp}y}EjtEH&B1U28o&~wK_d9OQ`@p5YMj-|RmY4L<4N$yt=!b5*EA8fOZ^efBCh#Le?gID*?NKQ5MsHe39tnRJW!9tVk9 zbOd$|9304EF$E%m*%?^r{v7`D&xlAd#ji~)4Ow!^hJxl!3EzdN*1-VTbP>|<$?<%Q zP(I;n;U}5QU3~VYsj%tJteuD-?}U8gC>449Edq3AD9`$7(+p~NKAyPvljy6Vq_^Z% zY~;aKKr5k=_<~Fe%N>XaG3eYZt+y6sFJ0Uws;=CW{Z&PB^_ZvaWI^<#j(!x5phN)!ghGGTn?z^fx8A5)H(TYy!x{7vYpA_Y}7(KXakc@J~k2 zmxi|jOIW_%-%x+A`z$A5B%Wa;qbCmnlPx!{pXoC?RWMFp1V)=axsDByn3$H5X=kr& zkTj#H^r{@i;uogpB zYM$-B1{XfsX!zk%y|yBGbir6fR=anQRaZ+APNc1<46UomiR&PPn$zzpCdaZdgx>;dV40)588((7_JkVyh!mMH~RzmceW-~}n-u9EL!#c&G-eF0i5@W@i+6bJ>@u+Pm}Jx9RInp_&VpIX3ib%nal)=Z zMDftP!`eN@^K4sj;yDzxW8LTQIRSAeOrk8T#u0PQ#4nzU&Tv1XlAy!s9;2*GOqbdt z^cLE+SnXSZs<(5YUM=p3*Tqmg0N zET33&9Gqt(DlTY86rnzZb;5-j`N5Tl<^m{&xo-jqM=ru^1o3s8&QTXg&10I(L~wMi z`>tq1jYcvNnU2oiR&8`;Af0uzM>JHo9?$qNB=!pFL&41JSx>%H7TkWKt5MJ-2-_Jz zb81U>s_!1gO?tU%zgeo)NZc%Nv1eT35pYk8GoA7)2K6{jqdwPU(sy$l_2imdJOQ(l zmt0d0Gdb3uXr=XiXH*Xf1aS9w%A;p=POBKDlkY-Z}TU<&r&D*HhgauU>ofZL| zU)oehS+Wz28v`9@c89%cG>{*II$~-iX;~(4ZzkDwi!5Yd%K7NhDOx}^MI?hJJ0u*O z(?Rx%@W;mLnmV8OA^Jh!;T(aX;v)WG>hmqKT|8Fwi_dO*_UGJMK;A09F~UjXdzQSq z{=C#~Wh`HxFW zC8{BbZJ?E-8GR;*+$w&;@X<2F9oklJX*0}}lW^|2E#(@P`GF<_zDdb_-a3QE%S((c zcB%l*8H@X3NA(x&NmVOw8INs`#bdvJ8~;*HV#f~~`DCkBm{XG+aVh20>4Qq+pd|B1 zS$w{qLB8^|OMM<2!u!L->}`F8l6bcEP!2+s3P6Vta_JLl-+H67?((9X`f9b;oIA>N z_;Bx}ZG-IyE@_SFUE(=YI5x8Fk!_Og7QOuXceIWG28Xli@Ip<%-3tV!m#Mopn;0TN z@=-KmH(>O z^F|SY30QX=uhy!?vpqzuF!crZK&uB;EsGLWFqB{FwAC(gYhFG$LTn=~(109%M9J2r<%_~lLn@3*V_{b%|AXm;TK`w;Z zMI^pr_K=GVz<$14}cOcsup|(mxd#VPxWR-l&+gGw>SBXEiTJ=es9i9$3X_CJ1 zB^l7agv#8PhhO>d%G@8v4Y5$6rEIVeT_#U#)wO3!@uGsY1rFNv`+2D;fyS`1cN_Fh zyj_ZKX`=8KA+PV;eDDYEsgN#5#~Ci%4IMeFYOaa==sPyc;t!)kn?#MtVP6lz$HzH3 z@>_ag8zz;n>T;)-cactC0oM10n{qlv7u+}b;m1znix4_HTVC+(O=8ItSt1#CsxHA| ze|c(Bm5>1J?VHlgUvP7GW)yPrgd*cfEa76bQh6^RtYp@Vtjac64t#Op;>ge$Axorr z`sh=2#H#obn{C4@SXGu0Sxv-I%$>c(BuTTmbbCUTa>wm@sWvwbY`V^^dPypVmmyu! z$jwSpqkGb$Yv&b4WpJ6fo6YD9Sth*X2{?!AFvW_Iuk$uGsnPGzX}j*i$?h+{c`7a# ztSP|~sGO(Xk*7|ZvOWf|cVTo?=P9C4n25|&2eZ6b;V#)}tjlUEGm7CG&B|8y za~GgJS!OO#VDY+!dZ&PaQ@=geC1QKPWm)5(V{f`bkI9M zfdamjrz4(&1#p@ffvvbKYyg5BfI^;JN1ohxoVL}zDl3_|pw)ieYHW9g(0rf5i4^_B zj9&`Lq(G|5*E6)O;;vzH&)&kwbVrxo%a5;Ks-~mQy-BYKod}7T$YT|Hqgm}z$^1$O zmQmC!==fx>*NEFx@hNHurp{MeTB*K38dV2(n}kk-{&ahM;nFwhj_2uz_MV!oAqb|5 zAqf7aw4!SS=vioE4o89Y0yYo!T9*+gX~}?6Y|_Z4iqgoi2oF`)TvYS=r zR@3PsG>@K06ANS<{m2`v7eh(TM0|LF2i526v3-{!^TP8Z@3Gte?%nhu1iv%Hv|grX^5?q^?Cp zbgUIjd;t*4CW64uq9o|pj!Cq{q}Q~pv10PE^m>ge%H+&6JZ7zZ1?lQDPq!Jpd5j|Y z4)6X6DTr=p!mRZe(!s77A#S(fF%jpwjmc)hZCkK-mo z;@&w>gc5m;@4+@qCTl%!sV-3WJl?kDJB&qmbkyRSqf>iNwB^)|cXP_=OzxytfAvRc z$rJb=a?tOZrCxB`ZVUG}r~~~CM$Y8=O0u>BR?PU3r~1E>Q~N+MAYmp_e@>m;a+=~t zeti4`oelx>V=^+~%oa+^1P)huZ`6m-FSagEY<08`)NPg+?CE(1NCzoVKb5o!R zVGSHxhz}8pD=c142AOMY7dDWJNsbgE;-2}oC9hD5<7IdFi#OJVyYDn%%B^_L0T4em z&Nx7R5UV>rFV*q&MJ|h>(W;Iv;__VC1~eRI?$~~`W%3Chyo}EM*n*UiWjgKfO%nAR zoa2fMooDSbWe(#8i~^x2O&_UyGacO`YTXPgN#hi}+P3yFWZAllty!@S8(zX%7Pl(O z{8F^(i&f_&R1aTHn`p`ow4r=Kd+h<7z4{X|57E{SAHBnv+SoMC(6G_>w9k>MN|&l# z{EN~vt?cc-Wpy(e@Cpy~JqwaI49e?>>cRg|R9^ZBcPP$P$!Xftq3)2s9%^nMyNxg}&{#z~!vj6ow=t7VPHI61+B zynD1na=hyJ*p@E6lAda~T~lXu`Qv>rhn9-VP7GpB6B}!fDWM~tU3tP|_e(Mwq`9QS z#9{efKJJYuQe$-2uW&PYdRo+oQ6;B^v#OeF516g64a^_<=2;!!rg)o$BxD=pK6`>P zGz%$H)LH8wd@=3%(DuU5rq?Bi$-|xR5>q~e*SYuJVfwkUt?wnFbr0HHcAeHKl_lPI zPkyWQaK%SETwR&+ky06V!ter(ogxHJu1=2EQK* z;cwS5yk`-y*r9;z8+`(Ss;P_l9s#3@c_w-6B_uL36yBJ7C973caK(6`$UEG{DQ3$e zM0SgOBUy8qK@({VD>>rQ^6rH4bfiY~$tD|e$%rR0lHr37z9?p3_KXi#^pq=PjHE4B zhX_=fI_BCxww&W0;e zw1?DtY!*#+^YB`D6{IQzW#e~C%E8}WkL25#)3)KUeh=W=vo<8jNSuTe{NN`k^v2!k zJyX?UnDo*ySMx6VBmF2l53+V>j`m!%h^3`g#R((5m$>vSdK&Bplqq2Yh6-3&57X&g zm}Fv(S)E#YS>=0!3C~eGhLtd^7y7Fo55;g%7A7c1^--A1=H|%}jNlYvW_La81Y%ZF zd3;IOb?@FzYY0g(DThr-jSkh^fv=d8SVlo(ISz=mQ+mVmZa>JgdFi<|D^hq{ItS=+o)0`@lr-4ipQ0w7mK8jI9RqD*IIh- zg!9D9!%&oZ2zRJMrL04n4})p9NH^}AFWqFRx_Q?+Vq}~KV_m|xDETj@LAiO#I8L}k zc*R|IZyy^1-*N**$@%)Q^qrFFSY%w-TPRY*h6xq1l33H}xTv&ao<+%%n}?mmvM;Qx zAq$xycE~du$LF`bcuLilrV_3sZXHyALR(#FyI6JtkwcUJ8Eqfa!B3q{JqjXlq?E9( zP0lxjbs}Aj&Yj#gHX<8Fk!FoA-kwC&d5SKbkvLNzT*h~I;7)4veoWNBihyT} zS4~3Df(d2EG1y%CHo_lJhV0kq`8o!bAsd#}Jo<53kHbq*#;6ZRU*&a|X7_yfxm);4 zy1ZXdhfdAa&=Z1cbuam>CDKUtR(hlXTSfC$`6?eu$(ZqKT%PD^&(ZDNIOhX(7ZJ zwJ|7p{hGbqJvM`)iz)G3QUO2_0R+w7PJY|x(CczE+Hcq*dhp8MX_oGI9wG}3qi>JS zsYGr2%*3s3c3s%O$Q@_$JFn8V)&qmt{yE?WAEi|_bQ*VqPDbf+MeH|5+>R!aEvRtk z-YyF2-_2UW$)qjp{U(s~E9SfM)A8+Firtm+Nwr1zQz3C3l&7|NP50-cO^&iG(rvKPJrx5)v_4E_w{w6G~ zbX1~RrlEa0h4oUR+ZIYBvjp@L*@Cxg?yWE9ORAyg)#!j6k>l{9=5e?M70t@4Y?}1a z!LMRZRF{p7ScFiq4RP?^D-KgG31k_7ipj%cR2Pxj{(4bWk`c+5W$kfVYILma#IZc|Je4;c)lb>?g?R@=|Cq;#tGE>uUbkL+MS&)#S znqr_u|JMNY!G~qi3|`2!q=Bh&>h9NGo*N`&-oJv;`}4A0CN(Y51yU zJJisKC@9ZX)h;3Ni8v2FPeV5?)l z70?!FakkKvmcYfapDay$dFsrAM36k>Js8*+IIL{@z*?%i>&!6UA~p>aVqppkT=m~8 z4L?eRELh8)U?{O#pYV1H-G!_)h(v5mDyNNn{bb(Hu)@q4%b1gGT$Teva?*gA2TxGg z>?~;HAiG1hhH;7JjsL9R6}#Vu!G((=rb#P{DWb1^9^?9z2MQI~uk*K>kmckuXeW`B z6%%;WAAPNoU zh;n0sc^2u1Jc^n-L6C8IYb)r+rf!NCuY#l!cU0ymI1(kPrCgd}vOsr6#4PLI(pR{c zL%!^+8kepLWBzeW33(ZN->X}COz_;B9isW!$!s_$x_fe9GQ?62D`5%g`Kt+X*3<(Q z12_-lg|odH7iqoJzJc9VCjzL-SN<4G1UO$Y?1(qqBw6>Rqh`)1CFwLo6VYM@=T;M7 zrSOhze{8*fPg44-ma45g+VC*&4Sf?vt|?5n&LujuH5an)DuE`gp-gy#D2HyR7{QGZ>i28mzKhpA&?OMq#QVJE{rLsSD{}az`TbWHT~$8 z9mE>IRUy)icI8<~9P^H4@k=^Hi+Wp1>G z<!vXcTgWrqyQhG812J$V8nAkzINNNP;62i3pq4(2id7;sBwt1QJJ%b+EW)lVbf1K#@ zzgE*a6}2b>l-3r}002BtNkt=@XA1T5;98A|N@C8K4r5~vKG55>wQMg9=DAV-_^qKCmD z1`?@Mq=pwgNX>u>gh51phEaJ!ND^G~(q-FPR{;G@WBn8Noj8XE_wCc(dI!$PXOSfa zE~0rw*!cc0?LElsH7BjgHPpt5N3yD<=pF+qCX>=$r?3Ma!59rFnsCtTy&p{7g2QbW zfL#*uAENY=adxNF&(Qu@tn(NWnHa54X6x!}>^v?79w6GEYc*C|Hc$(~rIC)J3y{EO z%48lX99a4MK`$nYV5?a0Jr!*syZYcbx0+Oxe;oQ&F-n#k>f$BA zp?s#&%I|k?Lnbf>s+63^&@8D7xMOuN$|#%|HntUQRjKH%mD-T6ne@e(9Ttx}J@JCE z(}&$RC{W%NMk^};4bD2PRcgb}C6QB`B&<(WqKAD{Bax#dODe1-s2~Z)xwSfw6w5c< zoE=y!y&K0pd@?oj+A?Cf2?h3CHO5>f9g9LV!!WMC^4q#Lt9ZMt%1TrWiC!Kjah*k?YdWel=*#46S!zMDuAD%G^509^BHA@a&!Y-{Ztz+ndkY) z=A08Z(tD36Ism2`S$pE!LNC3uvkWYj9}>qG0c^*W!ryz=zog8=o$_s-okko3iAn(~@k!T_qyco%PxEdc>N*F=t z#c@RN0sjMmS6y}&Z9%Kmva>y@ZSBYDsTKV>w~xBp!q!a3u*bTCy3;2ft;f0p_mri1 z^UN8%^?;*cBeH`rtL+=vs}HgE9Jy;ebgHch-P+ra8C>g)82B_^z9`Jk`>N#XwEA5z zBwax1A(40G*Tkz;OV;outV#Bqs=(xL_`=Ud-k8eh_se|6w%BLK!S2`}k`dNpIT|gH zOWJ@%d_|#F*88?AF9R6UYpv#i318O#a42LH6S)ZI1&Z#tg8rjk_V`_8N_Ca3gd-(uGeOSjOQnIb2zq&h>VCAIFy#Aw$(MLM(O5jqUHs z5q*Cka7?7GVHrMiihKa$y+li|;ANb!XZ;u_NW1k+sup2xzg=Q!r&wZXUoF%NGqr=1 z05CqWegL4?m9cpt0rhDKQXaTi>rHU4BTI%vcMiK8u<=45{V0ER;@iON`e)}d7Fehx z?Zj!}yhtqlv?~(pf&sD*_k4p6z4=hw@aOP(OWpj3*dlqENgTeZ3T?J@l@iUfPgEGJ z5lD=26KVJ_YJA$`5j5+DT7^qr5tsSQX(q;KXHH$*`B3A7o%967LHLYfxbO-U-e?GM z%V4>KSVU(MtCQx8&Jb{x7ig`x#~X`jVfM7TC_TH%dX05TH%@E!k;{t$O75wt!J4YA zv;b09$BSnX{O`>Ul;!ykHXZsJ`zxI$rI{MbE=Io2cx-GX?bJ=yTO5m%?wlI!G+d;3 z9(|iH`4LXUJL=^zcgP8BxY#~xF6U2dwB7qU%Xo3wk>aJOqXI0<8uk;_*AU`L&#J+% z^20oQmGZiTz(6zIshEKKy}piStm|SdaeWIF&g0&&d_1%)pnNhf^EaNDY?@kj1L7;` zr`j4qU+q3G?KZ7(^U}YfQ{PXGe!)!a@1JI1icje8f5j#8c0ufz}EJ**70#I`!S#o9zq2G{_2dsAKnnep!uA(wvJ4;_QnpN zB{y(1v$4L7(9yPn7Yh1(T@(O-^zY`NXK#olgkOYT%YKt{YhP$5ierm_U_pQMMG>e^ z|JM1La&L11vQdS-12y*nbsTtCl7a00YVK@i{QS=5(TG+~_hA8m72=znu@35d0njr3fxA{E@Yg5& zu7Y!ONu`4n)SE!vT!ah&JOCrdkz6Co7`WOvIsQlV*9(?gO}&lh13!Q`93Wc;a2^Q% zU(>&N*G#WdZ{4T>7n>*#LEjD-&?W)ir*Fw`LI3RF|3GhBMb}Ni`xFv%E!Y2t>0i)W zMIqEzL##l38VlOi!9}4!WqbZ0UlaX#?f=bHbhl^;$omi6A6@#ppukOXi2j!5Gf?iN*Iy#@D&=3;CMp&z(?=cem)A;w`v8M1MkWjia3H>?dKM zZL|jzN5ON`ywWY?YofnyiSSRR;JK+-?H1l2roX7Ss;(|c4se51%P9Z=Q1AAp86vD`O>X7s^mKi9>r+3ap1Xj3r6 zHKDsRSl-%t)+#76i1XhRIt8cw6uLEQ-7SP|cbCxJnF_}qAjl39ddGTG2*DnV_EYHA z>~yz~ohumPS`XcwkutbbK>N-BK(**iArm(++E1Zdv(Vi_1wLSiYeIKtp499sHc5~W zqSQ^HR9`UKPoZ10&D}yopLU(7FlLxM6nV@}~evz9(7-3+vpF+20mAi#jW5E#D zgznBHH}CTN_t(}NVmc0t_EYHA>~Xh{b{ZJsn$SOFj2pKA{4J!I4o3SabZeHlTWC5T z3~^29?#yuWmN>tK1`EJwKZS132G@ z!5Um?wE>KAUFq&5<=Fx;(5}gUrPTP zu)&pF`@tC3mF|w%H!FDjR6G5Fx)Ste+}H= zN*dq67}u5Vj@&n=OZ--nI|9S~Qu^1>4X(s=4#v2y^bfK7#;LB~N|YDZjw^zn_)?ID W0gdti0142K8t91E1tbLM)&B#cW766H literal 0 HcmV?d00001 diff --git a/README.md b/README.md index 831ba85..f3824e4 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,29 @@ npm run dev npm run build npm run preview ``` + +## Дизайн-система + +Токены в `src/tokens.css`. + +Основные цвета: + +- `#2BB4A8` — primary (teal), бренд и основные CTA +- `#E04E44` — accent (красный), выделенные действия +- `#F5EDDF` — warm hero, тёплые приветственные блоки +- `#2E9B6B` — success, `#E8A13C` — warning, `#D94141` — danger + +Шрифты: **PT Sans** (основной), **PT Sans Narrow** (display/заголовки). + +## Экраны + +Описания всех экранов — в `src/docs.js`. Ключевые группы и цели пользователя: + +- **Главная** (`home:*`) — варианты стартового экрана: Карточки, Лента, Таймлайн, Таймлайн X, Светлая плитка. Цель: быстрый старт — запись или ближайший приём. +- **Запись** (`booking-*`) — 4-шаговый флоу: специализация → врач → дата/время → подтверждение → успех. +- **Врачи** (`doctors`, `doctor`) — список и карточка врача для решения «записаться». +- **Приёмы и медкарта** (`appts`, `appt`, `medcard`, `results`, `result*`) — контроль визитов, заключения, анализы, амбулаторная карта. +- **Восстановление и тесты** (`recovery`, `audiotest`) — трекер после операции и скрининг слуха. +- **Чаты** (`chat`, `chat:ai`, `chat:doctor-*`, `chat:operator`) — AI-помощник, врач, администратор. +- **Сервис** (`profile`, `qr`, `telemed`, `notifications`, `search`, `contacts`, `prices`, `articles`, `article`) — профиль, идентификация, видеозвонок, уведомления, поиск, контент. +- **DEV/Docs** (`dev-colors`, `dev-examples`, `docs`) — палитра, примеры компонентов и гайд по экранам. diff --git a/build_deck.py b/build_deck.py new file mode 100644 index 0000000..c033732 --- /dev/null +++ b/build_deck.py @@ -0,0 +1,913 @@ +"""Собирает MEETING_2026-04-23.pptx из содержания MEETING_2026-04-23.md. + +Запуск: python3 build_deck.py +Выход: MEETING_2026-04-23.pptx в корне проекта +""" + +from pptx import Presentation +from pptx.util import Inches, Pt, Emu +from pptx.dml.color import RGBColor +from pptx.enum.shapes import MSO_SHAPE +from pptx.enum.text import PP_ALIGN, MSO_ANCHOR +from pptx.oxml.ns import qn +from copy import deepcopy + +# ---------- Бренд клиники ---------- +PRIMARY = RGBColor(0x2B, 0xB4, 0xA8) +PRIMARY_DARK = RGBColor(0x1C, 0x8A, 0x80) +PRIMARY_50 = RGBColor(0xE8, 0xF7, 0xF5) +ACCENT = RGBColor(0xE0, 0x4E, 0x44) +WARM = RGBColor(0xF5, 0xED, 0xDF) +SUCCESS = RGBColor(0x2E, 0x9B, 0x6B) +WARNING = RGBColor(0xE8, 0xA1, 0x3C) +DANGER = RGBColor(0xD9, 0x41, 0x41) +FG = RGBColor(0x1F, 0x29, 0x37) +FG2 = RGBColor(0x4B, 0x55, 0x63) +FG3 = RGBColor(0x9C, 0xA3, 0xAF) +BG = RGBColor(0xFF, 0xFF, 0xFF) +SOFT = RGBColor(0xF3, 0xF4, 0xF6) + +FONT_SANS = "PT Sans" +FONT_DISPLAY = "PT Sans Narrow" + +# ---------- Геометрия 16:9 ---------- +prs = Presentation() +prs.slide_width = Inches(13.333) +prs.slide_height = Inches(7.5) +SW = prs.slide_width +SH = prs.slide_height + +BLANK_LAYOUT = prs.slide_layouts[6] # Blank + +MARGIN_L = Inches(0.6) +MARGIN_R = Inches(0.6) +MARGIN_T = Inches(0.45) +CONTENT_W = SW - MARGIN_L - MARGIN_R + +FOOTER_TXT = "Клиника УГН · Приложение · 23 апр 2026" + +# ---------- Примитивы ---------- +def add_rect(slide, x, y, w, h, fill=None, line=None, line_w=None): + shp = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, x, y, w, h) + shp.shadow.inherit = False + if fill is None: + shp.fill.background() + else: + shp.fill.solid() + shp.fill.fore_color.rgb = fill + if line is None: + shp.line.fill.background() + else: + shp.line.color.rgb = line + if line_w is not None: + shp.line.width = line_w + return shp + +def add_text(slide, x, y, w, h, text, *, size=16, bold=False, color=FG, + font=FONT_SANS, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, + line_spacing=None): + tb = slide.shapes.add_textbox(x, y, w, h) + tf = tb.text_frame + tf.word_wrap = True + tf.margin_left = tf.margin_right = Emu(0) + tf.margin_top = tf.margin_bottom = Emu(0) + tf.vertical_anchor = anchor + lines = text.split("\n") + for i, ln in enumerate(lines): + p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() + p.alignment = align + if line_spacing is not None: + p.line_spacing = line_spacing + r = p.add_run() + r.text = ln + r.font.name = font + r.font.size = Pt(size) + r.font.bold = bold + r.font.color.rgb = color + return tb + +def add_rich(slide, x, y, w, h, runs, *, align=PP_ALIGN.LEFT, + anchor=MSO_ANCHOR.TOP, size=14, line_spacing=1.15): + """runs = [(text, {'bold':bool,'color':RGB,'size':int,'font':str}), ...]""" + tb = slide.shapes.add_textbox(x, y, w, h) + tf = tb.text_frame + tf.word_wrap = True + tf.margin_left = tf.margin_right = Emu(0) + tf.margin_top = tf.margin_bottom = Emu(0) + tf.vertical_anchor = anchor + paragraphs = [[]] + for text, style in runs: + if text == "\n": + paragraphs.append([]) + else: + paragraphs[-1].append((text, style)) + for i, para in enumerate(paragraphs): + p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() + p.alignment = align + p.line_spacing = line_spacing + for text, style in para: + r = p.add_run() + r.text = text + r.font.name = style.get("font", FONT_SANS) + r.font.size = Pt(style.get("size", size)) + r.font.bold = style.get("bold", False) + r.font.color.rgb = style.get("color", FG) + return tb + +def footer(slide, idx, total): + add_rect(slide, 0, SH - Inches(0.32), SW, Inches(0.32), fill=PRIMARY_50) + add_text(slide, MARGIN_L, SH - Inches(0.3), Inches(8), Inches(0.25), + FOOTER_TXT, size=10, color=FG2) + add_text(slide, SW - Inches(1.2), SH - Inches(0.3), Inches(0.6), Inches(0.25), + f"{idx} / {total}", size=10, color=FG2, align=PP_ALIGN.RIGHT) + +def title_bar(slide, title, eyebrow=None): + """Верхняя полоска: маленькая подпись категории + крупный заголовок.""" + y = MARGIN_T + if eyebrow: + add_text(slide, MARGIN_L, y, CONTENT_W, Inches(0.3), + eyebrow.upper(), size=11, bold=True, color=PRIMARY, + font=FONT_SANS) + y += Inches(0.34) + add_text(slide, MARGIN_L, y, CONTENT_W, Inches(0.6), + title, size=28, bold=True, color=FG, font=FONT_DISPLAY) + y += Inches(0.7) + # Тонкая линия + add_rect(slide, MARGIN_L, y, Inches(1.2), Inches(0.04), fill=PRIMARY) + return y + Inches(0.25) + +def new_slide(): + return prs.slides.add_slide(BLANK_LAYOUT) + +# ---------- Стилизованная таблица ---------- +def add_table(slide, x, y, w, h, data, *, + header=True, col_widths=None, row_heights=None, + header_fill=PRIMARY, header_fg=BG, body_fg=FG, + alt_fill=PRIMARY_50, size=11, header_size=11): + rows, cols = len(data), len(data[0]) + tbl_shp = slide.shapes.add_table(rows, cols, x, y, w, h).table + if col_widths: + for i, cw in enumerate(col_widths): + tbl_shp.columns[i].width = cw + if row_heights: + for i, rh in enumerate(row_heights): + tbl_shp.rows[i].height = rh + for ri, row in enumerate(data): + for ci, cell_val in enumerate(row): + cell = tbl_shp.cell(ri, ci) + cell.margin_left = cell.margin_right = Inches(0.08) + cell.margin_top = cell.margin_bottom = Inches(0.05) + # Fill + if header and ri == 0: + cell.fill.solid(); cell.fill.fore_color.rgb = header_fill + elif alt_fill and ri % 2 == 0 and not (header and ri == 0): + cell.fill.solid(); cell.fill.fore_color.rgb = alt_fill + else: + cell.fill.solid(); cell.fill.fore_color.rgb = BG + # Text + tf = cell.text_frame + tf.word_wrap = True + tf.clear() + lines = str(cell_val).split("\n") + for i, ln in enumerate(lines): + p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() + p.alignment = PP_ALIGN.LEFT + r = p.add_run() + r.text = ln + r.font.name = FONT_SANS + if header and ri == 0: + r.font.size = Pt(header_size) + r.font.bold = True + r.font.color.rgb = header_fg + else: + r.font.size = Pt(size) + r.font.color.rgb = body_fg + return tbl_shp + +# ============================================================ +# СЛАЙДЫ +# ============================================================ + +def slide_title(): + s = new_slide() + # Фоновая тёплая полоса слева + add_rect(s, 0, 0, Inches(4.5), SH, fill=WARM) + # Teal-акцент + add_rect(s, Inches(4.5), 0, Inches(0.12), SH, fill=PRIMARY) + # Левый блок — мета + add_text(s, Inches(0.7), Inches(0.7), Inches(3.5), Inches(0.3), + "КЛИНИКА УГН", size=12, bold=True, color=PRIMARY_DARK) + add_text(s, Inches(0.7), Inches(1.1), Inches(3.5), Inches(0.3), + "Мобильное приложение", size=13, color=FG2) + add_text(s, Inches(0.7), SH - Inches(1.2), Inches(3.5), Inches(0.3), + "Встреча", size=12, color=FG3) + add_text(s, Inches(0.7), SH - Inches(0.9), Inches(3.5), Inches(0.4), + "23 апреля 2026", size=18, bold=True, color=FG, font=FONT_DISPLAY) + # Правый блок — заголовок + add_text(s, Inches(5.1), Inches(2.2), Inches(7.6), Inches(1.0), + "Приоритеты развития", size=48, bold=True, color=FG, font=FONT_DISPLAY) + add_text(s, Inches(5.1), Inches(3.2), Inches(7.6), Inches(0.8), + "мобильного приложения", size=36, color=FG2, font=FONT_DISPLAY) + # Подзаголовок + add_rect(s, Inches(5.1), Inches(4.5), Inches(0.8), Inches(0.04), fill=PRIMARY) + add_text(s, Inches(5.1), Inches(4.7), Inches(7.6), Inches(1.5), + "Джентльменский набор для всех\nи четыре группы сегментов повторных", + size=20, color=FG2, font=FONT_DISPLAY) + return s + +def slide_agenda(): + s = new_slide() + y = title_bar(s, "Повестка", "Что обсуждаем") + items = [ + ("1", "Контекст и критерии приоритизации", + "Факты о потоке пациентов, 10 бизнес-сегментов, рамка оценки"), + ("2", "Текущие функции — что есть", + "Приоритеты использования и точки усиления"), + ("3", "Джентльменский набор для всех пациентов", + "Транзакционная база (Фаза 1) + коммуникационная надстройка (Фаза 1.5)"), + ("4", "10 сегментов × первичный/повторный в приложении", + "Матрица и 4 группы сегментных модулей"), + ("5", "Порядок внедрения и решение", + "Фаза 1 → Фаза 1.5 → Фаза 2 (A → C → B → D). Что выбираем сегодня"), + ] + row_h = Inches(0.95) + for i, (num, title, sub) in enumerate(items): + yy = y + row_h * i + # кружок-номер + circle = s.shapes.add_shape(MSO_SHAPE.OVAL, MARGIN_L, yy, + Inches(0.6), Inches(0.6)) + circle.shadow.inherit = False + circle.fill.solid(); circle.fill.fore_color.rgb = PRIMARY + circle.line.fill.background() + tf = circle.text_frame + tf.margin_left = tf.margin_right = Emu(0) + tf.margin_top = tf.margin_bottom = Emu(0) + p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER + r = p.add_run(); r.text = num + r.font.name = FONT_DISPLAY; r.font.size = Pt(20); r.font.bold = True + r.font.color.rgb = BG + add_text(s, MARGIN_L + Inches(0.9), yy + Inches(0.02), + Inches(11), Inches(0.4), title, size=18, bold=True, + color=FG, font=FONT_DISPLAY) + add_text(s, MARGIN_L + Inches(0.9), yy + Inches(0.45), + Inches(11), Inches(0.4), sub, size=13, color=FG2) + return s + +def slide_facts(): + s = new_slide() + y = title_bar(s, "Ключевые факты о потоке", "Контекст") + # Большая статистика слева + add_text(s, MARGIN_L, y + Inches(0.2), Inches(5), Inches(1.6), + "2 из 3", size=88, bold=True, color=PRIMARY, font=FONT_DISPLAY) + add_text(s, MARGIN_L, y + Inches(1.8), Inches(5.5), Inches(0.7), + "пациентов клиники — повторные", + size=18, color=FG, font=FONT_DISPLAY) + add_text(s, MARGIN_L, y + Inches(2.4), Inches(5.5), Inches(1.4), + "Значительная доля — повторные внутри одного эпизода лечения: " + "после первого визита ещё 1–3+ приёма, процедуры, контроль.", + size=13, color=FG2) + # Правый блок — выручка + bx = Inches(7.0); bw = Inches(5.7) + add_rect(s, bx, y + Inches(0.1), bw, Inches(4.8), + fill=PRIMARY_50, line=PRIMARY, line_w=Pt(0.5)) + add_text(s, bx + Inches(0.3), y + Inches(0.3), bw - Inches(0.6), Inches(0.4), + "ВЫРУЧКА ~200 МЛН / ГОД · 10 СЕГМЕНТОВ", size=11, bold=True, + color=PRIMARY_DARK) + lead_rows = [ + ("Амбулаторный поток", "тысячи/мес", "~120 млн"), + ("Взрослая хирургия (FESS, полипы, перегородка)", "300 оп/год", "~30 млн"), + ("Детская аденоидная хирургия", "400–500 оп/год", "~20–30 млн"), + ("Сурдология (слуховые аппараты)", "высокая конверсия", "~20 млн"), + ("Хроники, ЧБД, вазотомия, пульмо, храпуны, фониатрия, check-up", "6 сегментов", "остальное"), + ] + ty = y + Inches(0.8) + for name, vol, rev in lead_rows: + add_text(s, bx + Inches(0.3), ty, Inches(3.5), Inches(0.3), + name, size=12, bold=True, color=FG) + add_text(s, bx + Inches(0.3), ty + Inches(0.28), Inches(3.5), Inches(0.24), + vol, size=10, color=FG3) + add_text(s, bx + bw - Inches(1.8), ty, Inches(1.5), Inches(0.4), + rev, size=14, bold=True, color=ACCENT, align=PP_ALIGN.RIGHT, + font=FONT_DISPLAY) + ty += Inches(0.76) + return s + +def slide_frame(): + s = new_slide() + y = title_bar(s, "Приложение = удержание, не привлечение", "Продуктовая рамка") + # Две колонки + colw = Inches(6.15) + gap = Inches(0.23) + # Левая + lx = MARGIN_L + add_rect(s, lx, y + Inches(0.1), colw, Inches(4.7), + fill=SOFT, line=None) + add_text(s, lx + Inches(0.3), y + Inches(0.25), colw - Inches(0.6), Inches(0.4), + "ПЕРВИЧНЫЕ", size=12, bold=True, color=FG3) + add_text(s, lx + Inches(0.3), y + Inches(0.6), colw - Inches(0.6), Inches(0.6), + "Приходят через веб", size=22, bold=True, color=FG, font=FONT_DISPLAY) + add_rich(s, lx + Inches(0.3), y + Inches(1.3), colw - Inches(0.6), Inches(3.4), [ + ("Главный job: ", {"bold": True, "color": FG}), + ("«куда мне обратиться, как записаться»", {}), + ("\n", {}), + ("Источник: ", {"bold": True, "color": FG}), + ("сайт, SEO, реклама, сарафан", {}), + ("\n", {}), + ("В приложении: ", {"bold": True, "color": FG}), + ("базовый минимум, который уже есть", {}), + ("\n", {}), + ("Приоритет развития: ", {"bold": True, "color": FG}), + ("низкий — веб работает лучше", {}), + ], size=13, line_spacing=1.3) + # Правая (основная) + rx = lx + colw + gap + add_rect(s, rx, y + Inches(0.1), colw, Inches(4.7), + fill=PRIMARY_50, line=PRIMARY, line_w=Pt(1)) + add_text(s, rx + Inches(0.3), y + Inches(0.25), colw - Inches(0.6), Inches(0.4), + "ПОВТОРНЫЕ · 2 ИЗ 3", size=12, bold=True, color=PRIMARY_DARK) + add_text(s, rx + Inches(0.3), y + Inches(0.6), colw - Inches(0.6), Inches(0.6), + "Приоритет приложения", size=22, bold=True, color=FG, font=FONT_DISPLAY) + add_rich(s, rx + Inches(0.3), y + Inches(1.3), colw - Inches(0.6), Inches(3.4), [ + ("Главный job: ", {"bold": True, "color": FG}), + ("«что делать сейчас по моему лечению»", {}), + ("\n", {}), + ("Источник: ", {"bold": True, "color": FG}), + ("приложение уже установлено, push / сам открывает", {}), + ("\n", {}), + ("В приложении: ", {"bold": True, "color": FG}), + ("специализированный модуль сегмента", {}), + ("\n", {}), + ("Приоритет развития: ", {"bold": True, "color": FG}), + ("высокий — закрывает 2/3 потока", {}), + ], size=13, line_spacing=1.3) + # Сноска-исключения + add_text(s, MARGIN_L, y + Inches(5.1), CONTENT_W, Inches(0.4), + "Исключения: сурдология, хирургия, АСИТ — после первого визита пациент «уходит думать», и вернуть его может только приложение.", + size=11, color=FG2) + return s + +def slide_current_functions(): + s = new_slide() + y = title_bar(s, "Текущие функции — утилита и приоритет", "Что есть сегодня") + # Три колонки: зелёная, жёлтая, красная + groups = [ + ("ЯДРО ЦЕННОСТИ", SUCCESS, [ + ("Запись на приём", "Единственная функция, приводящая новых."), + ("Ближайший приём", "3–10 открытий перед визитом. Самый частый экран."), + ]), + ("ПОДДЕРЖКА", WARNING, [ + ("Список врачей", "Помогает при первом визите, повторный не открывает."), + ("Чат с оператором", "Ограничен одним собеседником — основное развитие в §3."), + ("Профиль", "Редко открывается, но критичен для персонализации."), + ]), + ("НИША", DANGER, [ + ("Семейный профиль", "Критичен для 20–30% (дети, пожилые)."), + ("Контакты", "1–2 открытия за всё время. Функция «галочка»."), + ]), + ] + cw = (CONTENT_W - Inches(0.5)) / 3 + for i, (label, col, items) in enumerate(groups): + cx = MARGIN_L + (cw + Inches(0.25)) * i + add_rect(s, cx, y, cw, Inches(0.5), fill=col) + add_text(s, cx + Inches(0.3), y + Inches(0.1), cw - Inches(0.6), Inches(0.3), + label, size=12, bold=True, color=BG) + yy = y + Inches(0.7) + for title, desc in items: + add_text(s, cx, yy, cw, Inches(0.35), title, + size=15, bold=True, color=FG, font=FONT_DISPLAY) + add_text(s, cx, yy + Inches(0.38), cw, Inches(0.9), desc, + size=11, color=FG2) + yy += Inches(1.15) + # Подвал — вывод + add_rect(s, MARGIN_L, Inches(6.55), CONTENT_W, Inches(0.55), + fill=WARM) + add_text(s, MARGIN_L + Inches(0.3), Inches(6.66), CONTENT_W - Inches(0.6), Inches(0.4), + "Слабое место: нет причины открывать приложение между визитами. Это и закрывает джентльменский набор.", + size=13, bold=True, color=FG, font=FONT_DISPLAY) + return s + +def slide_three_layers(): + s = new_slide() + y = title_bar(s, "Три слоя работы", "План") + box_h = Inches(1.75) + gap = Inches(0.15) + # Слой 1 + y1 = y + Inches(0.1) + add_rect(s, MARGIN_L, y1, CONTENT_W, box_h, + fill=PRIMARY_50, line=PRIMARY, line_w=Pt(1)) + add_text(s, MARGIN_L + Inches(0.3), y1 + Inches(0.2), Inches(3), Inches(0.3), + "ФАЗА 1 · ТРАНЗАКЦИОННАЯ БАЗА", size=11, bold=True, color=PRIMARY_DARK) + add_text(s, MARGIN_L + Inches(0.3), y1 + Inches(0.5), Inches(11), Inches(0.5), + "Всем пациентам · быстрый MVP · минимум рисков", + size=18, bold=True, color=FG, font=FONT_DISPLAY) + add_text(s, MARGIN_L + Inches(0.3), y1 + Inches(1.0), CONTENT_W - Inches(0.6), Inches(0.7), + "План лечения · Результаты и медкарта · Заказ справок · " + "(+ уже есть: запись, ближайший приём, чат с оператором, статьи)", + size=13, color=FG2) + # Слой 1.5 + y15 = y1 + box_h + gap + add_rect(s, MARGIN_L, y15, CONTENT_W, box_h, + fill=WARM, line=WARNING, line_w=Pt(1)) + add_text(s, MARGIN_L + Inches(0.3), y15 + Inches(0.2), Inches(4), Inches(0.3), + "ФАЗА 1.5 · КОММУНИКАЦИОННАЯ НАДСТРОЙКА", size=11, bold=True, color=WARNING) + add_text(s, MARGIN_L + Inches(0.3), y15 + Inches(0.5), Inches(11), Inches(0.5), + "Поверх стабильной базы · отдельный регламент и безопасность", + size=18, bold=True, color=FG, font=FONT_DISPLAY) + add_text(s, MARGIN_L + Inches(0.3), y15 + Inches(1.0), CONTENT_W - Inches(0.6), Inches(0.7), + "Чат с медицинским консьержем (не прямой с лечащим) · AI-помощник на RAG в shadow-mode", + size=13, color=FG2) + # Слой 2 + y2 = y15 + box_h + gap + add_rect(s, MARGIN_L, y2, CONTENT_W, box_h, + fill=SOFT, line=FG3, line_w=Pt(0.5)) + add_text(s, MARGIN_L + Inches(0.3), y2 + Inches(0.2), Inches(3), Inches(0.3), + "ФАЗА 2 · СЕГМЕНТНЫЕ МОДУЛИ", size=11, bold=True, color=FG3) + add_text(s, MARGIN_L + Inches(0.3), y2 + Inches(0.5), Inches(11), Inches(0.5), + "Углубление по группам сегментов", + size=18, bold=True, color=FG, font=FONT_DISPLAY) + # 4 мини-плитки + tiles = [("A", "Самопомощь"), ("C", "Пред-оп"), ("B", "Сурдология"), ("D", "АСИТ + астма")] + tw = (CONTENT_W - Inches(0.6) - Inches(0.15) * 3) / 4 + for i, (code, name) in enumerate(tiles): + tx = MARGIN_L + Inches(0.3) + (tw + Inches(0.15)) * i + ty = y2 + Inches(1.05) + add_rect(s, tx, ty, tw, Inches(0.55), fill=BG, line=PRIMARY, line_w=Pt(0.75)) + add_text(s, tx + Inches(0.15), ty + Inches(0.08), Inches(0.4), Inches(0.4), + code, size=18, bold=True, color=PRIMARY, font=FONT_DISPLAY) + add_text(s, tx + Inches(0.55), ty + Inches(0.13), tw - Inches(0.6), Inches(0.4), + name, size=11, bold=True, color=FG, font=FONT_DISPLAY) + return s + +def slide_phase1_base(): + s = new_slide() + y = title_bar(s, "Фаза 1 · Транзакционная база", "Джентльменский набор, часть 1") + add_text(s, MARGIN_L, y + Inches(0.0), CONTENT_W, Inches(0.4), + "Детерминированные функции без LLM и прямого врачебного диалога. Быстрый MVP, минимум рисков.", + size=13, color=FG2) + data = [ + ["Функция", "Статус", "Суть"], + ["Запись на приём", "Есть", "Запись 24/7, ближайшие окна."], + ["Ближайший приём", "Есть", "Что, где, когда — снимает тревогу перед визитом."], + ["Чат с оператором", "Есть", "Справки, переносы, счета, расписание (человеческий канал)."], + ["Статьи / база знаний", "Есть", "Источник правды для будущего RAG."], + ["План лечения с приёма", "Нет — фундамент", "Структурированные назначения, календарь, напоминания, ссылки на процедуры."], + ["Результаты и медкарта", "Нет — новое", "Анализы, аудиограммы, снимки, заключения. Срок годности виден — критично для пред-опа."], + ["Заказ справок и финдокументов", "Нет — новое", "ФНС-вычет 13%, справки работодателю, счета. Снимает нагрузку с администраторов."], + ] + add_table(s, MARGIN_L, y + Inches(0.55), + CONTENT_W, Inches(4.6), + data, + col_widths=[Inches(3.3), Inches(2.3), CONTENT_W - Inches(5.6)], + size=12, header_size=12) + # Порядок работ + add_rect(s, MARGIN_L, Inches(6.25), CONTENT_W, Inches(0.85), + fill=PRIMARY_50, line=PRIMARY, line_w=Pt(0.5)) + add_rich(s, MARGIN_L + Inches(0.3), Inches(6.35), CONTENT_W - Inches(0.6), Inches(0.7), [ + ("Порядок внутри Фазы 1: ", {"bold": True, "color": PRIMARY_DARK}), + ("1) План лечения — фундамент · ", {}), + ("2) Результаты / медкарта · ", {}), + ("3) Заказ справок. ", {}), + ("Критическая зависимость — МИС: ", {"bold": True, "color": FG}), + ("отдаёт ли API структурированные назначения или план заполняет врач в виджете (см. вопрос №1).", {}), + ], size=12, line_spacing=1.3) + return s + +def slide_phase1_5_overlay(): + s = new_slide() + y = title_bar(s, "Фаза 1.5 · Коммуникационная надстройка", "Джентльменский набор, часть 2") + add_text(s, MARGIN_L, y + Inches(0.0), CONTENT_W, Inches(0.6), + "Живой диалог по поводу лечения. Строится поверх стабильной Фазы 1, требует отдельных регламентов.", + size=13, color=FG2) + # Две большие карточки + cw = (CONTENT_W - Inches(0.3)) / 2 + cy = y + Inches(0.7) + ch = Inches(4.5) + # Консьерж + lx = MARGIN_L + add_rect(s, lx, cy, cw, ch, fill=BG, line=PRIMARY, line_w=Pt(1)) + add_rect(s, lx, cy, cw, Inches(0.55), fill=PRIMARY) + add_text(s, lx + Inches(0.3), cy + Inches(0.12), cw - Inches(0.6), Inches(0.4), + "ЧАТ С МЕДИЦИНСКИМ КОНСЬЕРЖЕМ", size=12, bold=True, color=BG) + add_text(s, lx + Inches(0.3), cy + Inches(0.75), cw - Inches(0.6), Inches(0.5), + "Не прямой с лечащим врачом", + size=18, bold=True, color=FG, font=FONT_DISPLAY) + add_text(s, lx + Inches(0.3), cy + Inches(1.3), cw - Inches(0.6), Inches(2.4), + "Дежурный врач / фельдшер / медсестра отвечает по протоколам " + "на 80% рутины: «можно ли совмещать с X», «нормально ли, что третий день болит», " + "«когда повторно сдать анализ».\n\n" + "Эскалация лечащему — только клинически значимое.\n\n" + "Буфер SLA и защита от выгорания врачей.", + size=12, color=FG2) + add_rect(s, lx + Inches(0.3), cy + ch - Inches(0.85), cw - Inches(0.6), Inches(0.7), + fill=WARM) + add_rich(s, lx + Inches(0.45), cy + ch - Inches(0.75), cw - Inches(0.9), Inches(0.55), [ + ("Риск: ", {"bold": True, "color": DANGER}), + ("перегрузка врачей. ", {}), + ("Митигация: ", {"bold": True, "color": SUCCESS}), + ("консьерж-слой + тарификация эскалаций.", {}), + ], size=11, line_spacing=1.3) + # AI + rx = lx + cw + Inches(0.3) + add_rect(s, rx, cy, cw, ch, fill=BG, line=WARNING, line_w=Pt(1)) + add_rect(s, rx, cy, cw, Inches(0.55), fill=WARNING) + add_text(s, rx + Inches(0.3), cy + Inches(0.12), cw - Inches(0.6), Inches(0.4), + "AI-ПОМОЩНИК (RAG, 24/7)", size=12, bold=True, color=BG) + add_text(s, rx + Inches(0.3), cy + Inches(0.75), cw - Inches(0.6), Inches(0.5), + "Запуск в shadow-mode", + size=18, bold=True, color=FG, font=FONT_DISPLAY) + add_text(s, rx + Inches(0.3), cy + Inches(1.3), cw - Inches(0.6), Inches(2.4), + "Знает: базу знаний клиники, статьи, план лечения пациента, медкарту, историю приёмов.\n\n" + "Объясняет назначения, ищет ответ в статьях, оценивает «норма или срочно», " + "эскалирует консьержу/врачу.\n\n" + "Отвечает ночью, когда человеческие каналы недоступны.", + size=12, color=FG2) + add_rect(s, rx + Inches(0.3), cy + ch - Inches(0.85), cw - Inches(0.6), Inches(0.7), + fill=WARM) + add_rich(s, rx + Inches(0.45), cy + ch - Inches(0.75), cw - Inches(0.9), Inches(0.55), [ + ("Риск: ", {"bold": True, "color": DANGER}), + ("галлюцинация LLM = юр./репутация. ", {}), + ("Митигация: ", {"bold": True, "color": SUCCESS}), + ("shadow-mode, валидация через консьержа, метрики точности перед прямым режимом.", {}), + ], size=11, line_spacing=1.3) + return s + +def slide_primary_vs_returning(): + s = new_slide() + y = title_bar(s, "Первичный vs повторный в приложении", "Фундаментальное деление") + data = [ + ["Линза", "Первичный в приложении", "Повторный в приложении"], + ["Главный job", + "«Куда обратиться и как записаться»", + "«Что делать сейчас по моему лечению»"], + ["Источник", + "Сайт, SEO, реклама, сарафан", + "Уведомление, пациент сам открывает"], + ["Частота открытия", + "1–3 раза до визита", + "Ежедневно в активном эпизоде"], + ["Что нужно в приложении", + "Базовый минимум (есть)", + "Специализированный модуль сегмента"], + ["Приоритет развития", + "Низкий — веб работает лучше", + "Высокий — 2/3 потока"], + ] + add_table(s, MARGIN_L, y + Inches(0.2), + CONTENT_W, Inches(4.4), + data, + col_widths=[Inches(3.0), Inches(4.55), Inches(4.55)], + size=13, header_size=12, + header_fill=FG) + add_rect(s, MARGIN_L, Inches(6.1), CONTENT_W, Inches(1.0), + fill=PRIMARY_50, line=PRIMARY, line_w=Pt(0.5)) + add_rich(s, MARGIN_L + Inches(0.3), Inches(6.2), CONTENT_W - Inches(0.6), Inches(0.85), [ + ("Исключения: ", {"bold": True, "color": PRIMARY_DARK}), + ("сегменты, где «первичный в приложении» ≠ «первый визит» — сурдология, хирургия, АСИТ. " + "После первого визита пациент уходит, и вернуть его может только приложение. " + "Эти сегменты требуют отдельных «первичных» сценариев удержания.", {}), + ], size=13, line_spacing=1.3) + return s + +def slide_segment_matrix(): + s = new_slide() + y = title_bar(s, "10 сегментов × первичный / повторный", "Матрица") + header = ["#", "Сегмент", "Объём · вклад", "Повторный — что нужно", "Группа"] + rows = [ + ["1", "Заблокированный нос (FESS, полипы)", "300 оп · 30 млн", + "Пред-оп бегунок → восстановление → контроль 3/6/12 мес", "C"], + ["2", "Амбулаторный поток (острые + хроники)", "тысячи/мес · 120 млн", + "План лечения + эпизод + процедуры самопомощи", "A"], + ["3", "Дети с аденоидами", "400–500 оп · 20–30 млн", + "Детский бегунок → восстановление (семейный профиль)", "C"], + ["4", "Потеря слуха (сурдология)", "20 млн", + "Паспорт аппарата, сервисный календарь, расходники, калибровка", "B"], + ["5", "Сложные хроники, ЧБД, аллергия", "часть 120 млн · LTV", + "Процедуры самопомощи + АСИТ-трекер + пыльцевой календарь", "A+D"], + ["6", "Зависимость от капель (вазотомия)", "высокий объём", + "Пред-оп бегунок (компактный) → восстановление", "C"], + ["7", "Пульмонология (кашель/астма)", "средний · сезонный", + "Дневник астмы, напоминания об ингаляторах", "D"], + ["8", "Социально активные храпуны", "низкий · высокий чек", + "Сомнологический чек-up, СИПАП-трекер", "—"], + ["9", "Фониатрия (голос)", "очень низкий", + "Короткое окно, низкая повторность", "—"], + ["10", "Check-up и второе мнение", "единичный", + "Разовая услуга, минимальная повторность", "—"], + ] + data = [header] + rows + # раскраска групп через цвет текста в последней колонке делается потом + tbl = add_table(s, MARGIN_L, y + Inches(0.1), + CONTENT_W, Inches(5.6), + data, + col_widths=[Inches(0.4), Inches(3.4), Inches(2.2), Inches(5.5), Inches(0.8)], + size=10, header_size=11) + # Подкрасить колонку «Группа» + group_colors = {"A": PRIMARY_DARK, "B": PRIMARY_DARK, "C": PRIMARY_DARK, + "D": PRIMARY_DARK, "A+D": PRIMARY_DARK, "—": FG3} + for ri, row in enumerate(rows, start=1): + cell = tbl.cell(ri, 4) + tf = cell.text_frame + for p in tf.paragraphs: + for r in p.runs: + r.font.bold = True + r.font.color.rgb = group_colors.get(row[4], FG) + r.font.size = Pt(12) + return s + +def slide_groups_overview(): + s = new_slide() + y = title_bar(s, "Четыре группы сегментов для повторных", "Укрупнение") + # 4 плитки 2×2 + tiles = [ + ("A", "Процедуры самопомощи", + "Амбулаторный + хроники (2, 5ч)", + "Библиотека техник, напоминания, трекер, дневник.\nСамый массовый охват."), + ("C", "Пред-оп подготовка", + "FESS + аденоиды + вазотомия (1, 3, 6)", + "Две фазы: возврат сомневающихся; бегунок → восстановление.\nОдин модуль — три сегмента."), + ("B", "Сурдология", + "Потеря слуха (4)", + "Две фазы: возврат после демо-аппарата; обслуживание аппарата.\nИзолированный модуль, высокий чек."), + ("D", "АСИТ + астма", + "Часть аллергии + пульмо (5ч, 7)", + "Ежедневный трекер, пыльцевой календарь, навигатор побочки.\nВысокая ответственность."), + ] + tw = (CONTENT_W - Inches(0.3)) / 2 + th = (Inches(5.0) - Inches(0.3)) / 2 + for i, (code, name, segments, desc) in enumerate(tiles): + col = i % 2 + row = i // 2 + tx = MARGIN_L + (tw + Inches(0.3)) * col + ty = y + Inches(0.1) + (th + Inches(0.3)) * row + add_rect(s, tx, ty, tw, th, fill=BG, line=PRIMARY, line_w=Pt(1)) + # Круг с буквой + circ = s.shapes.add_shape(MSO_SHAPE.OVAL, tx + Inches(0.3), ty + Inches(0.3), + Inches(0.8), Inches(0.8)) + circ.shadow.inherit = False + circ.fill.solid(); circ.fill.fore_color.rgb = PRIMARY + circ.line.fill.background() + tf = circ.text_frame + tf.margin_left = tf.margin_right = Emu(0); tf.margin_top = tf.margin_bottom = Emu(0) + p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER + r = p.add_run(); r.text = code + r.font.name = FONT_DISPLAY; r.font.size = Pt(28); r.font.bold = True + r.font.color.rgb = BG + # Название + add_text(s, tx + Inches(1.3), ty + Inches(0.3), tw - Inches(1.5), Inches(0.5), + name, size=20, bold=True, color=FG, font=FONT_DISPLAY) + add_text(s, tx + Inches(1.3), ty + Inches(0.8), tw - Inches(1.5), Inches(0.3), + segments, size=11, color=FG3) + # Описание + add_text(s, tx + Inches(0.3), ty + Inches(1.5), tw - Inches(0.6), + th - Inches(1.7), desc, size=12, color=FG2) + return s + +def slide_group_detail(code, color, title, caption, what_it_does, why_priority): + s = new_slide() + y = title_bar(s, title, f"Группа {code}") + # Крупный код слева + add_rect(s, MARGIN_L, y, Inches(1.4), Inches(5.0), fill=PRIMARY_50) + add_text(s, MARGIN_L, y + Inches(1.5), Inches(1.4), Inches(2.0), + code, size=120, bold=True, color=PRIMARY, font=FONT_DISPLAY, + align=PP_ALIGN.CENTER) + # Правый контент + rx = MARGIN_L + Inches(1.6) + rw = CONTENT_W - Inches(1.6) + add_text(s, rx, y + Inches(0.0), rw, Inches(0.4), + caption, size=13, color=FG3) + # Что делает + add_text(s, rx, y + Inches(0.45), rw, Inches(0.35), + "ЧТО В МОДУЛЕ", size=11, bold=True, color=PRIMARY_DARK) + add_text(s, rx, y + Inches(0.8), rw, Inches(2.8), + what_it_does, size=13, color=FG) + # Почему приоритет + add_rect(s, rx, y + Inches(3.7), rw, Inches(1.3), + fill=WARM) + add_text(s, rx + Inches(0.3), y + Inches(3.8), Inches(5), Inches(0.3), + "ПОЧЕМУ В ЭТОМ ПОРЯДКЕ", size=11, bold=True, color=FG2) + add_text(s, rx + Inches(0.3), y + Inches(4.1), rw - Inches(0.6), Inches(1.1), + why_priority, size=13, color=FG) + return s + +def slide_group_A(): + return slide_group_detail( + "A", PRIMARY, + "Процедуры самопомощи для хроников", + "Амбулаторный поток + сложные хроники · сегменты 2 и 5", + "• Библиотека техник (видео + шаги): промывание носа физраствором, полоскание горла, " + "ингаляции, гимнастика слуховой трубы, голосовые упражнения, туалет уха.\n" + "• Напоминания утром/вечером, привязка к плану лечения.\n" + "• Трекер комплаенса: ежедневные отметки, стрики, сводка.\n" + "• Дневник симптомов в связке: «промываю 5 дней, насморк 4 → 2».\n" + "• Сводка для врача перед контрольным приёмом.", + "Самый массовый охват (тысячи/мес) × ежедневная повторность. " + "Напрямую опирается на план лечения из Фазы 1. " + "Контент-ориентированный MVP (видео + простой трекер). " + "Эффект заметен пациенту сразу." + ) + +def slide_group_C(): + return slide_group_detail( + "C", PRIMARY, + "Пред-операционная подготовка + восстановление", + "FESS + дети-аденоиды + вазотомия · сегменты 1, 3, 6", + "Две фазы:\n" + "1) Кандидаты на операцию — ушли «думать» после пред-оп приёма. Возврат через материалы, " + "кейсы пациентов, чат с хирургом, возвратные push (3/7/21 день), прозрачность цены/рассрочки.\n" + "2) Решившиеся — маршрутная карта («бегунок») с чекбоксами, сроки годности анализов, " + "автозапись из пункта, загрузка сторонних результатов, чек-лист дня операции, " + "договор и оплата в приложении → передача в «Восстановление».", + "Один модуль закрывает 3 сегмента. Быстрый MVP (маршрутная карта — простой список с состояниями). " + "Прямой бизнес-эффект: меньше переносов операций + возврат сомневающихся кандидатов. " + "Вклад сегментов: ~50–60 млн." + ) + +def slide_group_B(): + return slide_group_detail( + "B", PRIMARY, + "Сурдология — слухопротезирование", + "Потеря слуха · сегмент 4", + "Две фазы, зеркально Группе C:\n" + "1) Кандидаты — после первого визита и демо уходят думать 2–3 месяца. " + "Аудиограмма в профиле, аудио-демо «до/после» в любых наушниках, каталог моделей с фильтром " + "по аудиограмме, калькулятор стоимости (ФСС, рассрочка, трейд-ин), шеринг близкому, " + "истории пациентов, возвратные push 3/7/21 день.\n" + "2) Пользователи аппарата — паспорт аппарата, календарь обслуживания, расходники, " + "навигатор неполадок, ежегодная калибровка.", + "Пожизненный LTV × высокий чек (60–180 тыс.) × растущий рынок (старение). " + "Изолированный модуль — не пересекается с другими. " + "Можно запускать параллельно с C после Фазы 1." + ) + +def slide_group_D(): + return slide_group_detail( + "D", PRIMARY, + "АСИТ + контроль астмы", + "Часть аллергии + пульмонология · сегменты 5ч и 7", + "• Календарь курса (3–5 лет) с маркером «вы здесь».\n" + "• Ежедневный приём СЛИТ с push, стрики.\n" + "• Журнал инъекций ПКИТ.\n" + "• Дневник симптомов + пыльцевой календарь региона.\n" + "• Навигатор побочки с эскалацией к врачу / скорой.\n" + "• Экстренная связь, аптечка дома.\n" + "• Автошеринг дневника врачу перед плановым визитом.", + "Самая высокая глубина пользы — прямое влияние на медицинский исход (удержание = успех лечения). " + "Но самая высокая ответственность: требует верификации контента аллергологом/пульмонологом. " + "Запускаем последним; контент можно готовить параллельно." + ) + +def slide_roadmap(): + s = new_slide() + y = title_bar(s, "Порядок внедрения", "Дорожная карта") + # Фаза 1 бокс + p1_h = Inches(0.9) + add_rect(s, MARGIN_L, y + Inches(0.05), CONTENT_W, p1_h, + fill=PRIMARY_50, line=PRIMARY, line_w=Pt(1)) + add_text(s, MARGIN_L + Inches(0.3), y + Inches(0.15), Inches(3), Inches(0.3), + "ФАЗА 1 · ТРАНЗАКЦИОННАЯ БАЗА", size=11, bold=True, color=PRIMARY_DARK) + add_text(s, MARGIN_L + Inches(0.3), y + Inches(0.48), CONTENT_W - Inches(0.6), Inches(0.4), + "План лечения → Результаты и медкарта → Заказ справок", + size=14, bold=True, color=FG, font=FONT_DISPLAY) + # Фаза 1.5 бокс + y15 = y + Inches(0.05) + p1_h + Inches(0.15) + add_rect(s, MARGIN_L, y15, CONTENT_W, p1_h, + fill=WARM, line=WARNING, line_w=Pt(1)) + add_text(s, MARGIN_L + Inches(0.3), y15 + Inches(0.1), Inches(4), Inches(0.3), + "ФАЗА 1.5 · КОММУНИКАЦИОННАЯ НАДСТРОЙКА", size=11, bold=True, color=WARNING) + add_text(s, MARGIN_L + Inches(0.3), y15 + Inches(0.43), CONTENT_W - Inches(0.6), Inches(0.4), + "Чат с медицинским консьержем → AI-помощник (shadow-mode)", + size=14, bold=True, color=FG, font=FONT_DISPLAY) + # Фаза 2 — 4 шага + y2 = y15 + p1_h + Inches(0.25) + add_text(s, MARGIN_L, y2, Inches(6), Inches(0.3), + "ФАЗА 2 · СЕГМЕНТНЫЕ МОДУЛИ", size=11, bold=True, color=FG2) + # 4 карточки горизонтально со стрелками + steps = [ + ("1", "A", "Процедуры самопомощи", "Массовый охват · продолжение плана лечения"), + ("2", "C", "Пред-оп подготовка", "3 сегмента одним модулем · быстрый эффект"), + ("3", "B", "Сурдология", "Высокий чек · пожизненное удержание"), + ("4", "D", "АСИТ + астма", "Верификация врачом · высокая польза"), + ] + step_y = y2 + Inches(0.35) + step_h = Inches(2.0) + step_w = (CONTENT_W - Inches(0.4) * 3) / 4 + for i, (num, code, name, sub) in enumerate(steps): + sx = MARGIN_L + (step_w + Inches(0.4)) * i + add_rect(s, sx, step_y, step_w, step_h, fill=BG, line=PRIMARY, line_w=Pt(1)) + # Номер и код группы + add_text(s, sx + Inches(0.2), step_y + Inches(0.2), Inches(0.8), Inches(0.4), + f"Шаг {num}", size=11, color=FG3) + add_text(s, sx + Inches(0.2), step_y + Inches(0.45), step_w - Inches(0.4), Inches(0.6), + code, size=36, bold=True, color=PRIMARY, font=FONT_DISPLAY) + add_text(s, sx + Inches(0.2), step_y + Inches(1.0), step_w - Inches(0.4), Inches(0.4), + name, size=13, bold=True, color=FG, font=FONT_DISPLAY) + add_text(s, sx + Inches(0.2), step_y + Inches(1.4), step_w - Inches(0.4), Inches(0.55), + sub, size=10, color=FG2) + # Стрелка + if i < 3: + arr_x = sx + step_w + Inches(0.05) + arr = s.shapes.add_shape(MSO_SHAPE.RIGHT_ARROW, arr_x, + step_y + step_h / 2 - Inches(0.15), + Inches(0.3), Inches(0.3)) + arr.shadow.inherit = False + arr.fill.solid(); arr.fill.fore_color.rgb = PRIMARY + arr.line.fill.background() + return s + +def slide_questions(): + s = new_slide() + y = title_bar(s, "Вопросы к обсуждению", "Что уточнить у клиники") + questions = [ + ("Поток", "«2 из 3 повторных» — уникальные пациенты в год или визиты?"), + ("Метрики", "Процент повторных, не доходящих до контрольного приёма?"), + ("Врачи", "Готовность врачей к SLA по асинхронному чату и в каком окне?"), + ("МИС", "API: структурированные назначения, сроки годности анализов, расписание?"), + ("Сурдо", "Сколько кандидатов/мес, % возврата за аппаратом?"), + ("Хирургия", "Частая причина переноса операций — просроченные анализы?"), + ("Аллерго", "Готов ли аллерголог верифицировать контент АСИТ-трекера?"), + ("Контент", "Достаточно ли материалов для RAG-базы?"), + ] + col_h = Inches(0.6) + for i, (tag, q) in enumerate(questions): + col = i % 2 + row = i // 2 + qx = MARGIN_L + (CONTENT_W / 2 + Inches(0.15)) * col + qy = y + Inches(0.2) + (col_h + Inches(0.4)) * row + qw = CONTENT_W / 2 - Inches(0.15) + add_rect(s, qx, qy, Inches(1.0), col_h, fill=PRIMARY) + add_text(s, qx, qy, Inches(1.0), col_h, tag, + size=12, bold=True, color=BG, font=FONT_DISPLAY, + align=PP_ALIGN.CENTER, anchor=MSO_ANCHOR.MIDDLE) + add_rect(s, qx + Inches(1.0), qy, qw - Inches(1.0), col_h, + fill=PRIMARY_50) + add_text(s, qx + Inches(1.2), qy, qw - Inches(1.2), col_h, q, + size=12, color=FG, anchor=MSO_ANCHOR.MIDDLE) + return s + +def slide_final(): + s = new_slide() + # Большой финал: что решить сегодня + add_rect(s, 0, 0, SW, SH, fill=PRIMARY) + add_text(s, MARGIN_L, Inches(0.8), CONTENT_W, Inches(0.5), + "ЧТО РЕШАЕМ СЕГОДНЯ", size=14, bold=True, color=PRIMARY_50) + add_text(s, MARGIN_L, Inches(1.4), CONTENT_W, Inches(1.2), + "Подтвердить порядок:", size=36, bold=True, color=BG, font=FONT_DISPLAY) + add_text(s, MARGIN_L, Inches(2.4), CONTENT_W, Inches(1.8), + "Фаза 1 · транзакционная база\n→ Фаза 1.5 · консьерж + AI\n→ Фаза 2: A → C → B → D", + size=26, bold=True, color=BG, font=FONT_DISPLAY, line_spacing=1.3) + # Три пункта + items = [ + ("1", "С чего начинаем Фазу 1?", "План лечения — фундамент. Зависит от МИС-API."), + ("2", "Когда стартует Фаза 1.5?", "После стабилизации базы. AI в shadow-mode."), + ("3", "Параллельно", "Контент для D, добавить аллерголога в справочники."), + ] + y = Inches(4.8) + for i, (num, q, a) in enumerate(items): + ix = MARGIN_L + (CONTENT_W / 3) * i + iw = CONTENT_W / 3 - Inches(0.3) + add_text(s, ix, y, Inches(0.5), Inches(0.5), + num, size=36, bold=True, color=WARM, font=FONT_DISPLAY) + add_text(s, ix + Inches(0.6), y + Inches(0.05), iw - Inches(0.6), Inches(0.5), + q, size=14, bold=True, color=BG, font=FONT_DISPLAY) + add_text(s, ix + Inches(0.6), y + Inches(0.6), iw - Inches(0.6), Inches(1.2), + a, size=12, color=PRIMARY_50) + # Подпись + add_text(s, MARGIN_L, SH - Inches(0.7), CONTENT_W, Inches(0.4), + "Клиника УГН · мобильное приложение · 23 апр 2026", + size=11, color=PRIMARY_50) + return s + +# ============================================================ +# СБОРКА +# ============================================================ +slides = [ + slide_title(), + slide_agenda(), + slide_facts(), + slide_frame(), + slide_current_functions(), + slide_three_layers(), + slide_phase1_base(), + slide_phase1_5_overlay(), + slide_primary_vs_returning(), + slide_segment_matrix(), + slide_groups_overview(), + slide_group_A(), + slide_group_C(), + slide_group_B(), + slide_group_D(), + slide_roadmap(), + slide_questions(), + slide_final(), +] + +# Футер на всех слайдах, кроме титульного и финального (у них свой фон) +total = len(slides) +for idx, s in enumerate(slides, start=1): + if idx == 1 or idx == total: + continue + footer(s, idx, total) + +out = "MEETING_2026-04-23.pptx" +prs.save(out) +print(f"OK {out} ({total} slides)") From 25552690c6260625a06898e8b53638673d5c2dbb Mon Sep 17 00:00:00 2001 From: AR 15 M4 Date: Sun, 26 Apr 2026 19:59:00 +0500 Subject: [PATCH 2/3] Add app roadmap and strategy pages; link from Tweaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - public/roadmap.html — карта развития приложения по фазам (0/1/1.5/2) - public/strategy.html — полный текст концепции развития (встреча 23 апр 2026) - Tweaks panel: секция «Документы» со ссылкой на карту развития - Roadmap: кнопка «Концепция развития» ведёт на strategy.html Co-Authored-By: Claude Sonnet 4.6 --- public/roadmap.html | 1479 ++++++++++++++++++++++++++++++++++++++++++ public/strategy.html | 1259 +++++++++++++++++++++++++++++++++++ src/App.jsx | 10 + 3 files changed, 2748 insertions(+) create mode 100644 public/roadmap.html create mode 100644 public/strategy.html diff --git a/public/roadmap.html b/public/roadmap.html new file mode 100644 index 0000000..f46e8c6 --- /dev/null +++ b/public/roadmap.html @@ -0,0 +1,1479 @@ + + + + + +Карта приложения — Клиника Оленевой + + + + + + + + + +

+ +
+
+

Карта развития приложения

+
+ Все экраны прототипа разложены по фазам стратегии 23 апр 2026. Видно — что работает сегодня, что добавляем в Фазу 1, что строим под сегменты в Фазе 2. +
+
+
+ Клиника Оленевой · Мобильное приложение + Стратегия от 23 апр 2026 · версия 1 +
+ ↗ Открыть прототип + 📋 Концепция развития +
+
+ +
+ + 0 + Сейчас в прототипе + + + 1 + Фаза 1 · Транзакционная база + + + 1.5 + Фаза 1.5 · Коммуникация + + + A + Самопомощь хроников + + + C + Пред-оп подготовка + + + B + Сурдология + + + D + АСИТ + астма + + + + Не делаем + +
+ + +
+
+
0
+
+

Сейчас в прототипе

+

16 экранов: транзакционное ядро (запись + ближайший приём + врачи) и базовая идентификация. Точка отсчёта.

+
+
+
Экранов
16
+
Покрытие
~30%
+
+
+ +
+ + +
+
+
1
+
+

Транзакционная база

+

Ради чего пациент не удалит приложение между визитами. Детерминированные функции — без LLM, без живого диалога с врачом. Минимум юридических и регламентных рисков.

+
+
+
Новых экранов
+8
+
Охват
100%
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ★ Фундамент +
+
+

План лечения с приёма

+

Главный новый объект. Диагноз, препараты с расписанием, процедуры, контрольный приём, запись в 1 клик.

+
фаза 1источник правды
+
+
+ +
+
+
+
+
8:00
+
13:00
+
20:00
+
+
+
+

Карточка препарата

+

Детали назначения: дозировка, дни, что осталось, инструкция, побочки.

+
фаза 1
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+

Медкарта со «срок годности»

+

Анализы, аудиограммы, снимки + статус «годен до». Критично для пред-опа.

+
фаза 1расширение
+
+
+ +
+
+
+
+
+
+
+
+
ЗАКАЗАТЬ
+
+
+
+

Заказ справок

+

Справка ФНС, выписки, счета. 1 клик. Готовая в приложении.

+
фаза 1retention-якорь
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Главная: «эпизод лечения»

+

Альтернативная главная для повторных. Активный диагноз, день курса, что делать сегодня.

+
фаза 1переосмысление
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Чек-лист подготовки к приёму

+

Не забыть карту/анализы, маршрут до кабинета, тригеры 24ч/2ч/15мин.

+
усиление
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Эпизод лечения

+

Сводка: первичный → процедуры → контроль. Видна траектория.

+
фаза 1
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+

Семейный профиль (расширение)

+

Переключение между профилями ребёнка/пожилого. Критично для аденоидов и ЧБД.

+
усиление
+
+
+
+ +
+ Зависимость: структурированные назначения от МИС. Если API отдаёт препарат + дозу + курс — план собирается автоматически. Если только PDF — врач заполняет в виджете шаблонами по нозологиям. Решение определяет сроки Фазы 1. +
+
+
+ + +
+
+
1.5
+
+

Коммуникационная надстройка

+

Живой и AI-диалог по поводу лечения. Поверх стабильной Фазы 1. Требует регламентов SLA, безопасности и отдельной выкладки — поэтому отделено от Фазы 1.

+
+
+
Новых экранов
+4
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SLA +
+
+

Чат с медицинским консьержем

+

Дежурный врач/фельдшер по протоколам. 80% рутины, эскалация лечащему. Не прямой чат с врачом.

+
фаза 1.5регламент
+
+
+ +
+
+
+
+
+
+
+
+
+ Shadow +
+
+

AI-помощник (RAG)

+

Знает базу знаний клиники, план лечения. Сначала shadow-mode под валидацией консьержа.

+
фаза 1.5риск
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+

Маршрутизатор каналов

+

Куда писать: оператор, консьерж или AI. Без когнитивной нагрузки на пациента.

+
UX-вызов
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Триаж тревожности

+

«Норма / срочно / неотложно» — AI оценивает и эскалирует к консьержу или 112.

+
безопасность
+
+
+
+
+
+ + +
+
+
2
+
+

Сегментные модули

+

Углубление по приоритетным группам сегментов. Порядок запуска: A → C → B → D. Каждый модуль опирается на план лечения и медкарту из Фазы 1.

+
+
+
Групп
4
+
Новых экранов
~25
+
+
+
+ +
+ + +
+
+ A + Самопомощь хроников + первый запуск · охват ~тысячи в мес. +
+

+ Промывания, полоскания, ингаляции. Видео-библиотека техник + ежедневный трекер + дневник симптомов в связке. Главный мотиватор — «промываю 5 дней, насморк уменьшился с 4 до 2». +

+
+
+
+
+
+
+
+
+
+
+
+

Библиотека техник

+

10–15 видео-инструкций: промывание, полоскание, ингаляция.

+
A
+
+
+
+
+
+
+
+
+
+
+

Трекер комплаенса

+

Ежедневные отметки, стрики, сводка для врача.

+
A
+
+
+
+
+
+ + + + +
+ Симптом 4 + → 2 +
+
+
+
+

Дневник симптомов

+

Видна динамика — главный мотиватор продолжать.

+
A
+
+
+
+
+
+
8:00 — Промыть нос
+
13:00 Ингаляция
+
20:00 Полоскание
+
+
+
+

Расписание дня

+

Утро/день/вечер из плана. Уведомления.

+
A
+
+
+
+
+ + +
+
+ C + Пред-операционная подготовка + 3 сегмента одним модулем · ~50 млн +
+

+ Закрывает FESS, аденоиды, вазотомию. Две половины: сомневающиеся после пред-оп приёма и решившиеся в 6-недельной подготовке. У нас уже есть прототип «Восстановление» — это вторая половина. +

+
+
+
+
+
+
+
+
+
+
РЕШИТЬСЯ
+
+
+
+

Сомневающийся пациент

+

Кейсы, метод (FESS/лазер), цена/рассрочка, чат с хирургом.

+
Cвозврат
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Бегунок 6 недель

+

Чек-листы, анализы (срок годности!), запись на операцию.

+
C
+
+
+
+
+
+
ДЕНЬ ОПЕРАЦИИ
+
+
+
+
+
+

Чек-лист дня операции

+

Что взять, во сколько, голод, маршрут, контакт.

+
C
+
+
+
+
+
+
+
+
+
+ ✓ есть +
+
+

Восстановление (есть)

+

Уже в прототипе — основа второй половины Группы C.

+
Cв проде
+
+
+
+
+ + +
+
+ B + Сурдология + пожизненный LTV · самый нестандартный UX +
+

+ Возврат после демо (2–3 мес «уходят думать») + обслуживание после покупки. Каталог как маркетплейс, аудио-демо, паспорт аппарата, шеринг с близким, сервисный календарь. +

+
+
+
+
+
🦻
+
+
+
+
+
+
+

Каталог аппаратов

+

Маркетплейс моделей с калькулятором, рассрочкой, шерингом.

+
B
+
+
+
+
+
+
+
+
+
+
+
+
+
Слышу с аппаратом
+
+
+
+

Аудио-демо

+

Сравнение «без аппарата / с аппаратом» в наушниках. На любом устройстве.

+
Bконверсия
+
+
+
+
+
+
+
МОЯ МОДЕЛЬ
+
Phonak Audéo
+
+
+
+
+
+
+

Паспорт аппарата

+

Сервисный календарь, расходники, калибровка раз в год.

+
B
+
+
+
+
+
+
+
+
+
+
+
+

Дневник адаптации

+

Первые 90 дней с аппаратом — сложности, успехи, советы.

+
B
+
+
+
+
+ + +
+
+ D + АСИТ + контроль астмы + самая высокая ответственность · с верификацией +
+

+ Курс 3–5 лет: «не начал», «бросил в первые месяцы». Ежедневный дневник симптомов, пыльцевой календарь, навигатор побочки. Без верификации аллерголога не запускаем. +

+
+
+
+
+
+
+
+
+
+
+
+
+
+
Берёза · пик 18 апр
+
+
+
+

Пыльцевой календарь

+

Прогноз концентрации аллергена на неделю вперёд.

+
D
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Дневник симптомов АСИТ

+

Чихание, заложенность, глаза. Видит аллерголог перед визитом.

+
D
+
+
+
+
+
+
⚠ Зуд во рту?
+
+
+
+
+
+

Навигатор побочки

+

Зуд, отёк — норма / осторожно / срочно. Удерживает на курсе.

+
D
+
+
+
+
+
+
+
+
+
+
+
Год 1 из 5
+
+
+
+
+

Дорожная карта 5 лет

+

Видеть длинный путь — главный мотив не бросить.

+
D
+
+
+
+
+ +
+ +
+ Зависимости: все 4 группы используют план лечения и медкарту из Фазы 1 как источник правды. Группа D дополнительно требует верификации контента аллергологом и добавления его в каталог врачей. Без Фазы 1 модули не строятся. +
+
+
+ + +
+
+

10 сегментов сквозь призму приложения

+

Бизнес-сегментация клиники наложена на фазы. Видно, какой модуль закрывает какие сегменты и где у нас «не делаем».

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
СегментГлавная работа в App (повторный)ГруппаВыручка / годПриоритет
1Заблокированный нос (FESS)Пред-оп бегунок · восстановление · контроль 3/6/12 месC~30 млн● высокий
2Амбулаторный потокПлан лечения · процедуры самопомощи · дневник1 A~120 млн● высший
3Дети + аденоидыСемейный профиль · подготовка ребёнка · детский бегунокC~25 млн● высокий
4СурдологияКаталог · аудио-демо · паспорт аппарата · сервисB~20 млн● высокий
5Сложные хроники + ЧБДСамопомощь · АСИТ-трекер · семейный профильA Dчасть 120 млн● высокий
6Зависимость от капельВозврат сомневающихся · компактный бегунок · восстановлениеCчасть хирургии● высокий
7ПульмонологияДневник астмы · ингаляторы · триггерыDсредний◐ средний
8Социальные храпуныОбразовательный контент · СИПАП-трекернизкий○ низкий
9ФониатрияСрочная запись (короткое окно)очень низкий○ низкий
10Check-up + Второе мнениеРазовая услуга · бренд Оленевойединичный○ низкий
+
+ + +
+
+
Принцип
+
Удержание, не привлечение
+
Первичных приводит сайт и реклама. Приложение — инструмент углубления для повторных. 2 из 3 пациентов клиники — повторные.
+
+
+
Дизайн-сдвиг
+
Из «витрины» в «эпизод лечения»
+
Главная сейчас построена для первичного. Нужна альтернативная главная: активный диагноз, день курса, что делать сегодня.
+
+
+
Что не делаем
+
Отдельные «первичные» модули
+
Базовый минимум (запись, цены, врачи) уже работает. Специализированных первичных потоков под каждый сегмент строить не нужно.
+
+
+
Исключения
+
Пациент уходит думать
+
Сурдология (2–3 мес до покупки), хирургия (решение об операции), АСИТ (3–5 лет курса) — там «первичный в App» ≠ «первый визит».
+
+
+ +
+ + + diff --git a/public/strategy.html b/public/strategy.html new file mode 100644 index 0000000..1cce416 --- /dev/null +++ b/public/strategy.html @@ -0,0 +1,1259 @@ + + + + + +Приоритеты развития приложения — Клиника Оленевой + + + + + + + + + +
+ +
+ +
Встреча 23 апр 2026 · Клиника Оленевой
+
+ + +
+
📋 Протокол встречи
+

Приоритеты развития мобильного приложения

+

Клиника УГН — ЛОР, сурдология, аллергология, хирургия. Цель встречи — определить порядок развития функций: что закрываем как базу для всех пациентов, какие сегменты углубляем и в каком порядке.

+
+ 23 апреля 2026 + 10 сегментов + 3 фазы + ~200 млн выручки +
+
+ + + + + +
+
+
1
+
+

Контекст и критерии приоритизации

+

Ключевые факты о потоке пациентов и продуктовая логика

+
+
+
+
+

Ключевые факты о потоке

+
+
+
+ +
+
2 из 3 пациентов клиники — повторные
+
Значительная доля — повторные внутри одного лечения: после первого визита (острое заболевание или обострение хронического) ещё 1–3+ приёма, процедуры, контроль.
+
+
+
+ +
+
Бизнес-сегментация — 10 сегментов
+
Лидеры по выручке: амбулаторный поток (~120 млн), взрослая хирургия «заблокированный нос» (~30 млн), детская аденоидная хирургия (~20–30 млн), сурдология (~20 млн).
+
+
+
+ +
+
Продуктовая логика
+ Приложение — инструмент удержания и углубления, а не привлечения. Первичного пациента клиника получает через сайт, SEO, рекламу, сарафан. Приложение устанавливают уже пришедшие — в момент записи или на первом визите. Поэтому приоритет развития — повторные во всех сегментах. +
+ +
+

Это совпадает с фактом «2 из 3 — повторные» и означает, что:

+
    +
  • Первичные сценарии в приложении закрываются универсальным минимумом (запись + контакты + цены + AI-помощник, который знает услуги). Нет смысла строить специализированные первичные потоки для каждого из 10 сегментов.
  • +
  • Повторные сценарии — специфичны по сегменту и требуют отдельных модулей (бегунок, аудиограмма, АСИТ-дневник и т.п.).
  • +
  • Исключения — сегменты, где «первичный в приложении» ≠ «первый визит»: после первого визита пациент уходит, и вернуть его в клинику может только приложение. +
      +
    • Сурдология — после визита и демо пациент уходит думать 2–3 месяца о покупке аппарата.
    • +
    • Хирургия (FESS, детские аденоиды, вазотомия) — после пред-операционного приёма у хирурга пациент часто уходит думать, решаться ли на операцию вообще. Вторая часть — те, кто решился, уходят в 6-недельную подготовку (бегунок).
    • +
    • АСИТ — после назначения впереди 3–5-летний курс, есть сценарии «не начал» и «бросил в первые месяцы».
    • +
    +
  • +
+ +

Три слоя работы

+
+ +
+
+
Фаза 1
+
Транзакционная база
+
Детерминированные функции для любого пациента: запись, ближайший приём, чат с оператором, план лечения, результаты/медкарта, заказ справок. Без LLM и прямого чата с врачом. Минимум рисков, быстрый запуск.
+
+
+
Фаза 1.5
+
Коммуникационная надстройка
+
Чат с медицинским консьержем (дежурным врачом/фельдшером) и AI-помощник в shadow-mode. Отделено от Фазы 1, чтобы регламенты и безопасность не задерживали релиз базы.
+
+
+
Фаза 2
+
Сегментные модули
+
Углубление по приоритетным группам сегментов в порядке A → C → B → D.
+
+
+ +

Критерии приоритета

+
+
+
Охват
+
Сколько пациентов клиники затронуто (в абсолюте)
+
+
+
Глубина пользы
+
Насколько закрывает реальную боль (есть ли альтернатива без нас)
+
+
+
Частота касаний
+
Как часто функция возвращает пользователя в приложение
+
+
+
Бизнес-эффект
+
Влияние на возвратность, средний чек, удержание, вклад в выручку
+
+
+
Сложность
+
Вторичная ось: усилия, включая зависимости от МИС и контента
+
+
+
+
+ + +
+
+
2
+
+

Текущие функции — утилита и приоритет

+

Оценка существующих функций по трём уровням приоритета

+
+
+
+

🟢 Высокий приоритет (ядро ценности)

+
+
+ +
+
Запись на приём
+
Главный транзакционный поток. Экономит 3–8 минут разговора, 24/7, ближайшие окна. Единственная функция, которая приводит новых пациентов через приложение. Усилить: запись из плана лечения в 1 клик, запись из маршрутной карты пред-опа.
+
+
+
+ +
+
Ближайший приём
+
«Что со мной сейчас». Снимает тревогу «когда, куда, к кому». Перед визитом открывается 3–10 раз. Усилить: чек-лист подготовки, маршрут до кабинета, тригеры 24ч/2ч/15мин.
+
+
+
+ +

🟡 Средний приоритет (поддержка)

+
+
+ +
+
Список врачей / карточка врача
+
Инструмент выбора при первом визите. Повторный пациент почти не открывает. Усилить: соцдоказательства, фильтр «по моему диагнозу».
+
+
+
+ +
+
Чат с оператором
+
Замена звонка в регистратуру. Ограничен одним собеседником. Основное развитие — в §3.
+
+
+
+ +
+
Профиль
+
Точка идентификации. Редко открывается, но критичен для персонализации.
+
+
+
+ +

🔴 Низкий приоритет (ниша)

+
+
+ +
+
Семейный профиль
+
Высокая польза для 20–30% (родители с детьми, взрослые с пожилыми), становится критичным для сегмента «дети с аденоидами» и «сложные хроники — ЧБД».
+
+
+
+ +
+
Контакты
+
Функция «галочка», 1–2 открытия за всё время.
+
+
+
+ +
+
Вывод
+ Ядро «Запись + Ближайший приём» работает. Слабое место — нет причины открывать приложение между визитами. Это и закрывает джентльменский набор. +
+
+
+ + +
+
+
3
+
+

Джентльменский набор

+

Базовые функции для всех пациентов — приоритет до любых сегментных модулей

+
+
+
+
+

Набор сознательно разделён на две подфазы по риску и зависимостям: транзакционная база (детерминированные функции с понятным MVP) и коммуникационная надстройка (LLM и асинхронные каналы, требующие регламентов и безопасности). Это позволяет выпустить базу быстро и безопасно, а надстройку строить поверх уже стабильного фундамента.

+ +

Фаза 1 · Транзакционная база

+

Функции, где пациент что-то получает или делает — без LLM и без диалога. Минимизируют риски, быстрее в запуск, дают те самые «не удалю это приложение»-причины.

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ФункцияСтатусЧто в ней
Запись на приём✅ естьЗапись 24/7, ближайшие окна.
Ближайший приём✅ естьЧто, где, когда — снимает тревогу перед визитом.
Чат с оператором✅ естьСправки, переносы, счета, расписание (человеческий канал).
Статьи / база знаний✅ естьУже есть. Источник правды для будущего RAG.
План лечения с приёма❌ нет — фундаментПосле каждого приёма структурированный чеклист: диагноз, назначения (препарат + доза + курс + календарь), ссылки на процедуры самопомощи, контрольный приём, запись на следующий визит в 1 клик. Живой объект с напоминаниями. Источник правды для всех следующих надстроек.
Результаты обследований и медкарта❌ нетПациент видит свои анализы, аудиограммы, снимки, заключения без звонка в клинику. Статус каждого результата: готов / в работе / годен до [дата]. Критично для Группы C: срок годности анализов виден в одном месте.
Заказ справок и финансовых документов❌ нетСправка для налогового вычета (13%), справки работодателю, копии заключений, счета. Заказ в 1 клик, готовая в приложении или с самовывозом. Снимает нагрузку с администраторов и даёт сильный retention-якорь.
+
+ +
+

Фаза 1.5 · Коммуникационная надстройка

+

Функции, где идёт живой или AI-диалог по поводу лечения. Строятся поверх уже работающей базы и требуют отдельных регламентов — по ответственности, SLA, проверке качества.

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
ФункцияСтатусЧто в нейКлючевой риск
Чат с медицинским консьержем❌ нетНе прямой чат с лечащим врачом. Дежурный врач / фельдшер / медсестра отвечает по протоколам на 80% рутины, эскалирует клинически значимое лечащему. Асинхронно, SLA — X часов в рабочее время.Перегрузка врачей и SLA-хаос, если пустить пациентов напрямую к хирургу. Нужен регламент тарификации/компенсации врачам за эскалированные вопросы.
AI-помощник (RAG 24/7)❌ нетЗнает базу знаний клиники, статьи, план лечения пациента. Объясняет назначение, ищет ответ в статьях, оценивает «норма или срочно» и эскалирует к консьержу/врачу при тревожных признаках.Галлюцинация LLM в медицинском контексте = юридический и репутационный риск. Запускаем в shadow-mode: ответы сначала идут через консьержа для валидации.
+
+ +
+
Почему это ядро, а не сегмент
+
    +
  • Работает на 100% пациентов — без разделения по сегменту.
  • +
  • Усиливает любое сегментное направление: бегунок, слухопротезирование, АСИТ — всё опирается на план лечения, медкарту и тот же чат-канал.
  • +
  • Закрывает главный пробел транзакционной модели — «что делать между визитами».
  • +
+
+ +

Порядок работ внутри набора

+

Фаза 1:

+
    +
  1. План лечения с приёма — фундамент. Без структурированных назначений не работает ничего поверх (ни AI, ни напоминания, ни процедуры самопомощи).
  2. +
  3. Результаты / медкарта — следующий retention-якорь.
  4. +
  5. Заказ справок — быстрый win для большой части пациентов, снимает нагрузку с администраторов.
  6. +
+

Фаза 1.5 (после стабилизации Фазы 1):

+
    +
  1. Чат с медицинским консьержем — инфраструктура канала + регламент ответов.
  2. +
  3. AI-помощник на RAG (shadow-mode) — сначала ответы валидируются консьержем, потом постепенный переход в прямой режим для безопасных категорий.
  4. +
+
+ +
+ Критическая зависимость от МИС. «План лечения» — это структурированные данные (препарат, доза, частота, курс, процедура). Два сценария: МИС отдаёт назначения по API структурированно — план собирается автоматически. МИС отдаёт только PDF-заключение — план лечения MVP стартует с ручного ввода врачом в виджет. Ответ на этот вопрос определяет сроки и стоимость Фазы 1. +
+
+
+ + +
+
+
4
+
+

Сегменты пациентов через призму приложения

+

Первичный vs повторный · матрица 10 сегментов · четыре группы

+
+
+
+

4.1. Фундаментальное деление: первичный vs повторный

+
+ + + + + + + + + + + +
ЛинзаПервичный в приложенииПовторный в приложении
Главный job«Куда мне обратиться и как записаться»«Что мне делать сейчас по моему лечению»
ИсточникПопадает через сайт/рекламу/сарафанПриложение уже установлено
Частота открытия1–3 раза до визитаЕжедневно в активном эпизоде
Что нужноБазовый минимум (запись, цены, контакты, врачи)Специализированный модуль сегмента
Приоритет развития○ Низкий● Высокий (2/3 потока)
+
+ +

4.2. Матрица 10 сегментов

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#СегментОбъёмПовторный в AppГруппаПриоритет
1Взрослые «заблокированный нос» (полипы, FESS)300 оп/год · 30 млнПред-оп бегунок (6 нед) → восстановление → контроль 3/6/12 месC● Высокий
2Амбулаторный поток (острые и хроники ЛОР)Тысячи/мес · 120 млнПлан лечения + процедуры самопомощи: промывания, полоскания, ингаляции1 A● Высший
3Родители детей с аденоидами400–500 оп/год · 20–30 млнПред-оп бегунок (детская версия) → восстановлениеC● Высокий
4Потеря слуха (сурдология)20 млн/годПаспорт аппарата, сервисный календарь, расходники, дневник адаптацииB● Высокий
5Сложные хроники (иммунология, ЧБД)Высокий · длинный LTVПроцедуры самопомощи (Группа A) + АСИТ-трекерA D● Высокий
6Зависимость от капель (вазотомия)Высокий объём · входной в хирургиюПред-оп бегунок (компактная версия) → восстановлениеC● Высокий
7Пульмонология (кашель/астма)Средний · сезонныйДневник астмы, напоминания об ингаляторах, контроль триггеровD◐ Средний
8Социально активные храпуныНизкийОбразовательный контент, СИПАП-трекер○ Низкий
9Фониатрия (голос)Очень низкий · срочныйКороткое окно, низкая повторность○ Низкий
10Check-up и Второе мнениеЕдиничный · высокая маржаРазовая услуга, минимальная повторность○ Низкий
+
+ +

4.3. Четыре группы сегментов для повторных

+ +
+
+
Группа A · Сегменты 2 + 5
+
Амбулаторный поток + хроники — процедуры самопомощи
+
Самый массовый сегмент × ежедневная повторность. Промывание носа, полоскание горла, ингаляции, гимнастика слуховой трубы. Что нужно: библиотека техник (видео), напоминания из плана лечения, трекер комплаенса, дневник симптомов, сводка для врача.
+
+
+
Группа B · Сегмент 4
+
Сурдология
+
Уникальный набор функций — аудиограмма, аудио-демо, каталог, паспорт аппарата. Пожизненная повторность. Высокий чек. Отдельный модуль, не пересекается с другими.
+
+
+
Группа C · Сегменты 1 + 3 + 6
+
Пред-операционная подготовка + восстановление
+
Кандидаты на операцию — ушли думать после пред-оп приёма. Возврат через материалы, кейсы, чат с хирургом, возвратные push-триггеры. Решившиеся — бегунок 6 нед → чек-лист дня операции → восстановление. Один модуль закрывает три сегмента.
+
+
+
Группа D · Часть сег. 5 + сег. 7
+
АСИТ + контроль астмы
+
Ежедневный трекер, дневник симптомов, навигатор побочки, пыльцевой календарь. Самый сложный — требует верификации контента аллергологом/пульмонологом.
+
+
+
+
+ + +
+
+
5
+
+

Порядок внедрения

+

Рекомендуемая последовательность фаз и групп

+
+
+
+
+

Фаза 1. Транзакционная база

+

План лечения → Результаты / медкарта → Заказ справок.

+

Эффект: пациент видит свои назначения, результаты и может получить документы без звонка в клинику. Базовые retention-якоря: «не удалю это приложение». Массовый амбулаторный + хроники покрыты в части информирования и документов.

+

Фаза 1.5. Коммуникационная надстройка

+

Чат с медицинским консьержем → AI-помощник (shadow-mode).

+

Выделение в 1.5 позволяет: собрать транзакционную базу быстро и безопасно, отработать регламент консьержа отдельно, запустить AI сначала под валидацией человека.

+

Фаза 2. Сегментные модули — порядок

+
+ +
+
+
A
+
+
Группа A — Процедуры самопомощи хроников · первым
+
Самый массовый охват — тысячи пациентов в месяц (сегменты 2 + 5). Напрямую опирается на план лечения из Фазы 1. Бизнес-эффект: рост комплаенса → меньше обострений → меньше экстренных приёмов. Эффект заметен пациенту сразу — ежедневная польза.
+
+
+
+
C
+
+
Группа C — Пред-операционная подготовка · вторым
+
Закрывает 3 сегмента (1, 3, 6) одним модулем. Восстановление уже есть в прототипе. Прямой бизнес-эффект: снижение переносов операций из-за просроченных анализов + возврат сомневающихся кандидатов. Вклад в выручку ~50–60 млн.
+
+
+
+
B
+
+
Группа B — Сурдология · третьим
+
Изолированный сегмент с уникальными функциями. Пожизненное удержание × высокий чек × растущий рынок (старение). Две половины: возврат кандидатов после демо + обслуживание после покупки. Можно запускать параллельно с C при наличии ресурса.
+
+
+
+
D
+
+
Группа D — АСИТ + астма · четвёртым
+
Самая высокая глубина пользы (влияет на медисход), но самая высокая ответственность. Требует верификации контента аллергологом/пульмонологом клиники. Можно начать готовить контент параллельно с A/C/B.
+
+
+
+ +
+
Что не берём в план сейчас
+ Сегменты 8 (храпуны), 9 (фониатрия), 10 (check-up) — специализированных модулей в горизонте планирования не делаем. Отдельные «первичные» модули под сегменты — не делаем. Нужно сделать уже сейчас: добавить аллерголога-иммунолога в список врачей — предусловие для Группы D. +
+
+
+ + +
+
+
6
+
+

Вопросы к обсуждению

+

Открытые вопросы, требующие ответа от клиники

+
+
+
+

Первоочередной (определяет сроки Фазы 1)

+
+
+
1
+
МИС и структурированные назначения. Отдаёт ли МИС по API назначения структурированно (препарат, доза, частота, курс) или только PDF-заключением? От этого зависит: план лечения собирается автоматически или врач заполняет вручную в виджете. Там же: API для результатов анализов, сроков годности, расписания.
+
+
+ +

По процессам клиники

+
+
+
2
+
Медицинский консьерж — кто в роли? Дежурный врач / фельдшер / медсестра? Кто держит SLA? Как компенсируется эскалация вопроса лечащему врачу?
+
+
+
3
+
SLA чата — целевое время ответа в рабочее время (1ч / 4ч / день)?
+
+
+
4
+
Справки и финдокументы — текущий процесс заказа через администратора; что готовы автоматизировать в первую очередь (ФНС-справка, выписки, счета)?
+
+
+ +

По метрикам (для оценки эффекта)

+
+
+
5
+
Подтвердить «2 из 3 — повторные» — уникальные пациенты в год или визиты? Меняет оценку охвата Группы A.
+
+
+
6
+
Процент повторных, не доходящих до контроля — метрика успеха Фазы 1.
+
+
+
7
+
Сурдология — кандидатов/мес, % возврата за аппаратом. От этого зависит приоритет Группы B.
+
+
+
8
+
Переносы операций — частая причина просроченные анализы? Усиливает Группу C.
+
+
+ +

По ресурсам Фаз 1.5 и 2

+
+
+
9
+
Готов ли аллерголог верифицировать контент для АСИТ-трекера? Без этого Группу D не запускаем.
+
+
+
10
+
База знаний / статьи — достаточно ли материала для RAG или формировать отдельно?
+
+
+
11
+
Политика по AI shadow-mode: кто утверждает категории вопросов для постепенного перевода в прямой режим?
+
+
+
+
+ + +
+
+
+
+

Короткая сводка

+

Итог встречи в четырёх пунктах

+
+
+
+
+
Что предлагаем
+
    +
  1. Фаза 1 — транзакционная база для всех: план лечения, результаты/медкарта, заказ справок + уже существующие запись, ближайший приём, чат с оператором, статьи. Быстрый MVP, минимум рисков.
  2. +
  3. Фаза 1.5 — коммуникационная надстройка: чат с медицинским консьержем (не прямой с врачом) и AI-помощник в shadow-mode. Запускается после стабилизации базы.
  4. +
  5. Фаза 2 — четыре сегментных модуля в порядке: A (процедуры самопомощи хроников — массовый) → C (пред-оп подготовка — 3 сегмента разом) → B (сурдология — высокий чек) → D (АСИТ + астма — с верификацией врачом).
  6. +
  7. Первичные в приложении = базовый минимум, который уже есть. Новых первичных модулей не строим — они приходят через веб. Исключения — сурдология, хирургия, АСИТ.
  8. +
+
+
+
+ + +
+ ← Карта развития приложения +
+ Клиника Оленевой · Мобильное приложение
+ Протокол встречи 23 апреля 2026 +
+
+ +
+ + diff --git a/src/App.jsx b/src/App.jsx index 1e2e247..257a9eb 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -188,6 +188,16 @@ function TweaksPanel({ tw, setTw, onClose }) { )) )} {group('Шрифт', opts(FONT_OPTIONS, 'font'))} + ); } From 7f09363811e05d26dd2526ff61d75675acf7c42e Mon Sep 17 00:00:00 2001 From: poturaevpetr Date: Tue, 28 Apr 2026 01:08:18 +0500 Subject: [PATCH 3/3] add pages contacts --- package-lock.json | 77 ++++- package.json | 5 +- src/App.jsx | 35 +-- src/ClinicLeafletMap.jsx | 46 +++ src/ContactsRoutePage.jsx | 577 ++++++++++++++++++++++++++++++++++++++ src/FitWrap.jsx | 33 +++ src/app.css | 6 + src/icons.jsx | 2 + src/main.jsx | 9 +- 9 files changed, 754 insertions(+), 36 deletions(-) create mode 100644 src/ClinicLeafletMap.jsx create mode 100644 src/ContactsRoutePage.jsx create mode 100644 src/FitWrap.jsx diff --git a/package-lock.json b/package-lock.json index 25bd1f3..83f7d94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,11 @@ "name": "pcs-pt-mobile", "version": "0.1.0", "dependencies": { + "leaflet": "^1.9.4", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-leaflet": "^4.2.1", + "react-router-dom": "^6.30.3" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.4", @@ -739,6 +742,26 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@react-leaflet/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", + "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==", + "license": "Hippocratic-2.1", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1375,6 +1398,12 @@ "node": ">=6" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1491,6 +1520,20 @@ "react": "^18.3.1" } }, + "node_modules/react-leaflet": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", + "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", + "license": "Hippocratic-2.1", + "dependencies": { + "@react-leaflet/core": "^2.1.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -1501,6 +1544,38 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/rollup": { "version": "4.60.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", diff --git a/package.json b/package.json index e3fe231..b13260a 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,11 @@ "preview": "vite preview" }, "dependencies": { + "leaflet": "^1.9.4", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-leaflet": "^4.2.1", + "react-router-dom": "^6.30.3" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.4", diff --git a/src/App.jsx b/src/App.jsx index 257a9eb..64c6cda 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,5 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { FitWrap } from './FitWrap.jsx'; import { IOSDevice } from './frames/IOSDevice.jsx'; import { AndroidDevice } from './frames/AndroidDevice.jsx'; import { PhoneApp } from './PhoneApp.jsx'; @@ -300,38 +301,6 @@ function DocModal({ doc, onClose }) { ); } -function FitWrap({ children, w = 402, h = 874, userScale = 'auto' }) { - const outerRef = useRef(null); - const [autoScale, setAutoScale] = useState(1); - useEffect(() => { - const outer = outerRef.current; - if (!outer) return; - const stage = outer.closest('.stage') || outer.parentElement; - if (!stage) return; - const measure = () => { - const padding = 48; - const availW = stage.clientWidth - padding; - const availH = stage.clientHeight - padding; - const s = Math.min(availW / w, availH / h, 1); - setAutoScale(Math.max(s, 0.3)); - }; - measure(); - const ro = new ResizeObserver(measure); - ro.observe(stage); - window.addEventListener('resize', measure); - return () => { ro.disconnect(); window.removeEventListener('resize', measure); }; - }, [w, h]); - const scale = userScale === 'auto' ? autoScale : parseFloat(userScale); - return ( -
-
{children}
-
- ); -} - export default function App() { const [tw, setTw] = useState(TWEAKS_DEFAULT); const [panelOpen, setPanelOpen] = useState(true); diff --git a/src/ClinicLeafletMap.jsx b/src/ClinicLeafletMap.jsx new file mode 100644 index 0000000..4d80e90 --- /dev/null +++ b/src/ClinicLeafletMap.jsx @@ -0,0 +1,46 @@ +import React, { useEffect, useMemo } from 'react'; +import { MapContainer, Marker, TileLayer, useMap } from 'react-leaflet'; +import L from 'leaflet'; +import 'leaflet/dist/leaflet.css'; + +function InvalidateOnMount() { + const map = useMap(); + useEffect(() => { + map.invalidateSize(); + const id = window.setTimeout(() => map.invalidateSize(), 120); + return () => window.clearTimeout(id); + }, [map]); + return null; +} + +function makePinIcon(pinColor) { + const safe = /^#[0-9A-Fa-f]{6}$/.test(pinColor) ? pinColor : '#E04E44'; + return L.divIcon({ + className: 'clinic-leaflet-pin-wrap', + html: ``, + iconSize: [26, 34], + iconAnchor: [13, 34], + }); +} + +/** OSM + один маркер; center — [lat, lng] в формате Leaflet */ +export function ClinicLeafletMap({ center, pinColor = '#E04E44' }) { + const icon = useMemo(() => makePinIcon(pinColor), [pinColor]); + + return ( + + + + + + ); +} diff --git a/src/ContactsRoutePage.jsx b/src/ContactsRoutePage.jsx new file mode 100644 index 0000000..6d14f3e --- /dev/null +++ b/src/ContactsRoutePage.jsx @@ -0,0 +1,577 @@ +import React from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { I } from './icons.jsx'; +import { IOSDevice } from './frames/IOSDevice.jsx'; +import { FitWrap } from './FitWrap.jsx'; +import { ClinicLeafletMap } from './ClinicLeafletMap.jsx'; + +/** Палитра и типографика ближе к скриншоту Oclinica */ +const oc = { + pageBg: '#F2F2F2', + headerBg: '#FFFFFF', + card: '#FFFFFF', + /** Телефон и «Записаться на приём» — один фон и цвет текста */ + phoneBtnBg: '#9ad1d8', + phoneBtnFg: '#001c22', + phoneBtnShadow: '0 3px 14px rgba(0, 28, 34, 0.08)', + bookBtnBg: '#9ad1d8', + bookBtnFg: '#001c22', + chatBorder: '#C4A574', + chatFg: '#6B542E', + openBadgeBg: '#769197', + openBadgeFg: '#FFFFFF', + /** Блок «Почему нас выбирают» */ + hWhy: '#B88E71', + whyWrapBg: '#EBEDF0', + /** Звезда над заголовком «Почему нас выбирают» */ + whyHeaderStar: '#5f96a0', + /** Иконки Email / Веб-сайт */ + whyInfoIconBg: '#f8faf9', + whyInfoIconFg: '#619799', + whyRowLabel: '#5A9E95', + whyRowValue: '#3A4149', + statCardValue: '#599195', + statCardLabel: '#6B7684', + noteBg: '#E8F4FC', + noteBorder: '#B9D8EE', + noteFg: '#245A7A', + routeBtnBg: '#EBEDEF', + routeBtnFg: '#4A5560', + teal: '#1F8F85', + tealDark: '#166B63', +}; + +function MiniBuilding() { + return ( +
+
+
OCLINICA
+
+ {[1, 0, 1, 1, 0, 1, 0, 1, 1, 0].map((l, i) => ( +
+ ))} +
+
+
+ ); +} + +function NavRouteIcon({ size = 18, color = oc.teal }) { + return ( + + + + ); +} + +/** Глифы VK / YouTube — геометрия Simple Icons, единый цвет в интерфейсе */ +const SI_VK_PATH = + 'm9.489.004.729-.003h3.564l.73.003.914.01.433.007.418.011.403.014.388.016.374.021.36.025.345.03.333.033c1.74.196 2.933.616 3.833 1.516.9.9 1.32 2.092 1.516 3.833l.034.333.029.346.025.36.02.373.025.588.012.41.013.644.009.915.004.98-.001 3.313-.003.73-.01.914-.007.433-.011.418-.014.403-.016.388-.021.374-.025.36-.03.345-.033.333c-.196 1.74-.616 2.933-1.516 3.833-.9.9-2.092 1.32-3.833 1.516l-.333.034-.346.029-.36.025-.373.02-.588.025-.41.012-.644.013-.915.009-.98.004-3.313-.001-.73-.003-.914-.01-.433-.007-.418-.011-.403-.014-.388-.016-.374-.021-.36-.025-.345-.03-.333-.033c-1.74-.196-2.933-.616-3.833-1.516-.9-.9-1.32-2.092-1.516-3.833l-.034-.333-.029-.346-.025-.36-.02-.373-.025-.588-.012-.41-.013-.644-.009-.915-.004-.98.001-3.313.003-.73.01-.914.007-.433.011-.418.014-.403.016-.388.021-.374.025-.36.03-.345.033-.333c.196-1.74.616-2.933 1.516-3.833.9-.9 2.092-1.32 3.833-1.516l.333-.034.346-.029.36-.025.373-.02.588-.025.41-.012.644-.013.915-.009ZM6.79 7.3H4.05c.13 6.24 3.25 9.99 8.72 9.99h.31v-3.57c2.01.2 3.53 1.67 4.14 3.57h2.84c-.78-2.84-2.83-4.41-4.11-5.01 1.28-.74 3.08-2.54 3.51-4.98h-2.58c-.56 1.98-2.22 3.78-3.8 3.95V7.3H10.5v6.92c-1.6-.4-3.62-2.34-3.71-6.92Z'; + +const SI_YOUTUBE_BG_PATH = + 'M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814z'; +const SI_YOUTUBE_PLAY_PATH = 'M9.545 15.568V8.432L15.818 12l-6.273 3.568z'; + +function SocialVkIcon({ size = 26, color = oc.whyHeaderStar }) { + return ( + + + + ); +} + +function SocialYoutubeIcon({ size = 26, color = oc.whyHeaderStar, playColor = '#fff' }) { + return ( + + + + + ); +} + +function OclinicaTabBar() { + const tab = (label, Icon, active) => ( +
+ + {label} +
+ ); + return ( +
+ + {tab('Главная', I.home, false)} + + {tab('Клиники', I.pin, true)} + {tab('Запись', I.calendar, false)} + {tab('Профиль', I.profile, false)} +
+ ); +} + +/** Конверт — заливка заданным зелёным */ +function WhyMailGlyph({ size = 22, color = oc.whyInfoIconFg }) { + return ( + + + + ); +} + +/** Глобус — контур тем же зелёным */ +function WhyGlobeGlyph({ size = 22, color = oc.whyInfoIconFg }) { + return ( + + + + + + + ); +} + +/** Девятиконечная звезда + белая галочка по центру (блок «Почему нас выбирают») */ +/** Внешний ≈8.35, внутренний ≈7.22 — чуть крупнее, лучи по-прежнему короткие */ +const WHY_NINE_POINT_STAR_D = + 'M12.000,3.650L14.469,5.215L17.367,5.604L18.253,8.390L20.223,10.550L19.110,13.254L19.231,16.175L16.641,17.531L14.856,19.846L12.000,19.220L9.144,19.846L7.359,17.531L4.769,16.175L4.890,13.254L3.777,10.550L5.747,8.390L6.633,5.604L9.531,5.215Z'; + +function WhyHeaderStarGlyph({ size = 52, color = oc.whyHeaderStar }) { + return ( + + + + + ); +} + +function StatPill({ value, label, suffix }) { + return ( +
+
+ {value} + {suffix} +
+
{label}
+
+ ); +} + +function InfoRow({ glyph, label, children }) { + return ( +
+
+ {glyph} +
+
+
{label}
+
+ {children} +
+
+
+ ); +} + +function ClinicCirclePhoto({ imageUrl, href }) { + const [useFallback, setUseFallback] = React.useState(false); + const img = !useFallback ? ( + setUseFallback(true)} + /> + ) : ( + + ); + + if (href) { + return ( + + {img} + + ); + } + return img; +} + +const FOOTER_H = 162; +/** Выше внутренних слоёв Leaflet (~400–700), чтобы хром не перекрывался картой при скролле */ +const Z_SCROLL = 0; +const Z_HEADER = 2000; +const Z_FOOTER_BAR = 2100; + +function ContactsPhoneBody() { + const navigate = useNavigate(); + const goBack = () => { + if (window.history.length > 1) navigate(-1); + else navigate('/'); + }; + + const clinics = [ + { + id: 'zvezda', + street: 'ул. Газеты Звезда, 31А', + hours: 'Пн–Пт 09:00 – 21:00\nСб–Вс 09:00 – 19:00', + note: 'Каждый 4-ый четверг месяца до 17:00', + mapPosition: [58.008116, 56.246041], + mapPinColor: '#E04E44', + circleImageSrc: 'https://avatars.mds.yandex.net/get-altay/1363018/2a00000164698e13e4695cde8053e9eed99f/L_height', + circleHref: 'https://yandex.ru/maps/org/klinika_ukho_gorlo_nos_imeni_professora_ye_n_olenevoy/1747301334/', + }, + { + id: 'tsetkin', + street: 'ул. Клары Цеткин, 9', + hours: 'Пн–Сб 09:00 – 17:00\nВс — выходной', + note: null, + mapPosition: [57.987262, 56.246448], + mapPinColor: '#D94A3D', + circleImageSrc: 'https://lh5.googleusercontent.com/proxy/FtM-XTbdVjWSzmAYLN1W_b69pueujUj2Gv6Yr7RqwYwQJOrisuUt_YI6qIXyaO9kZa3BZmQQrFpcJcfbcJUvdMGJx-s7UM3PYrnLsYtZcJ8jdVllLCU', + circleHref: 'http://job.oclinica.ru/vakansiya-administratora', + }, + ]; + + return ( +
+
+
+ +

Контакты

+ +
+ +
+ + + 8(342) 207-03-03 + + + + + Написать в чат + + +

Наши клиники

+ +
+ {clinics.map(c => ( +
+
+
+ +
ОТКРЫТО
+
+ +
+
+
+ +
+
+ {c.street} +
+ +
+ Режим работы +
+
+ +
+ {c.hours} +
+
+ + {c.note && ( +
+ {c.note} +
+ )} + + +
+
+ ))} +
+ +
+
+
+ +
+

Почему нас выбирают

+
+ +
+ + + + ★} /> +
+ + } label="Email"> + + mail@oclinica.ru + + + } label="Веб-сайт"> + + perm.oclinica.ru + + + + +
+ +
+
+
+ +
+
+ + + Записаться на приём + +
+ +
+
+ ); +} + +export default function ContactsRoutePage() { + return ( +
+
+ + + + + +
+
+ ); +} diff --git a/src/FitWrap.jsx b/src/FitWrap.jsx new file mode 100644 index 0000000..f12826b --- /dev/null +++ b/src/FitWrap.jsx @@ -0,0 +1,33 @@ +import React, { useEffect, useRef, useState } from 'react'; + +export function FitWrap({ children, w = 402, h = 874, userScale = 'auto' }) { + const outerRef = useRef(null); + const [autoScale, setAutoScale] = useState(1); + useEffect(() => { + const outer = outerRef.current; + if (!outer) return; + const stage = outer.closest('.stage') || outer.parentElement; + if (!stage) return; + const measure = () => { + const padding = 48; + const availW = stage.clientWidth - padding; + const availH = stage.clientHeight - padding; + const s = Math.min(availW / w, availH / h, 1); + setAutoScale(Math.max(s, 0.3)); + }; + measure(); + const ro = new ResizeObserver(measure); + ro.observe(stage); + window.addEventListener('resize', measure); + return () => { ro.disconnect(); window.removeEventListener('resize', measure); }; + }, [w, h]); + const scale = userScale === 'auto' ? autoScale : parseFloat(userScale); + return ( +
+
{children}
+
+ ); +} diff --git a/src/app.css b/src/app.css index 73501d0..3519429 100644 --- a/src/app.css +++ b/src/app.css @@ -345,3 +345,9 @@ input, select, textarea { font-family: inherit; } @keyframes pulse { 0%{opacity:.5;transform:scale(.95)} 100%{opacity:0;transform:scale(1.15)} } @keyframes blink { 50% { opacity: .3 } } + +/* Leaflet: divIcon marker without default box */ +.leaflet-div-icon.clinic-leaflet-pin-wrap { + border: none !important; + background: transparent !important; +} diff --git a/src/icons.jsx b/src/icons.jsx index f989b95..b39e8bc 100644 --- a/src/icons.jsx +++ b/src/icons.jsx @@ -42,4 +42,6 @@ export const I = { volume: (p) => , arrow: (p) => , menu: (p) => , + mail: (p) => , + globe: (p) => , }; diff --git a/src/main.jsx b/src/main.jsx index 4dc7be2..0ac5d9f 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,11 +1,18 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import { BrowserRouter, Routes, Route } from 'react-router-dom'; import App from './App.jsx'; +import ContactsRoutePage from './ContactsRoutePage.jsx'; import './tokens.css'; import './app.css'; ReactDOM.createRoot(document.getElementById('root')).render( - + + + } /> + } /> + + );