diff --git a/contracts/service_canon.json b/contracts/service_canon.json new file mode 100644 index 0000000..ea5be75 --- /dev/null +++ b/contracts/service_canon.json @@ -0,0 +1,415 @@ +[ + { + "canon_id": "cbc", + "canonical_ru": "Общий анализ крови", + "canonical_kz": "Қанның жалпы анализі", + "canonical_en": "Complete blood count (CBC)", + "category": "Лаборатория", + "code": "B03.016.003", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "ОАК", + "lang": "ru" + }, + { + "text": "Общий анализ крови (развернутый)", + "lang": "ru" + }, + { + "text": "Клинический анализ крови", + "lang": "ru" + }, + { + "text": "КАК", + "lang": "ru" + }, + { + "text": "CBC", + "lang": "en" + }, + { + "text": "Қанның жалпы талдауы", + "lang": "kz" + } + ] + }, + { + "canon_id": "uzi_abd", + "canonical_ru": "УЗИ органов брюшной полости", + "canonical_kz": "Құрсақ қуысы ағзаларының УДЗ", + "canonical_en": "Abdominal ultrasound", + "category": "Диагностика/УЗИ", + "code": "A04.16.001", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "УЗИ ОБП", + "lang": "ru" + }, + { + "text": "УЗИ брюшной полости", + "lang": "ru" + }, + { + "text": "Ультразвуковое исследование брюшной полости", + "lang": "ru" + }, + { + "text": "Құрсақ қуысы УДЗ", + "lang": "kz" + }, + { + "text": "Abdominal US", + "lang": "en" + } + ] + }, + { + "canon_id": "therapist", + "canonical_ru": "Приём врача-терапевта", + "canonical_kz": "Терапевт дәрігердің қабылдауы", + "canonical_en": "General practitioner consultation", + "category": "Приём специалистов", + "code": "B01.047.001", + "unit": "1 приём", + "synonyms": [ + { + "text": "Прием терапевта", + "lang": "ru" + }, + { + "text": "Консультация терапевта первичная", + "lang": "ru" + }, + { + "text": "Терапевт қабылдауы", + "lang": "kz" + }, + { + "text": "GP visit", + "lang": "en" + } + ] + }, + { + "canon_id": "ecg", + "canonical_ru": "Электрокардиография (ЭКГ)", + "canonical_kz": "Электрокардиография (ЭКГ)", + "canonical_en": "Electrocardiography (ECG)", + "category": "Диагностика", + "code": "A05.10.006", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "ЭКГ", + "lang": "ru" + }, + { + "text": "ЭКГ с расшифровкой", + "lang": "ru" + }, + { + "text": "Электрокардиография", + "lang": "ru" + }, + { + "text": "ECG", + "lang": "en" + }, + { + "text": "ЭКГ + описание", + "lang": "ru" + } + ] + }, + { + "canon_id": "glucose", + "canonical_ru": "Глюкоза крови", + "canonical_kz": "Қандағы глюкоза", + "canonical_en": "Blood glucose", + "category": "Лаборатория", + "code": "B03.016.006", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "Глюкоза", + "lang": "ru" + }, + { + "text": "Сахар крови", + "lang": "ru" + }, + { + "text": "Қандағы глюкоза", + "lang": "kz" + }, + { + "text": "Glucose", + "lang": "en" + } + ] + }, + { + "canon_id": "cardiologist", + "canonical_ru": "Консультация кардиолога", + "canonical_kz": "Кардиолог кеңесі", + "canonical_en": "Cardiologist consultation", + "category": "Приём специалистов", + "code": "B01.015.001", + "unit": "1 приём", + "synonyms": [ + { + "text": "Прием кардиолога", + "lang": "ru" + }, + { + "text": "Кардиолог консультация первичная", + "lang": "ru" + }, + { + "text": "Кардиолог қабылдауы", + "lang": "kz" + } + ] + }, + { + "canon_id": "mri_brain", + "canonical_ru": "МРТ головного мозга", + "canonical_kz": "Бас миының МРТ", + "canonical_en": "Brain MRI", + "category": "Диагностика/МРТ", + "code": "A05.23.009", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "МРТ г/м", + "lang": "ru" + }, + { + "text": "Магнитно-резонансная томография головного мозга", + "lang": "ru" + }, + { + "text": "Бас миы МРТ", + "lang": "kz" + }, + { + "text": "Brain MRI", + "lang": "en" + } + ] + }, + { + "canon_id": "biochem", + "canonical_ru": "Биохимический анализ крови", + "canonical_kz": "Қанның биохимиялық анализі", + "canonical_en": "Blood chemistry panel", + "category": "Лаборатория", + "code": "B03.016.004", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "Биохимия крови", + "lang": "ru" + }, + { + "text": "Б/х крови", + "lang": "ru" + }, + { + "text": "Биохимический анализ крови (базовый)", + "lang": "ru" + }, + { + "text": "Қан биохимиясы", + "lang": "kz" + } + ] + }, + { + "canon_id": "tooth_ext", + "canonical_ru": "Удаление зуба", + "canonical_kz": "Тісті жұлу", + "canonical_en": "Tooth extraction", + "category": "Стоматология", + "code": "A16.07.001", + "unit": "1 зуб", + "synonyms": [ + { + "text": "Удаление зуба простое", + "lang": "ru" + }, + { + "text": "Экстракция зуба", + "lang": "ru" + }, + { + "text": "Тіс жұлу", + "lang": "kz" + }, + { + "text": "Tooth removal", + "lang": "en" + } + ] + }, + { + "canon_id": "xray_chest", + "canonical_ru": "Рентген грудной клетки", + "canonical_kz": "Кеуде клеткасының рентгені", + "canonical_en": "Chest X-ray", + "category": "Диагностика", + "code": "A06.09.007", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "Рентгенография ОГК", + "lang": "ru" + }, + { + "text": "Флюорография", + "lang": "ru" + }, + { + "text": "Кеуде рентгені", + "lang": "kz" + }, + { + "text": "Chest X-ray", + "lang": "en" + } + ] + }, + { + "canon_id": "tsh", + "canonical_ru": "Тиреотропный гормон (ТТГ)", + "canonical_kz": "Тиреотропты гормон (ТТГ)", + "canonical_en": "Thyroid-stimulating hormone (TSH)", + "category": "Лаборатория", + "code": "B03.016.021", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "ТТГ", + "lang": "ru" + }, + { + "text": "Тиреотропный гормон", + "lang": "ru" + }, + { + "text": "TSH", + "lang": "en" + }, + { + "text": "ТТГ гормоны", + "lang": "kz" + } + ] + }, + { + "canon_id": "urinalysis", + "canonical_ru": "Общий анализ мочи", + "canonical_kz": "Зәрдің жалпы анализі", + "canonical_en": "Urinalysis", + "category": "Лаборатория", + "code": "B03.016.010", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "ОАМ", + "lang": "ru" + }, + { + "text": "Общий анализ мочи", + "lang": "ru" + }, + { + "text": "Зәр анализі", + "lang": "kz" + }, + { + "text": "Urine test", + "lang": "en" + } + ] + }, + { + "canon_id": "gyn", + "canonical_ru": "Приём гинеколога", + "canonical_kz": "Гинеколог қабылдауы", + "canonical_en": "Gynecologist consultation", + "category": "Приём специалистов", + "code": "B01.001.001", + "unit": "1 приём", + "synonyms": [ + { + "text": "Прием гинеколога", + "lang": "ru" + }, + { + "text": "Консультация гинеколога первичная", + "lang": "ru" + }, + { + "text": "Гинеколог кеңесі", + "lang": "kz" + } + ] + }, + { + "canon_id": "covid_pcr", + "canonical_ru": "ПЦР на COVID-19", + "canonical_kz": "COVID-19 ПТР", + "canonical_en": "COVID-19 PCR test", + "category": "Лаборатория", + "code": "A26.08.027", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "ПЦР COVID-19", + "lang": "ru" + }, + { + "text": "ПЦР-тест на коронавирус", + "lang": "ru" + }, + { + "text": "COVID PCR", + "lang": "en" + }, + { + "text": "COVID ПТР", + "lang": "kz" + } + ] + }, + { + "canon_id": "vit_d", + "canonical_ru": "Витамин D (25-OH)", + "canonical_kz": "D дәрумені (25-OH)", + "canonical_en": "Vitamin D (25-OH)", + "category": "Лаборатория", + "code": "B03.016.115", + "unit": "1 иссл.", + "synonyms": [ + { + "text": "25-OH витамин D", + "lang": "ru" + }, + { + "text": "Витамин Д", + "lang": "ru" + }, + { + "text": "Vitamin D", + "lang": "en" + }, + { + "text": "D витамині", + "lang": "kz" + } + ] + } +] \ No newline at end of file diff --git a/design/README.md b/design/README.md new file mode 100644 index 0000000..6909a9b --- /dev/null +++ b/design/README.md @@ -0,0 +1,28 @@ +# Дизайн-заготовка (бренд Nomad) + +Готовые токены для фронта (агент D). Полная дизайн-система: `Base/_Inbox/Хакатон Medtech/Дизайн-система — Nomad`. + +## Применение + +1. `tailwind.tokens.ts` → импортировать в `tailwind.config.ts`, разложить в `theme.extend`. +2. `globals.css` → переменные в `:root` (shadcn/ui). +3. Шрифт **Geologica** — `next/font` или `` на fonts.bunny.net (веса 300–700). + +## Шпаргалка + +| | | +|---|---| +| Primary (CTA) | `#ff4713`, текст белый, `rounded-lg`, hover `#e63e0f` | +| Вторичный | `#6b788e` (t-gray) | +| Фон страницы / карточек | `#f3f4f6` / белый `rounded-2xl shadow-card` | +| Успех / ошибка | `#0f973d` / `#dc2626` (фон `#fbe9e9`) | +| Шрифт | Geologica (заголовки 600–700, текст 400) | +| Контейнер | `max-w-container` (1280px) | + +## Ключевой компонент — таблица сравнения цен + +- Шапка `bg-light-gray`, текст `t-gray` 600/14px. +- Строки белые, разделители `#ebedf0`, hover `bg-nomad-soft`, цены вправо `tabular-nums`. +- Лучшая/нормализованная цена — бейдж `bg-nomad text-white rounded-full`. +- Статусы нормализации: success `#0f973d` · конфликт `#dc2626` · «проверить» `#ff4713`. +- shadcn: `Table` + `Badge` (variants default=оранжевый / success / destructive) + TanStack `DataTable`. diff --git a/design/globals.css b/design/globals.css new file mode 100644 index 0000000..7935100 --- /dev/null +++ b/design/globals.css @@ -0,0 +1,26 @@ +/* shadcn/ui тема под бренд Nomad — вставить в app/globals.css (:root) + Подключить шрифт Geologica: next/font или + */ +:root { + --radius: 0.5rem; /* база кнопок/инпутов Nomad */ + --background: 220 14% 96%; /* #f3f4f6 фон страницы */ + --foreground: 222 47% 11%; /* #111827 текст */ + --card: 0 0% 100%; /* белые карточки */ + --card-foreground: 222 47% 11%; + --popover: 0 0% 100%; + --popover-foreground: 222 47% 11%; + --primary: 11 100% 54%; /* #ff4713 бренд-оранжевый */ + --primary-foreground: 0 0% 100%; + --secondary: 218 15% 49%; /* #6b788e t-gray */ + --secondary-foreground: 0 0% 100%; + --muted: 210 20% 97%; /* #f5f7f9 light-gray */ + --muted-foreground: 220 9% 46%; /* #6b7280 */ + --accent: 16 100% 95%; /* #ffede7 nomad-soft */ + --accent-foreground: 11 100% 45%; + --destructive: 0 72% 51%; /* #dc2626 */ + --destructive-foreground: 0 0% 100%; + --success: 142 82% 32%; /* #0f973d */ + --border: 220 13% 91%; /* #ebedf0 */ + --input: 220 13% 85%; /* #d1d5db */ + --ring: 11 100% 54%; /* оранжевое кольцо фокуса */ +} diff --git a/design/tailwind.tokens.ts b/design/tailwind.tokens.ts new file mode 100644 index 0000000..999ae0e --- /dev/null +++ b/design/tailwind.tokens.ts @@ -0,0 +1,19 @@ +// Дизайн-токены Nomad Insurance — вставить в tailwind.config.ts → theme.extend +// Источник: nomad.kz (см. Base/_Inbox/Хакатон Medtech/Дизайн-система — Nomad) +export const nomadTheme = { + colors: { + nomad: { DEFAULT: "#ff4713", soft: "#ffede7", dark: "#e63e0f" }, // бренд-оранжевый + "t-gray": "#6b788e", // сине-серый (вторичный) + "light-gray": "#f5f7f9", // фон секций/карточек + link: "#2563eb", + success: { DEFAULT: "#0f973d", soft: "#e7f6ec" }, + error: { DEFAULT: "#dc2626", soft: "#fbe9e9" }, + ink: "#111827", // основной текст + }, + fontFamily: { + sans: ["Geologica", "ui-sans-serif", "system-ui", "sans-serif"], + }, + borderRadius: { lg: "0.5rem", xl: "0.75rem", "2xl": "1rem", "3xl": "1.5rem" }, + maxWidth: { container: "80rem" }, // 1280px — контейнер Nomad + boxShadow: { card: "0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1)" }, +}; diff --git a/fixtures/README.md b/fixtures/README.md index 7a180fe..edea9f4 100644 --- a/fixtures/README.md +++ b/fixtures/README.md @@ -15,6 +15,11 @@ Одни и те же услуги названы по-разному между файлами (ОАК / Общий анализ крови / Қанның жалпы анализі) — это материал для канонизации и дедупликации. +## `normalized/golden.json` — эталон нормализации (52 строки) + +Оракул для TDD ETL: для каждой строки из `raw/` указан `canon_id`, `canonical_ru/kz/en`, `category`, `code`, `price`, `currency`, `unit`, `source_file`. Сгенерирован тем же скриптом, что и raw → гарантированно согласован. + +Справочник канонов: `../contracts/service_canon.json` (15 услуг, синонимы с lang-тегами). + ## TODO (Фаза 0 хакатона) -- `normalized/*.json` — эталонные нормализованные записи (для TDD ETL) - `api/*.json` — эталонные ответы API (для MSW-моков фронта) diff --git a/fixtures/normalized/golden.json b/fixtures/normalized/golden.json new file mode 100644 index 0000000..4e6f46d --- /dev/null +++ b/fixtures/normalized/golden.json @@ -0,0 +1,782 @@ +[ + { + "source_file": "clinic_alpha_clean.xlsx", + "clinic": "Клиника «Аль-Шифа»", + "raw_name": "Тиреотропный гормон (ТТГ)", + "lang": "ru", + "canon_id": "tsh", + "canonical_ru": "Тиреотропный гормон (ТТГ)", + "canonical_kz": "Тиреотропты гормон (ТТГ)", + "canonical_en": "Thyroid-stimulating hormone (TSH)", + "category": "Лаборатория", + "code": "B03.016.021", + "price": 3600, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_alpha_clean.xlsx", + "clinic": "Клиника «Аль-Шифа»", + "raw_name": "Abdominal US", + "lang": "en", + "canon_id": "uzi_abd", + "canonical_ru": "УЗИ органов брюшной полости", + "canonical_kz": "Құрсақ қуысы ағзаларының УДЗ", + "canonical_en": "Abdominal ultrasound", + "category": "Диагностика/УЗИ", + "code": "A04.16.001", + "price": 12900, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_alpha_clean.xlsx", + "clinic": "Клиника «Аль-Шифа»", + "raw_name": "Общий анализ крови", + "lang": "ru", + "canon_id": "cbc", + "canonical_ru": "Общий анализ крови", + "canonical_kz": "Қанның жалпы анализі", + "canonical_en": "Complete blood count (CBC)", + "category": "Лаборатория", + "code": "B03.016.003", + "price": 3900, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_alpha_clean.xlsx", + "clinic": "Клиника «Аль-Шифа»", + "raw_name": "Общий анализ мочи", + "lang": "ru", + "canon_id": "urinalysis", + "canonical_ru": "Общий анализ мочи", + "canonical_kz": "Зәрдің жалпы анализі", + "canonical_en": "Urinalysis", + "category": "Лаборатория", + "code": "B03.016.010", + "price": 900, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_alpha_clean.xlsx", + "clinic": "Клиника «Аль-Шифа»", + "raw_name": "Глюкоза крови", + "lang": "ru", + "canon_id": "glucose", + "canonical_ru": "Глюкоза крови", + "canonical_kz": "Қандағы глюкоза", + "canonical_en": "Blood glucose", + "category": "Лаборатория", + "code": "B03.016.006", + "price": 1100, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_alpha_clean.xlsx", + "clinic": "Клиника «Аль-Шифа»", + "raw_name": "ЭКГ", + "lang": "ru", + "canon_id": "ecg", + "canonical_ru": "Электрокардиография (ЭКГ)", + "canonical_kz": "Электрокардиография (ЭКГ)", + "canonical_en": "Electrocardiography (ECG)", + "category": "Диагностика", + "code": "A05.10.006", + "price": 3600, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_alpha_clean.xlsx", + "clinic": "Клиника «Аль-Шифа»", + "raw_name": "Chest X-ray", + "lang": "en", + "canon_id": "xray_chest", + "canonical_ru": "Рентген грудной клетки", + "canonical_kz": "Кеуде клеткасының рентгені", + "canonical_en": "Chest X-ray", + "category": "Диагностика", + "code": "A06.09.007", + "price": 2600, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_alpha_clean.xlsx", + "clinic": "Клиника «Аль-Шифа»", + "raw_name": "GP visit", + "lang": "en", + "canon_id": "therapist", + "canonical_ru": "Приём врача-терапевта", + "canonical_kz": "Терапевт дәрігердің қабылдауы", + "canonical_en": "General practitioner consultation", + "category": "Приём специалистов", + "code": "B01.047.001", + "price": 5200, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_alpha_clean.xlsx", + "clinic": "Клиника «Аль-Шифа»", + "raw_name": "Кардиолог қабылдауы", + "lang": "kz", + "canon_id": "cardiologist", + "canonical_ru": "Консультация кардиолога", + "canonical_kz": "Кардиолог кеңесі", + "canonical_en": "Cardiologist consultation", + "category": "Приём специалистов", + "code": "B01.015.001", + "price": 7800, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_beta_dirty.xlsx", + "clinic": "Медцентр «Сункар»", + "raw_name": "Биохимия крови", + "lang": "ru", + "canon_id": "biochem", + "canonical_ru": "Биохимический анализ крови", + "canonical_kz": "Қанның биохимиялық анализі", + "canonical_en": "Blood chemistry panel", + "category": "Лаборатория", + "code": "B03.016.004", + "price": 8300, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_beta_dirty.xlsx", + "clinic": "Медцентр «Сункар»", + "raw_name": "Флюорография", + "lang": "ru", + "canon_id": "xray_chest", + "canonical_ru": "Рентген грудной клетки", + "canonical_kz": "Кеуде клеткасының рентгені", + "canonical_en": "Chest X-ray", + "category": "Диагностика", + "code": "A06.09.007", + "price": 3100, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_beta_dirty.xlsx", + "clinic": "Медцентр «Сункар»", + "raw_name": "Глюкоза крови", + "lang": "ru", + "canon_id": "glucose", + "canonical_ru": "Глюкоза крови", + "canonical_kz": "Қандағы глюкоза", + "canonical_en": "Blood glucose", + "category": "Лаборатория", + "code": "B03.016.006", + "price": 1400, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_beta_dirty.xlsx", + "clinic": "Медцентр «Сункар»", + "raw_name": "Общий анализ крови", + "lang": "ru", + "canon_id": "cbc", + "canonical_ru": "Общий анализ крови", + "canonical_kz": "Қанның жалпы анализі", + "canonical_en": "Complete blood count (CBC)", + "category": "Лаборатория", + "code": "B03.016.003", + "price": 3400, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_beta_dirty.xlsx", + "clinic": "Медцентр «Сункар»", + "raw_name": "Консультация терапевта первичная", + "lang": "ru", + "canon_id": "therapist", + "canonical_ru": "Приём врача-терапевта", + "canonical_kz": "Терапевт дәрігердің қабылдауы", + "canonical_en": "General practitioner consultation", + "category": "Приём специалистов", + "code": "B01.047.001", + "price": 7800, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_beta_dirty.xlsx", + "clinic": "Медцентр «Сункар»", + "raw_name": "Магнитно-резонансная томография головного мозга", + "lang": "ru", + "canon_id": "mri_brain", + "canonical_ru": "МРТ головного мозга", + "canonical_kz": "Бас миының МРТ", + "canonical_en": "Brain MRI", + "category": "Диагностика/МРТ", + "code": "A05.23.009", + "price": 19100, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_beta_dirty.xlsx", + "clinic": "Медцентр «Сункар»", + "raw_name": "Кардиолог қабылдауы", + "lang": "kz", + "canon_id": "cardiologist", + "canonical_ru": "Консультация кардиолога", + "canonical_kz": "Кардиолог кеңесі", + "canonical_en": "Cardiologist consultation", + "category": "Приём специалистов", + "code": "B01.015.001", + "price": 11800, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_beta_dirty.xlsx", + "clinic": "Медцентр «Сункар»", + "raw_name": "Приём гинеколога", + "lang": "ru", + "canon_id": "gyn", + "canonical_ru": "Приём гинеколога", + "canonical_kz": "Гинеколог қабылдауы", + "canonical_en": "Gynecologist consultation", + "category": "Приём специалистов", + "code": "B01.001.001", + "price": 10900, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_beta_dirty.xlsx", + "clinic": "Медцентр «Сункар»", + "raw_name": "Ультразвуковое исследование брюшной полости", + "lang": "ru", + "canon_id": "uzi_abd", + "canonical_ru": "УЗИ органов брюшной полости", + "canonical_kz": "Құрсақ қуысы ағзаларының УДЗ", + "canonical_en": "Abdominal ultrasound", + "category": "Диагностика/УЗИ", + "code": "A04.16.001", + "price": 7000, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_gamma.csv", + "clinic": "Diagnostic Lab Astana", + "raw_name": "УЗИ ОБП", + "lang": "ru", + "canon_id": "uzi_abd", + "canonical_ru": "УЗИ органов брюшной полости", + "canonical_kz": "Құрсақ қуысы ағзаларының УДЗ", + "canonical_en": "Abdominal ultrasound", + "category": "Диагностика/УЗИ", + "code": "A04.16.001", + "price": 10700, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_gamma.csv", + "clinic": "Diagnostic Lab Astana", + "raw_name": "ПЦР-тест на коронавирус", + "lang": "ru", + "canon_id": "covid_pcr", + "canonical_ru": "ПЦР на COVID-19", + "canonical_kz": "COVID-19 ПТР", + "canonical_en": "COVID-19 PCR test", + "category": "Лаборатория", + "code": "A26.08.027", + "price": 9600, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_gamma.csv", + "clinic": "Diagnostic Lab Astana", + "raw_name": "ЭКГ + описание", + "lang": "ru", + "canon_id": "ecg", + "canonical_ru": "Электрокардиография (ЭКГ)", + "canonical_kz": "Электрокардиография (ЭКГ)", + "canonical_en": "Electrocardiography (ECG)", + "category": "Диагностика", + "code": "A05.10.006", + "price": 2800, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_gamma.csv", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Витамин D (25-OH)", + "lang": "ru", + "canon_id": "vit_d", + "canonical_ru": "Витамин D (25-OH)", + "canonical_kz": "D дәрумені (25-OH)", + "canonical_en": "Vitamin D (25-OH)", + "category": "Лаборатория", + "code": "B03.016.115", + "price": 9800, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_gamma.csv", + "clinic": "Diagnostic Lab Astana", + "raw_name": "МРТ г/м", + "lang": "ru", + "canon_id": "mri_brain", + "canonical_ru": "МРТ головного мозга", + "canonical_kz": "Бас миының МРТ", + "canonical_en": "Brain MRI", + "category": "Диагностика/МРТ", + "code": "A05.23.009", + "price": 31600, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_gamma.csv", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Глюкоза", + "lang": "ru", + "canon_id": "glucose", + "canonical_ru": "Глюкоза крови", + "canonical_kz": "Қандағы глюкоза", + "canonical_en": "Blood glucose", + "category": "Лаборатория", + "code": "B03.016.006", + "price": 1000, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_gamma.csv", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Биохимический анализ крови (базовый)", + "lang": "ru", + "canon_id": "biochem", + "canonical_ru": "Биохимический анализ крови", + "canonical_kz": "Қанның биохимиялық анализі", + "canonical_en": "Blood chemistry panel", + "category": "Лаборатория", + "code": "B03.016.004", + "price": 5900, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_gamma.csv", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Кардиолог консультация первичная", + "lang": "ru", + "canon_id": "cardiologist", + "canonical_ru": "Консультация кардиолога", + "canonical_kz": "Кардиолог кеңесі", + "canonical_en": "Cardiologist consultation", + "category": "Приём специалистов", + "code": "B01.015.001", + "price": 7800, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_delta_messy.txt", + "clinic": "Медцентр «Тенгри»", + "raw_name": "TSH", + "lang": "en", + "canon_id": "tsh", + "canonical_ru": "Тиреотропный гормон (ТТГ)", + "canonical_kz": "Тиреотропты гормон (ТТГ)", + "canonical_en": "Thyroid-stimulating hormone (TSH)", + "category": "Лаборатория", + "code": "B03.016.021", + "price": 2300, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_delta_messy.txt", + "clinic": "Медцентр «Тенгри»", + "raw_name": "Консультация кардиолога", + "lang": "ru", + "canon_id": "cardiologist", + "canonical_ru": "Консультация кардиолога", + "canonical_kz": "Кардиолог кеңесі", + "canonical_en": "Cardiologist consultation", + "category": "Приём специалистов", + "code": "B01.015.001", + "price": 7700, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_delta_messy.txt", + "clinic": "Медцентр «Тенгри»", + "raw_name": "Консультация гинеколога первичная", + "lang": "ru", + "canon_id": "gyn", + "canonical_ru": "Приём гинеколога", + "canonical_kz": "Гинеколог қабылдауы", + "canonical_en": "Gynecologist consultation", + "category": "Приём специалистов", + "code": "B01.001.001", + "price": 6300, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_delta_messy.txt", + "clinic": "Медцентр «Тенгри»", + "raw_name": "CBC", + "lang": "en", + "canon_id": "cbc", + "canonical_ru": "Общий анализ крови", + "canonical_kz": "Қанның жалпы анализі", + "canonical_en": "Complete blood count (CBC)", + "category": "Лаборатория", + "code": "B03.016.003", + "price": 4300, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_delta_messy.txt", + "clinic": "Медцентр «Тенгри»", + "raw_name": "Электрокардиография", + "lang": "ru", + "canon_id": "ecg", + "canonical_ru": "Электрокардиография (ЭКГ)", + "canonical_kz": "Электрокардиография (ЭКГ)", + "canonical_en": "Electrocardiography (ECG)", + "category": "Диагностика", + "code": "A05.10.006", + "price": 4800, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_delta_messy.txt", + "clinic": "Медцентр «Тенгри»", + "raw_name": "Зәр анализі", + "lang": "kz", + "canon_id": "urinalysis", + "canonical_ru": "Общий анализ мочи", + "canonical_kz": "Зәрдің жалпы анализі", + "canonical_en": "Urinalysis", + "category": "Лаборатория", + "code": "B03.016.010", + "price": 1300, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_delta_messy.txt", + "clinic": "Медцентр «Тенгри»", + "raw_name": "ПЦР-тест на коронавирус", + "lang": "ru", + "canon_id": "covid_pcr", + "canonical_ru": "ПЦР на COVID-19", + "canonical_kz": "COVID-19 ПТР", + "canonical_en": "COVID-19 PCR test", + "category": "Лаборатория", + "code": "A26.08.027", + "price": 8700, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_epsilon_scan.png", + "clinic": "Клиника «Береке»", + "raw_name": "Рентген грудной клетки", + "lang": "ru", + "canon_id": "xray_chest", + "canonical_ru": "Рентген грудной клетки", + "canonical_kz": "Кеуде клеткасының рентгені", + "canonical_en": "Chest X-ray", + "category": "Диагностика", + "code": "A06.09.007", + "price": 3200, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_epsilon_scan.png", + "clinic": "Клиника «Береке»", + "raw_name": "Abdominal US", + "lang": "en", + "canon_id": "uzi_abd", + "canonical_ru": "УЗИ органов брюшной полости", + "canonical_kz": "Құрсақ қуысы ағзаларының УДЗ", + "canonical_en": "Abdominal ultrasound", + "category": "Диагностика/УЗИ", + "code": "A04.16.001", + "price": 12800, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_epsilon_scan.png", + "clinic": "Клиника «Береке»", + "raw_name": "Магнитно-резонансная томография головного мозга", + "lang": "ru", + "canon_id": "mri_brain", + "canonical_ru": "МРТ головного мозга", + "canonical_kz": "Бас миының МРТ", + "canonical_en": "Brain MRI", + "category": "Диагностика/МРТ", + "code": "A05.23.009", + "price": 34400, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_epsilon_scan.png", + "clinic": "Клиника «Береке»", + "raw_name": "Консультация гинеколога первичная", + "lang": "ru", + "canon_id": "gyn", + "canonical_ru": "Приём гинеколога", + "canonical_kz": "Гинеколог қабылдауы", + "canonical_en": "Gynecologist consultation", + "category": "Приём специалистов", + "code": "B01.001.001", + "price": 5700, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_epsilon_scan.png", + "clinic": "Клиника «Береке»", + "raw_name": "Витамин Д", + "lang": "ru", + "canon_id": "vit_d", + "canonical_ru": "Витамин D (25-OH)", + "canonical_kz": "D дәрумені (25-OH)", + "canonical_en": "Vitamin D (25-OH)", + "category": "Лаборатория", + "code": "B03.016.115", + "price": 8700, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_epsilon_scan.png", + "clinic": "Клиника «Береке»", + "raw_name": "Биохимия крови", + "lang": "ru", + "canon_id": "biochem", + "canonical_ru": "Биохимический анализ крови", + "canonical_kz": "Қанның биохимиялық анализі", + "canonical_en": "Blood chemistry panel", + "category": "Лаборатория", + "code": "B03.016.004", + "price": 6400, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_epsilon_scan.png", + "clinic": "Клиника «Береке»", + "raw_name": "Удаление зуба", + "lang": "ru", + "canon_id": "tooth_ext", + "canonical_ru": "Удаление зуба", + "canonical_kz": "Тісті жұлу", + "canonical_en": "Tooth extraction", + "category": "Стоматология", + "code": "A16.07.001", + "price": 9300, + "currency": "KZT", + "unit": "1 зуб" + }, + { + "source_file": "clinic_epsilon_scan.png", + "clinic": "Клиника «Береке»", + "raw_name": "Glucose", + "lang": "en", + "canon_id": "glucose", + "canonical_ru": "Глюкоза крови", + "canonical_kz": "Қандағы глюкоза", + "canonical_en": "Blood glucose", + "category": "Лаборатория", + "code": "B03.016.006", + "price": 2000, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_epsilon_scan.png", + "clinic": "Клиника «Береке»", + "raw_name": "ТТГ", + "lang": "ru", + "canon_id": "tsh", + "canonical_ru": "Тиреотропный гормон (ТТГ)", + "canonical_kz": "Тиреотропты гормон (ТТГ)", + "canonical_en": "Thyroid-stimulating hormone (TSH)", + "category": "Лаборатория", + "code": "B03.016.021", + "price": 3100, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "D витамині", + "lang": "kz", + "canon_id": "vit_d", + "canonical_ru": "Витамин D (25-OH)", + "canonical_kz": "D дәрумені (25-OH)", + "canonical_en": "Vitamin D (25-OH)", + "category": "Лаборатория", + "code": "B03.016.115", + "price": 10900, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Құрсақ қуысы УДЗ", + "lang": "kz", + "canon_id": "uzi_abd", + "canonical_ru": "УЗИ органов брюшной полости", + "canonical_kz": "Құрсақ қуысы ағзаларының УДЗ", + "canonical_en": "Abdominal ultrasound", + "category": "Диагностика/УЗИ", + "code": "A04.16.001", + "price": 6000, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "ТТГ гормоны", + "lang": "kz", + "canon_id": "tsh", + "canonical_ru": "Тиреотропный гормон (ТТГ)", + "canonical_kz": "Тиреотропты гормон (ТТГ)", + "canonical_en": "Thyroid-stimulating hormone (TSH)", + "category": "Лаборатория", + "code": "B03.016.021", + "price": 2500, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Қандағы глюкоза", + "lang": "kz", + "canon_id": "glucose", + "canonical_ru": "Глюкоза крови", + "canonical_kz": "Қандағы глюкоза", + "canonical_en": "Blood glucose", + "category": "Лаборатория", + "code": "B03.016.006", + "price": 800, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Приём гинеколога", + "lang": "ru", + "canon_id": "gyn", + "canonical_ru": "Приём гинеколога", + "canonical_kz": "Гинеколог қабылдауы", + "canonical_en": "Gynecologist consultation", + "category": "Приём специалистов", + "code": "B01.001.001", + "price": 10900, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Экстракция зуба", + "lang": "ru", + "canon_id": "tooth_ext", + "canonical_ru": "Удаление зуба", + "canonical_kz": "Тісті жұлу", + "canonical_en": "Tooth extraction", + "category": "Стоматология", + "code": "A16.07.001", + "price": 9900, + "currency": "KZT", + "unit": "1 зуб" + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "ЭКГ", + "lang": "ru", + "canon_id": "ecg", + "canonical_ru": "Электрокардиография (ЭКГ)", + "canonical_kz": "Электрокардиография (ЭКГ)", + "canonical_en": "Electrocardiography (ECG)", + "category": "Диагностика", + "code": "A05.10.006", + "price": 2100, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Прием терапевта", + "lang": "ru", + "canon_id": "therapist", + "canonical_ru": "Приём врача-терапевта", + "canonical_kz": "Терапевт дәрігердің қабылдауы", + "canonical_en": "General practitioner consultation", + "category": "Приём специалистов", + "code": "B01.047.001", + "price": 7600, + "currency": "KZT", + "unit": "1 приём" + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "Биохимический анализ крови", + "lang": "ru", + "canon_id": "biochem", + "canonical_ru": "Биохимический анализ крови", + "canonical_kz": "Қанның биохимиялық анализі", + "canonical_en": "Blood chemistry panel", + "category": "Лаборатория", + "code": "B03.016.004", + "price": 4000, + "currency": "KZT", + "unit": "1 иссл." + }, + { + "source_file": "clinic_zeta_prices.pdf", + "clinic": "Diagnostic Lab Astana", + "raw_name": "COVID PCR", + "lang": "en", + "canon_id": "covid_pcr", + "canonical_ru": "ПЦР на COVID-19", + "canonical_kz": "COVID-19 ПТР", + "canonical_en": "COVID-19 PCR test", + "category": "Лаборатория", + "code": "A26.08.027", + "price": 7800, + "currency": "KZT", + "unit": "1 иссл." + } +] \ No newline at end of file diff --git a/fixtures/raw/clinic_alpha_clean.xlsx b/fixtures/raw/clinic_alpha_clean.xlsx index f525594..b21d4e4 100644 Binary files a/fixtures/raw/clinic_alpha_clean.xlsx and b/fixtures/raw/clinic_alpha_clean.xlsx differ diff --git a/fixtures/raw/clinic_beta_dirty.xlsx b/fixtures/raw/clinic_beta_dirty.xlsx index dd032cf..b6c06a7 100644 Binary files a/fixtures/raw/clinic_beta_dirty.xlsx and b/fixtures/raw/clinic_beta_dirty.xlsx differ diff --git a/fixtures/raw/clinic_delta_messy.txt b/fixtures/raw/clinic_delta_messy.txt index a1ec6a8..6c2b505 100644 --- a/fixtures/raw/clinic_delta_messy.txt +++ b/fixtures/raw/clinic_delta_messy.txt @@ -1,11 +1,11 @@ МЕДЦЕНТР «ТЕНГРИ» — действующие цены -Қан биохимиясы — 8300 KZT (1 иссл.) -Терапевт қабылдауы 7000 KZT (1 приём) -Глюкоза крови: 1100 тг (1 иссл.) -Құрсақ қуысы УДЗ — 6700 ₸ (1 иссл.) -МРТ головного мозга: 21600 тг (1 иссл.) -Рентгенография ОГК: 5100 KZT (1 иссл.) -Удаление зуба простое ... 12900 KZT (1 зуб) +TSH ... 2300 ₸ (1 иссл.) +Консультация кардиолога 7700 KZT (1 приём) +Консультация гинеколога первичная: 6300 тенге (1 приём) +CBC ... 4300 KZT (1 иссл.) +Электрокардиография — 4800 тг (1 иссл.) +Зәр анализі — 1300 тенге (1 иссл.) +ПЦР-тест на коронавирус ... 8700 KZT (1 иссл.) * Цены справочные, уточняйте в регистратуре. Бағалар анықтамалық. diff --git a/fixtures/raw/clinic_epsilon_scan.png b/fixtures/raw/clinic_epsilon_scan.png index 4d929c5..53ba6c4 100644 Binary files a/fixtures/raw/clinic_epsilon_scan.png and b/fixtures/raw/clinic_epsilon_scan.png differ diff --git a/fixtures/raw/clinic_gamma.csv b/fixtures/raw/clinic_gamma.csv index 524c01f..13605d3 100644 --- a/fixtures/raw/clinic_gamma.csv +++ b/fixtures/raw/clinic_gamma.csv @@ -1,9 +1,9 @@ service;price_kzt;unit -УЗИ ОБП;11600;1 иссл. -Приём врача-терапевта;4500;1 приём -Электрокардиография;2100;1 иссл. -Флюорография;4100;1 иссл. -Б/х крови;4000;1 иссл. -Консультация кардиолога;9600;1 приём -Экстракция зуба;9500;1 зуб -Қандағы глюкоза;1600;1 иссл. +УЗИ ОБП;10700;1 иссл. +ПЦР-тест на коронавирус;9600;1 иссл. +ЭКГ + описание;2800;1 иссл. +Витамин D (25-OH);9800;1 иссл. +МРТ г/м;31600;1 иссл. +Глюкоза;1000;1 иссл. +Биохимический анализ крови (базовый);5900;1 иссл. +Кардиолог консультация первичная;7800;1 приём diff --git a/fixtures/raw/clinic_zeta_prices.pdf b/fixtures/raw/clinic_zeta_prices.pdf index 2d35dca..7356537 100644 Binary files a/fixtures/raw/clinic_zeta_prices.pdf and b/fixtures/raw/clinic_zeta_prices.pdf differ diff --git a/pitch/demo-script.md b/pitch/demo-script.md new file mode 100644 index 0000000..4e26989 --- /dev/null +++ b/pitch/demo-script.md @@ -0,0 +1,24 @@ +# Демо-сценарий (≈3 минуты) + +Один эффект, доведённый до «вау»: **хаос форматов на входе → сравнимая таблица за минуту**. + +## Шаги + +| Время | Действие | Что говорю | +|---|---|---| +| 0:00–0:15 | Открыт пустой экран загрузки | «У клиник-партнёров прайсы в разных форматах. Смотрите, что система с этим делает.» | +| 0:15–0:35 | Перетаскиваю **пёструю пачку** из `fixtures/raw/`: чистый Excel + грязный Excel (секции, «от») + CSV + текст + **фото-скан (наклонённый)** + PDF; часть на казахском, 1 заведомо мусорный. Жму «Обработать» | «Бросаю всё разом — Excel, скан, фото, казахский.» | +| 0:35–1:05 | Видно прогресс: у каждого файла статус и **путь извлечения** (structured / vision). Строки появляются в реальном времени | «Система сама выбрала быстрый путь для Excel и распознавание для фото.» | +| 1:05–2:05 | Открывается **единая таблица**. Ввожу поиск «УЗИ брюшной полости» → сводятся `УЗИ ОБП`, `Abdominal US`, `Құрсақ қуысы УДЗ` из разных клиник в **одну строку сравнения цен** | «Разные формулировки, три языка — одна услуга. Вот сравнение цен между клиниками.» (момент «вау») | +| 2:05–2:25 | Клик по строке с **жёлтой подсветкой** (low confidence) → всплывает оригинальный фрагмент файла рядом с извлечённым значением | «Система честно помечает, что проверить вручную — для медданных это важно.» | +| 2:25–2:40 | Жму **Экспорт в Excel** → скачивается чистый сводный прайс | «Из архива хаоса — готовый сравнительный прайс за минуту.» | + +## ⚠️ Чек-лист «не провалить live-demo» + +- [ ] **Fallback-видео демо (60–90 сек) записано накануне ночью** — переключаюсь без паузы, если упало. +- [ ] Демо работает **локально без интернета** (или предобработанный результат в кэше на случай тупящего ИИ-вызова на сцене). +- [ ] Данные демо прогнаны заранее, известно — на какой секунде что нажимать (**10+ репетиций с секундомером**). +- [ ] Запасное устройство (второй ноут / телефон-точка). +- [ ] Скриншоты ключевых экранов в слайдах (крайний fallback). +- [ ] Браузер: лишние вкладки закрыты, нотификации выкл, шрифт крупный (видно с проектора). +- [ ] Прод `med.secondbrain.tools` проверен с чужого устройства/сети перед выходом. diff --git a/pitch/outline.md b/pitch/outline.md new file mode 100644 index 0000000..408de7d --- /dev/null +++ b/pitch/outline.md @@ -0,0 +1,37 @@ +# Питч — структура (6–7 слайдов, ~3 мин речи + демо) + +Цель: ~400 слов речи. Первые 20 сек — крючок, без «здравствуйте, мы команда». Один назначенный спикер (Дмитрий). Демо в центре. + +## Слайд 1 — Хук + проблема (20–30 сек) +«У страховой/сети — десятки клиник-партнёров. Каждая шлёт прайс как умеет: Excel, скан, фото на телефон, половина на казахском. Свести это в единый прайс для ДМС/сравнения — недели ручной работы и ошибки в цене, которую видит пациент». +> Назвать масштаб боли и конкретного бенефициара (прайс-аналитик / менеджер партнёрской сети). + +## Слайд 2 — Решение в одном предложении (15 сек) +«Система, которая автоматически читает прайс **в любом формате** и превращает архив в единую актуальную базу цен — с поиском и сравнением между клиниками». + +## Слайд 3 — 🔴 ЖИВОЕ ДЕМО (60–90 сек) — ядро +См. `demo-script.md`. Перетащил пёструю пачку → одна кнопка → единая таблица → поиск сводит RU/KZ/EN формулировки в одну строку сравнения → клик по low-confidence показывает оригинал → экспорт. + +## Слайд 4 — Как это работает (20 сек) +Простая схема: **любые форматы → ИИ-извлечение + нормализация → единый каталог услуг**. Без технического занудства. Подчеркнуть: один путь на Excel/PDF/скан/фото, RU/KZ/EN. + +## Слайд 5 — Метрики / impact (20 сек) +- Онбординг прайса клиники: **часы ручного ввода → минуты**. +- % автоматически распознанных строк, точность матчинга, % строк на ручную проверку. +- «Административная автоматизация — самый окупаемый сегмент medtech AI». + +## Слайд 6 — Бизнес и масштаб (20 сек) +- Кому: страховщик с ДМС (Nomad), сеть клиник, мед-маркетплейс. +- Кейс 1 = data-движок Кейса 2 (агрегатор/сравнение цен) → монетизация. +- Масштаб: с десятков клиник → на сотни по РК → СНГ. Язык Terricon: **экспорт, продажи, масштаб**. + +## Слайд 7 — Закрытие (15 сек) +Сильная фраза: «Из архива хаоса — готовый сравнительный прайс за минуту». + «что дальше за неделю» (доменный справочник услуг РК, интеграция в CRM страховщика). Ничего не просить — оставить впечатление. + +--- + +### Помнить +- Доверие к медданным: confidence-score + флаги ручной проверки + ссылка на оригинал. +- Регуляторно безопасно: работаем с публичными коммерческими прайсами, не с данными пациентов — проговорить. +- Прогресс именно за 48 ч (связка с ИИ-агентами — честное преимущество по скорости). +- Слайдов максимум 7. Демо > слова. diff --git a/scripts/gen_fixtures.py b/scripts/gen_fixtures.py index 6e7a349..86e32bb 100644 --- a/scripts/gen_fixtures.py +++ b/scripts/gen_fixtures.py @@ -1,149 +1,164 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -"""Генератор синтетических прайсов клиник (RU/KZ) в разных форматах для отладки pipeline.""" -import csv, os, random, subprocess, sys +"""Генератор синтетических прайсов клиник (RU/KZ/EN) + эталонная нормализация + справочник канонов. -OUT = sys.argv[1] if len(sys.argv) > 1 else "fixtures/raw" -os.makedirs(OUT, exist_ok=True) -random.seed(42) # детерминированно +Выдаёт: + fixtures/raw/* — 6 форматов «сырых» прайсов (Excel/CSV/txt/PNG/PDF) + fixtures/normalized/golden.json — эталон: каждая строка → канон (оракул для TDD ETL) + contracts/service_canon.json — справочник канонических услуг (seed для нормализации) +Детерминированно (seed=42). +""" +import csv, json, os, random, subprocess, sys + +ROOT = sys.argv[1] if len(sys.argv) > 1 else "." +RAW = os.path.join(ROOT, "fixtures/raw") +NORM = os.path.join(ROOT, "fixtures/normalized") +CONTRACTS = os.path.join(ROOT, "contracts") +for d in (RAW, NORM, CONTRACTS): + os.makedirs(d, exist_ok=True) +random.seed(42) def ensure(pkg, mod=None): - try: - __import__(mod or pkg) - except ImportError: - subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", pkg]) + try: __import__(mod or pkg) + except ImportError: subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", pkg]) -# Базовый каталог: (канон RU, канон KZ, [варианты названий], мин, макс, единица) -CATALOG = [ - ("Общий анализ крови", "Қанның жалпы анализі", - ["Общий анализ крови", "ОАК", "Общий анализ крови (развернутый)", "Клинический анализ крови", "Қанның жалпы анализі"], - 1200, 4500, "1 иссл."), - ("УЗИ органов брюшной полости", "Құрсақ қуысы ағзаларының УДЗ", - ["УЗИ органов брюшной полости", "УЗИ ОБП", "УЗИ брюшной полости", "Құрсақ қуысы УДЗ"], - 6000, 13000, "1 иссл."), - ("Приём врача-терапевта", "Терапевт дәрігердің қабылдауы", - ["Прием терапевта", "Консультация терапевта первичная", "Приём врача-терапевта", "Терапевт қабылдауы"], - 4000, 9000, "1 приём"), - ("Электрокардиография (ЭКГ)", "Электрокардиография (ЭКГ)", - ["ЭКГ", "Электрокардиография", "ЭКГ с расшифровкой", "ЭКГ + описание"], - 2000, 5000, "1 иссл."), - ("Глюкоза крови", "Қандағы глюкоза", - ["Глюкоза", "Глюкоза крови", "Сахар крови", "Қандағы глюкоза"], - 800, 2200, "1 иссл."), - ("Консультация кардиолога", "Кардиолог кеңесі", - ["Консультация кардиолога", "Прием кардиолога", "Кардиолог консультация первичная"], - 5000, 12000, "1 приём"), - ("МРТ головного мозга", "Бас миының МРТ", - ["МРТ головного мозга", "Магнитно-резонансная томография головного мозга", "МРТ г/м", "Бас миы МРТ"], - 18000, 35000, "1 иссл."), - ("Биохимический анализ крови", "Қанның биохимиялық анализі", - ["Биохимия крови", "Биохимический анализ крови (базовый)", "Б/х крови", "Қан биохимиясы"], - 3500, 9000, "1 иссл."), - ("Удаление зуба", "Тісті жұлу", - ["Удаление зуба", "Удаление зуба простое", "Экстракция зуба", "Тіс жұлу"], - 6000, 15000, "1 зуб"), - ("Рентген грудной клетки", "Кеуде клеткасының рентгені", - ["Рентген грудной клетки", "Рентгенография ОГК", "Флюорография", "Кеуде рентгені"], - 2500, 6000, "1 иссл."), +# Справочник канонических медуслуг. lang-тегированные синонимы — материал для нормализации. +CANONS = [ + {"id":"cbc","ru":"Общий анализ крови","kz":"Қанның жалпы анализі","en":"Complete blood count (CBC)", + "category":"Лаборатория","code":"B03.016.003","unit":"1 иссл.","price":(1200,4500), + "syn":[("ОАК","ru"),("Общий анализ крови (развернутый)","ru"),("Клинический анализ крови","ru"),("КАК","ru"),("CBC","en"),("Қанның жалпы талдауы","kz")]}, + {"id":"uzi_abd","ru":"УЗИ органов брюшной полости","kz":"Құрсақ қуысы ағзаларының УДЗ","en":"Abdominal ultrasound", + "category":"Диагностика/УЗИ","code":"A04.16.001","unit":"1 иссл.","price":(6000,13000), + "syn":[("УЗИ ОБП","ru"),("УЗИ брюшной полости","ru"),("Ультразвуковое исследование брюшной полости","ru"),("Құрсақ қуысы УДЗ","kz"),("Abdominal US","en")]}, + {"id":"therapist","ru":"Приём врача-терапевта","kz":"Терапевт дәрігердің қабылдауы","en":"General practitioner consultation", + "category":"Приём специалистов","code":"B01.047.001","unit":"1 приём","price":(4000,9000), + "syn":[("Прием терапевта","ru"),("Консультация терапевта первичная","ru"),("Терапевт қабылдауы","kz"),("GP visit","en")]}, + {"id":"ecg","ru":"Электрокардиография (ЭКГ)","kz":"Электрокардиография (ЭКГ)","en":"Electrocardiography (ECG)", + "category":"Диагностика","code":"A05.10.006","unit":"1 иссл.","price":(2000,5000), + "syn":[("ЭКГ","ru"),("ЭКГ с расшифровкой","ru"),("Электрокардиография","ru"),("ECG","en"),("ЭКГ + описание","ru")]}, + {"id":"glucose","ru":"Глюкоза крови","kz":"Қандағы глюкоза","en":"Blood glucose", + "category":"Лаборатория","code":"B03.016.006","unit":"1 иссл.","price":(800,2200), + "syn":[("Глюкоза","ru"),("Сахар крови","ru"),("Қандағы глюкоза","kz"),("Glucose","en")]}, + {"id":"cardiologist","ru":"Консультация кардиолога","kz":"Кардиолог кеңесі","en":"Cardiologist consultation", + "category":"Приём специалистов","code":"B01.015.001","unit":"1 приём","price":(5000,12000), + "syn":[("Прием кардиолога","ru"),("Кардиолог консультация первичная","ru"),("Кардиолог қабылдауы","kz")]}, + {"id":"mri_brain","ru":"МРТ головного мозга","kz":"Бас миының МРТ","en":"Brain MRI", + "category":"Диагностика/МРТ","code":"A05.23.009","unit":"1 иссл.","price":(18000,35000), + "syn":[("МРТ г/м","ru"),("Магнитно-резонансная томография головного мозга","ru"),("Бас миы МРТ","kz"),("Brain MRI","en")]}, + {"id":"biochem","ru":"Биохимический анализ крови","kz":"Қанның биохимиялық анализі","en":"Blood chemistry panel", + "category":"Лаборатория","code":"B03.016.004","unit":"1 иссл.","price":(3500,9000), + "syn":[("Биохимия крови","ru"),("Б/х крови","ru"),("Биохимический анализ крови (базовый)","ru"),("Қан биохимиясы","kz")]}, + {"id":"tooth_ext","ru":"Удаление зуба","kz":"Тісті жұлу","en":"Tooth extraction", + "category":"Стоматология","code":"A16.07.001","unit":"1 зуб","price":(6000,15000), + "syn":[("Удаление зуба простое","ru"),("Экстракция зуба","ru"),("Тіс жұлу","kz"),("Tooth removal","en")]}, + {"id":"xray_chest","ru":"Рентген грудной клетки","kz":"Кеуде клеткасының рентгені","en":"Chest X-ray", + "category":"Диагностика","code":"A06.09.007","unit":"1 иссл.","price":(2500,6000), + "syn":[("Рентгенография ОГК","ru"),("Флюорография","ru"),("Кеуде рентгені","kz"),("Chest X-ray","en")]}, + {"id":"tsh","ru":"Тиреотропный гормон (ТТГ)","kz":"Тиреотропты гормон (ТТГ)","en":"Thyroid-stimulating hormone (TSH)", + "category":"Лаборатория","code":"B03.016.021","unit":"1 иссл.","price":(1500,4000), + "syn":[("ТТГ","ru"),("Тиреотропный гормон","ru"),("TSH","en"),("ТТГ гормоны","kz")]}, + {"id":"urinalysis","ru":"Общий анализ мочи","kz":"Зәрдің жалпы анализі","en":"Urinalysis", + "category":"Лаборатория","code":"B03.016.010","unit":"1 иссл.","price":(900,2500), + "syn":[("ОАМ","ru"),("Общий анализ мочи","ru"),("Зәр анализі","kz"),("Urine test","en")]}, + {"id":"gyn","ru":"Приём гинеколога","kz":"Гинеколог қабылдауы","en":"Gynecologist consultation", + "category":"Приём специалистов","code":"B01.001.001","unit":"1 приём","price":(5000,11000), + "syn":[("Прием гинеколога","ru"),("Консультация гинеколога первичная","ru"),("Гинеколог кеңесі","kz")]}, + {"id":"covid_pcr","ru":"ПЦР на COVID-19","kz":"COVID-19 ПТР","en":"COVID-19 PCR test", + "category":"Лаборатория","code":"A26.08.027","unit":"1 иссл.","price":(7000,16000), + "syn":[("ПЦР COVID-19","ru"),("ПЦР-тест на коронавирус","ru"),("COVID PCR","en"),("COVID ПТР","kz")]}, + {"id":"vit_d","ru":"Витамин D (25-OH)","kz":"D дәрумені (25-OH)","en":"Vitamin D (25-OH)", + "category":"Лаборатория","code":"B03.016.115","unit":"1 иссл.","price":(6000,12000), + "syn":[("25-OH витамин D","ru"),("Витамин Д","ru"),("Vitamin D","en"),("D витамині","kz")]}, ] +CLINICS = ["Клиника «Аль-Шифа»","Медцентр «Сункар»","Diagnostic Lab Astana","Клиника «Береке»","Медцентр «Тенгри»"] -CLINICS = ["Клиника «Аль-Шифа»", "Медцентр «Сункар»", "Diagnostic Lab Astana", "Клиника «Береке»", "Медцентр «Тенгри»"] +def price(c): return random.randint(c["price"][0]//100, c["price"][1]//100) * 100 +def variant(c): return random.choice([(c["ru"],"ru")] + c["syn"]) -def price(lo, hi): - return round(random.randint(lo, hi), -2) # круглые сотни - -def variants_for(clinic_idx, n): - """Выбрать n услуг с разными вариантами написания и ценами.""" +GOLDEN = [] +def build(file_name, clinic, n): rows = [] - chosen = random.sample(CATALOG, k=min(n, len(CATALOG))) - for ru, kz, names, lo, hi, unit in chosen: - name = random.choice(names) - rows.append((name, price(lo, hi), unit)) + for c in random.sample(CANONS, k=min(n, len(CANONS))): + raw_name, lang = variant(c); p = price(c) + rows.append({"raw_name":raw_name,"price":p,"unit":c["unit"]}) + GOLDEN.append({"source_file":file_name,"clinic":clinic,"raw_name":raw_name,"lang":lang, + "canon_id":c["id"],"canonical_ru":c["ru"],"canonical_kz":c["kz"],"canonical_en":c["en"], + "category":c["category"],"code":c["code"],"price":p,"currency":"KZT","unit":c["unit"]}) return rows -ensure("openpyxl") -from openpyxl import Workbook +ensure("openpyxl"); from openpyxl import Workbook -# 1) Чистый Excel -wb = Workbook(); ws = wb.active; ws.title = "Прайс" -ws.append(["Наименование услуги", "Цена, тг"]) -for name, p, _ in variants_for(0, 9): - ws.append([name, p]) -wb.save(f"{OUT}/clinic_alpha_clean.xlsx") +# 1) чистый Excel +rows = build("clinic_alpha_clean.xlsx", CLINICS[0], 9) +wb=Workbook(); ws=wb.active; ws.title="Прайс"; ws.append(["Наименование услуги","Цена, тг"]) +for r in rows: ws.append([r["raw_name"], r["price"]]) +wb.save(f"{RAW}/clinic_alpha_clean.xlsx") -# 2) Грязный Excel: секции-заголовки, "от", разные колонки, KZ -wb = Workbook(); ws = wb.active; ws.title = "PRICE 2026" -ws.append(["Прайс-лист медцентра «Сункар» (с 01.06.2026)"]) -ws.append([]) -ws.append(["№", "Услуга / Қызмет", "Стоимость (тенге)", "Ед."]) -section = ["— Лаборатория / Зертхана —", "— Диагностика —", "— Приём специалистов —"] -i = 1 -for s in section: - ws.append([s, None, None, None]) - for name, p, unit in variants_for(1, 3): - val = f"от {p}" if random.random() < 0.3 else p - ws.append([i, name, val, unit]); i += 1 -wb.save(f"{OUT}/clinic_beta_dirty.xlsx") +# 2) грязный Excel +rows = build("clinic_beta_dirty.xlsx", CLINICS[1], 9) +wb=Workbook(); ws=wb.active; ws.title="PRICE 2026" +ws.append(["Прайс-лист медцентра «Сункар» (с 01.06.2026)"]); ws.append([]) +ws.append(["№","Услуга / Қызмет","Стоимость (тенге)","Ед."]) +secs=["— Лаборатория / Зертхана —","— Диагностика —","— Приём специалистов —"]; i=1 +for k,r in enumerate(rows): + if k%3==0 and secs: ws.append([secs.pop(0),None,None,None]) + val=f"от {r['price']}" if random.random()<0.3 else r["price"] + ws.append([i, r["raw_name"], val, r["unit"]]); i+=1 +wb.save(f"{RAW}/clinic_beta_dirty.xlsx") -# 3) CSV с разделителем ; и смешанными языками -with open(f"{OUT}/clinic_gamma.csv", "w", encoding="utf-8", newline="") as f: - w = csv.writer(f, delimiter=";") - w.writerow(["service", "price_kzt", "unit"]) - for name, p, unit in variants_for(2, 8): - w.writerow([name, p, unit]) +# 3) CSV ; +rows = build("clinic_gamma.csv", CLINICS[2], 8) +with open(f"{RAW}/clinic_gamma.csv","w",encoding="utf-8",newline="") as f: + w=csv.writer(f,delimiter=";"); w.writerow(["service","price_kzt","unit"]) + for r in rows: w.writerow([r["raw_name"], r["price"], r["unit"]]) -# 4) Грязный текст (прайс абзацем) -with open(f"{OUT}/clinic_delta_messy.txt", "w", encoding="utf-8") as f: +# 4) грязный текст +rows = build("clinic_delta_messy.txt", CLINICS[4], 7) +with open(f"{RAW}/clinic_delta_messy.txt","w",encoding="utf-8") as f: f.write("МЕДЦЕНТР «ТЕНГРИ» — действующие цены\n\n") - for name, p, unit in variants_for(3, 7): - sep = random.choice([" — ", " ... ", ": ", " "]) - cur = random.choice(["тг", "тенге", "₸", "KZT"]) - f.write(f"{name}{sep}{p} {cur} ({unit})\n") + for r in rows: + sep=random.choice([" — "," ... ",": "," "]); cur=random.choice(["тг","тенге","₸","KZT"]) + f.write(f"{r['raw_name']}{sep}{r['price']} {cur} ({r['unit']})\n") f.write("\n* Цены справочные, уточняйте в регистратуре. Бағалар анықтамалық.\n") -# 5) Картинка-«скан» прайса (имитация фото) -ensure("Pillow", "PIL") -from PIL import Image, ImageDraw, ImageFont -img = Image.new("RGB", (900, 700), (250, 248, 244)) -d = ImageDraw.Draw(img) +# 5) PNG-скан +rows = build("clinic_epsilon_scan.png", CLINICS[3], 9) +ensure("Pillow","PIL"); from PIL import Image, ImageDraw, ImageFont def font(sz): - for p in ["/System/Library/Fonts/Supplemental/Arial.ttf", "/Library/Fonts/Arial.ttf", - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"]: - if os.path.exists(p): - return ImageFont.truetype(p, sz) + for p in ["/System/Library/Fonts/Supplemental/Arial.ttf","/Library/Fonts/Arial.ttf","/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"]: + if os.path.exists(p): return ImageFont.truetype(p,sz) return ImageFont.load_default() -d.text((40, 30), "Клиника «Береке» — прайс-лист", fill=(20, 20, 20), font=font(34)) -d.line((40, 85, 860, 85), fill=(180, 180, 180), width=2) -y = 110 -for name, p, unit in variants_for(4, 9): - d.text((50, y), name, fill=(30, 30, 30), font=font(24)) - d.text((720, y), f"{p} тг", fill=(30, 30, 30), font=font(24)) - y += 58 -img = img.rotate(-1.4, expand=False, fillcolor=(250, 248, 244)) # лёгкий наклон как у фото -img.save(f"{OUT}/clinic_epsilon_scan.png") +img=Image.new("RGB",(900,720),(250,248,244)); d=ImageDraw.Draw(img) +d.text((40,30),"Клиника «Береке» — прайс-лист",fill=(20,20,20),font=font(34)); d.line((40,85,860,85),fill=(180,180,180),width=2) +y=110 +for r in rows: + d.text((50,y),r["raw_name"][:42],fill=(30,30,30),font=font(23)); d.text((720,y),f"{r['price']} тг",fill=(30,30,30),font=font(23)); y+=62 +img=img.rotate(-1.4,expand=False,fillcolor=(250,248,244)); img.save(f"{RAW}/clinic_epsilon_scan.png") -# 6) PDF (если есть reportlab) +# 6) PDF +rows = build("clinic_zeta_prices.pdf", CLINICS[2], 10) +pdf="skip" try: ensure("reportlab") - from reportlab.lib.pagesizes import A4 - from reportlab.pdfgen import canvas - from reportlab.pdfbase import pdfmetrics - from reportlab.pdfbase.ttfonts import TTFont - fp = next((p for p in ["/Library/Fonts/Arial.ttf", "/System/Library/Fonts/Supplemental/Arial.ttf", - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"] if os.path.exists(p)), None) + from reportlab.lib.pagesizes import A4; from reportlab.pdfgen import canvas + from reportlab.pdfbase import pdfmetrics; from reportlab.pdfbase.ttfonts import TTFont + fp=next((p for p in ["/Library/Fonts/Arial.ttf","/System/Library/Fonts/Supplemental/Arial.ttf","/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"] if os.path.exists(p)),None) if fp: - pdfmetrics.registerFont(TTFont("U", fp)) - c = canvas.Canvas(f"{OUT}/clinic_zeta_prices.pdf", pagesize=A4) - c.setFont("U", 18); c.drawString(60, 790, "Diagnostic Lab Astana — Price list / Прайс") - c.setFont("U", 12); y = 750 - for name, p, unit in variants_for(2, 10): - c.drawString(60, y, name[:55]); c.drawRightString(540, y, f"{p} KZT"); y -= 28 - c.save() - pdf = "ok" - else: - pdf = "no font" -except Exception as e: - pdf = f"skip ({e})" + pdfmetrics.registerFont(TTFont("U",fp)); c=canvas.Canvas(f"{RAW}/clinic_zeta_prices.pdf",pagesize=A4) + c.setFont("U",18); c.drawString(60,790,"Diagnostic Lab Astana — Price list / Прайс"); c.setFont("U",12); y=750 + for r in rows: c.drawString(60,y,r["raw_name"][:55]); c.drawRightString(540,y,f"{r['price']} KZT"); y-=28 + c.save(); pdf="ok" +except Exception as e: pdf=f"skip ({e})" + +# эталон нормализации + справочник +with open(f"{NORM}/golden.json","w",encoding="utf-8") as f: + json.dump(GOLDEN, f, ensure_ascii=False, indent=2) +canon_out=[{"canon_id":c["id"],"canonical_ru":c["ru"],"canonical_kz":c["kz"],"canonical_en":c["en"], + "category":c["category"],"code":c["code"],"unit":c["unit"], + "synonyms":[{"text":t,"lang":l} for t,l in c["syn"]]} for c in CANONS] +with open(f"{CONTRACTS}/service_canon.json","w",encoding="utf-8") as f: + json.dump(canon_out, f, ensure_ascii=False, indent=2) print("PDF:", pdf) -print("Files:", sorted(os.listdir(OUT))) +print("raw:", sorted(os.listdir(RAW))) +print("golden rows:", len(GOLDEN), "| canons:", len(CANONS))