Stomatology Booking Bot
Production-ready Telegram-бот записи в стоматологию. Config-driven шаблон, готовый к развёртыванию под нового клиента за 1-2 часа.
Зачем это нужно
Малые и средние стоматологии в РФ теряют 20-30% входящих заявок: запись только по телефону, после 19:00 никто не отвечает, неявки на приёмы 15-22% от загрузки. Найм администратора закрывает часть проблемы за 50-80 тыс. ₽/мес. Этот бот закрывает её за 9 900 ₽/мес и работает 24/7.
Целевая аудитория: 1-3 врача, 30-100 записей в неделю, бюджет на digital до 200 тыс. ₽/год.
Что бот умеет
Для пациентов
- 📋 156-ФЗ согласие отдельным документом (ФЗ №156 от 24.06.2025) — двухкнопочный flow с PDF + audit log с SHA-256 хэшем документа
- 📅 Запись на приём: услуга → врач → дата → время → подтверждение
- 🔒 Защита от двойной записи через
pg_advisory_xact_lock+SELECT FOR UPDATE— 100 одновременных кликов на один слот = ровно одна успешная запись - ⏰ Напоминания за 24 часа и за 1 час до приёма (Telegram, опционально SMTP)
- ↩️ Отмена и перенос записи без звонка администратору
- 🤖 AI-роутер (опционально): GigaChat классифицирует намерение пациента (
book/cancel/reschedule/services/doctors/faq/smalltalk/junk) и направляет в нужный handler - ❓ AI-FAQ (опционально): GigaChat отвечает на вопросы о клинике с многослойными guardrails (см. Безопасность AI)
Для администратора (отдельный admin-bot)
- 📊 Просмотр записей за сегодня / завтра / неделю
- ✏️ Управление услугами, врачами, расписанием
- 🚫 Отмена / перенос от лица клиники
- 📈 Отчёты по периодам
- 🔐 Whitelist по
ADMIN_TELEGRAM_IDS
Технологический стек
| Слой | Технология | Версия |
|---|---|---|
| Runtime | Python | 3.13 |
| Bot framework | aiogram | 3.27 |
| Web framework | FastAPI | 0.136 |
| ORM | SQLAlchemy 2.0 (async) | 2.0.49 |
| Migrations | Alembic | 1.18 |
| DB | PostgreSQL | 17 |
| FSM / Cache | Redis | 7.4 |
| AI (опц.) | GigaChat 2 Lite/Pro | SDK 0.2 |
| Logging | structlog | 25.5 |
| Testing | pytest + testcontainers | — |
| Linting | ruff + mypy | — |
| Container | Docker Compose | — |
Архитектура
┌─────────────────┐
│ Pacient(s) │
│ in Telegram │
└────────┬────────┘
│
┌──────────────────────────┴──────────────────────────┐
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ patient-bot │ │ admin-bot │
│ (aiogram) │ │ (aiogram) │
└────────┬────────┘ └────────┬────────┘
│ │
│ consent flow → booking → reminders │ CRUD operations
│ + AI router + AI FAQ │ reports + manage
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────────────┐
│ Domain Layer (booking_bot.domain) │
│ booking | cancel | reschedule | consent | slot_generator │
│ ai_router | ai_faq | ai_guardrails | ai_circuit_breaker │
│ ai_client | rate_limiter | prompt_loader │
└────────────────────────┬─────────────────────────────────────────────┘
│
┌──────────┼──────────┬──────────────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌─────────┐ ┌────────┐ ┌──────────────┐
│PostgreSQL│ │ Redis │ │GigaChat│ │ FastAPI app │
│ (17) │ │ (7.4) │ │ API │ │ /healthz │
│ │ │ + FSM │ │ │ │ /readyz │
│ 10 tables│ │ + RL │ │ │ │ │
└──────────┘ └─────────┘ └────────┘ └──────────────┘
▲
│ poll every 60s
│ FOR UPDATE SKIP LOCKED
│
┌──────┴───────────┐
│ reminder worker │
│ (separate proc) │
└──────────────────┘
Подробное описание компонентов и потоков данных — в ARCHITECTURE.md.
Quick Start (5 минут)
git clone https://github.com/kiskameow/dental-clinic-telegram-bot.git
cd dental-clinic-telegram-bot
cp .env.example .env
# Отредактируй .env: PATIENT_BOT_TOKEN, ADMIN_BOT_TOKEN, ADMIN_TELEGRAM_IDS
# Получи токены у @BotFather
docker compose up -d # postgres, redis, api, patient-bot, admin-bot, worker
docker compose run --rm api alembic upgrade head # применить миграции
docker compose run --rm api python -m scripts.seed_demo # заполнить demo-данными
Бот готов. Откройте Telegram, найдите своего бота, отправьте /start.
Для production-разворачивания (Caddy + TLS + Sentry + бэкапы + healthchecks) — см. DEPLOY.md.
Структура проекта
stomatology-booking-bot/
├── src/booking_bot/
│ ├── api/ # FastAPI: /healthz, /readyz, future webhooks
│ ├── bots/
│ │ ├── patient/ # patient bot entry-point
│ │ ├── admin/ # admin bot entry-point + handlers
│ │ ├── handlers/ # patient handlers: booking, consent, ai, cancel, reschedule
│ │ ├── middlewares/ # admin_auth, admin_session, consent, rate_limit
│ │ └── factory.py # bot dispatcher factory
│ ├── domain/ # бизнес-логика (без I/O, без БД)
│ │ ├── booking.py # booking + race condition pattern
│ │ ├── cancel.py
│ │ ├── reschedule.py
│ │ ├── consent.py # 156-ФЗ flow
│ │ ├── slot_generator.py
│ │ ├── ai_router.py # intent classification
│ │ ├── ai_faq.py # FAQ orchestration с pipeline
│ │ ├── ai_guardrails.py # 476 строк защиты (input + output)
│ │ ├── ai_circuit_breaker.py
│ │ ├── ai_client.py # GigaChat wrapper
│ │ ├── ai_errors.py # AIInputRejectedError, AIOutputRejectedError, etc.
│ │ ├── rate_limiter.py
│ │ ├── prompt_loader.py
│ │ ├── config_schemas.py # Pydantic strict validation для clinic.yaml
│ │ └── enums.py
│ ├── db/
│ │ ├── models.py # 10 SQLAlchemy 2.0 моделей
│ │ ├── engine.py
│ │ └── repositories/ # 9 repository-модулей (по одному на агрегат)
│ ├── notifications/ # telegram + email_smtp + base
│ ├── workers/
│ │ └── reminder.py # SELECT FOR UPDATE SKIP LOCKED loop
│ ├── app_context.py # composition root
│ └── config.py # env settings
├── config/
│ ├── clinic.example.yaml # шаблон для нового клиента
│ └── ulybka-kazan.yaml # demo: «Стоматология Улыбка Казань»
├── prompts/
│ ├── intent_router.txt # AI-роутер system prompt
│ └── clinic_faq.txt # FAQ system prompt с hard-rules + repeat anti-injection
├── legal/
│ ├── consent_pd_v1.html # 156-ФЗ согласие, source
│ └── consent_pd_v1.pdf # rendered, hashed at consent:agree
├── scripts/
│ ├── generate_consent_pdf.py # WeasyPrint render
│ └── seed_demo.py # demo-данные
├── tests/
│ ├── unit/ # 43 теста
│ └── integration/ # 17 тестов (Postgres + Redis через testcontainers)
├── alembic/versions/ # миграции
├── .github/workflows/ # CI: pytest + ruff + mypy
├── docker-compose.yml # 5 services
├── Dockerfile
├── pyproject.toml
├── ARCHITECTURE.md # подробная архитектура
├── DEPLOY.md # production-deploy
└── ADD_CLINIC.md # rebrand под нового клиента
Production-grade паттерны
Это template, который применяет осознанно отобранные production-практики, а не «работает на моей машине».
Race condition protection
Двойная запись на один слот — самый частый production-баг booking-ботов. Решено через pg_advisory_xact_lock(doctor_id, date_int) + SELECT ... FOR UPDATE в одной транзакции:
# domain/booking.py — упрощённая версия
async with session.begin():
await session.execute(
text("SELECT pg_advisory_xact_lock(:doctor_id, :date_int)"),
{"doctor_id": doctor_id, "date_int": int(date.strftime("%Y%m%d"))},
)
slot = await session.scalar(select(Slot).where(...).with_for_update())
if slot.taken_by_appointment_id:
raise SlotAlreadyTakenError
# commit on async with exit
100 параллельных booking-запросов на один слот — гарантированно одна успешная запись, остальные получают SlotAlreadyTakenError. Покрыто tests/integration/test_concurrent_booking.py.
Reminder worker — durable очередь
APScheduler молча теряет jobs при рестарте. Вместо него:
- Каждое booking создаёт строки в
reminder_queue(reminder_at,channel,appointment_id) - Worker опрашивает таблицу каждые 60 секунд через
SELECT ... FOR UPDATE SKIP LOCKED LIMIT 100 - Несколько worker-инстансов могут работать параллельно —
SKIP LOCKEDгарантирует уникальную выдачу - Retry с подсчётом attempts, max 5, last_error в БД для дебага
Pattern переживает рестарты, scaling, долгие GIL-локи, всё.
156-ФЗ согласие отдельным документом
С 1 сентября 2025 (ФЗ №156-ФЗ от 24.06.2025) согласие на обработку ПД обязано быть отдельным документом. Штраф до 700 000 ₽.
Реализация:
- При первом взаимодействии бот шлёт PDF-согласие отдельным сообщением
- Кнопка «Я согласен» — отдельный callback (не объединена с другими подтверждениями)
- При клике пишется строка в
consent_log:telegram_id+consent_version+ SHA-256 хэш PDF +consent_type+created_at - Документ хэшируется при каждой даче согласия, не на старте — это гарантирует, что подмена PDF на хосте сразу видна в audit log
- При смене версии PDF — пересогласование у всех пользователей
Безопасность AI
domain/ai_guardrails.py (476 строк) — defense-in-depth для AI-FAQ:
Pre-LLM (input):
- Структурные: control chars, zero-width chars (token smuggling), prompt-tag injection (
<|im_start|>, etc.), HTML/XML tags, markdown system-prompt injection (### System) - Эвристики: base64-blob detection (40+ chars), URL detection (любой URL = подозрительно), excessive newlines, char floods (30+ same char)
- Паттерны: 5 семейств jailbreak (EN + RU), code injection, refusal bypass (
в виде исключения,гипотетически), prompt extraction - Operator-supplied custom patterns с fail-soft compile (broken pattern → warning, не crash)
Post-LLM (output):
instruction_bypassmarkers первыми — фингерпринт компромисса, чтобы не маскировался другими guard’ами- Forbidden patterns: «гарантирую», «100%», «противопоказаний нет» (RU consumer-protection law)
- Medical advice: диагнозы (
у вас кариес), назначения препаратов - Price authorization: response может содержать только цены из
allowed_prices(загружаются из конфига) - Phone authorization: только клиники, не любые
- URL в response, code blocks
- System prompt echo (verbatim) + 60-char sliding-window overlap (paraphrase attacks)
- Cyrillic ratio ≥ 0.7 для
enforce_russian: true
Defense layers:
- Circuit breaker (5 consecutive failures → open 5 min) — не сжигаем токены и не лагуем UX при backend-проблемах
- Per-clinic hourly rate limit — guard от runaway cost
- Audit log для каждого AI-call (input, output, is_safe, rejection_reason) — всегда пишется, fail-safe (если БД недоступна — log и не падаем)
Все защиты протестированы в tests/integration/test_ai_adversarial.py (30 KB end-to-end тестов с реальными jailbreak-промптами).
Прочее
- Soft delete для пациентов (
deleted_at) — мягкое удаление + право на забвение по 152-ФЗ - Naming convention для Alembic constraints — установлен с первой миграции, чтобы автогенерация имён была детерминированной
- N+1 prevention через
selectinloading в моделях appointments - Idempotent seed-script — можно запускать повторно без падений
- structlog с JSON-output — structured logging готов под Loki/Grafana
Testing
docker compose run --rm api pytest # все 60 тестов
docker compose run --rm api pytest -m "not integration" # только unit
docker compose run --rm api pytest --cov # coverage
| Тип | Кол-во | Что проверяют |
|---|---|---|
| Unit | 43 | Чистые функции, валидация, FSM-states, handlers с моками |
| Integration | 17 | Real Postgres + Redis через testcontainers |
| Adversarial (subset of integration) | 1 (но 30 KB) | End-to-end атаки на AI: jailbreak, prompt-injection, price-spoofing |
Ключевые сценарии:
test_concurrent_booking.py— 100 одновременных booking на один слотtest_concurrent_cancel.py— race на отменеtest_consent_flow_e2e.py— полный 156-ФЗ flow с audit logtest_migration_roundtrip.py— каждая миграция up + downtest_pdf_render.py— PDF-генерацияtest_reminder_worker.py—SKIP LOCKEDповедениеtest_ai_adversarial.py— все слои AI safety
Configuration
Бот config-driven. Один YAML-файл (config/<clinic-code>.yaml) описывает клинику целиком: услуги, врачей, расписание, FAQ, согласия. Pydantic-схема (domain/config_schemas.py) валидирует:
- Cross-references: каждый
doctor.servicesсуществует вservices - Уникальность кодов услуг и врачей
work_start < work_end- Хорошо-сформированные коды (regex
^[a-z][a-z0-9-]*$) - Лимиты длины и значений
Развёртывание под нового клиента — см. ADD_CLINIC.md. Время — 1-2 часа на rebrand.
Deployment
Production-deploy с Caddy + Let’s Encrypt + Sentry + бэкапы — см. DEPLOY.md.
Development
# Install (Python 3.13 required)
pip install -e ".[dev]"
# Lint + type check
ruff check src/ tests/
mypy src/booking_bot/
# Run locally (без Docker)
DATABASE_URL=postgresql+asyncpg://booking:booking@localhost:5432/booking \
REDIS_URL=redis://localhost:6379/0 \
python -m booking_bot.bots.patient
CI (.github/workflows/) запускает pytest, ruff, mypy на каждый push.
Roadmap
- Day 1-2: Foundation, схема БД, 156-ФЗ consent flow
- Day 3: Booking flow + race condition protection
- Day 4: Reminder worker + email backend
- Day 5: Admin bot + management/reports
- Day 6: Cancel/reschedule + middlewares
- Day 7: AI router + AI FAQ + guardrails + circuit breaker + rate limit + audit log
- Day 8: Production deploy (Caddy, TLS, Sentry, backups), email reminder enable
- Day 9: Loom walkthrough video, public live demo
- Day 10: Case study / VC.ru article
License
MIT — см. LICENSE.
Demo
🌐 Live demo URL: coming after Day 8 deploy 🎬 7-minute video walkthrough: in production this week 🤖 Try the patient bot: link added after deploy
Until the live demo is up, docker compose up -d gives you a fully working patient + admin bot in under 5 minutes. Use the demo config config/ulybka-kazan.yaml — three services, two doctors, 30 days of generated slots.
Need this for your clinic?
Three paths forward — pick based on your tech comfort and timeline.
1. Fork & deploy yourself
Code is MIT-licensed. Fork the repo, edit config/<your-clinic>.yaml, follow ADD_CLINIC.md. Full production setup in DEPLOY.md. Best fit if you have an in-house developer or sysadmin who can read the docs and own deployment.
2. I deploy under your clinic
What’s included:
- Full deployment on your VPS (or my managed infrastructure)
- Custom 156-ФЗ consent PDF generated with your legal entity’s data
- Configured both bots, reverse proxy with TLS, daily backups, monitoring + Telegram alerts
- Production smoke-test (end-to-end booking + reminder + cancel + admin operations)
- Recorded walkthrough specific to your configuration
- Handover documentation so any developer can continue maintenance
Indicative delivery: 1-3 days after access. Final price depends on scope (single location vs multi-location, custom integrations).
3. Monthly retainer (after launch)
Ongoing support:
- 4-hour response time (business days), 12-hour off-hours
- Library security updates, key rotation, dependabot PRs reviewed
- Reasonable config changes (new doctors, services, schedule updates)
- Quarterly health-check + AI audit log review
Pricing by tier — contact for current rates.
Contact
- 💬 Telegram (preferred for business): @wskaleurs
- 📧 Email: wsskueal@gmail.com
- 🐛 Bugs / feature requests: GitHub Issues
- 🔒 Security disclosure: see SECURITY.md
Hire inquiries and technical questions both welcome.
Production-grade Telegram bot template for Russian SMB. Companion templates (B2B portal for dental labs, secure corporate channel setup) in development.