Операционная модель конструктора туров
Версия: 1.0 Дата: 26.04.2026 Статус: Готов к обсуждению
Назначение документа
Документ определяет операционную модель конструктора туров (Tour Builder operational model) Vitiana — как именно пользователь (агент агентства, партнёр через программный интерфейс, конечный клиент через потребительскую витрину) собирает многокомпонентный тур из черновика до подтверждённого пакета. Включает:
- Каноничные правила композиции (composition rules) — что и с чем совместимо, что обязательно, что опционально.
- Поток состояний черновика тура (
TourDraft) от создания до публикации. - Обработку расхождения цен и доступности (drift handling) между моментом добавления компонента в черновик и моментом публикации.
- Разрешение конфликтов вариантов (variant conflicts).
- Версионирование и журнал изменений черновика.
- Атомарность бронирования пакетного тура — как обеспечивается «всё или ничего» при подтверждении тура из нескольких компонентов.
- Тип-1 / тип-2 / тип-3 связки сегментов — каноничная классификация типов тура.
- Партнёрскую модель интеграции — как партнёр строит тур через программный интерфейс.
- Интеграцию с другими доменами (поиск, платежи, пост-бронирование, медиа).
- Метрики операционной модели и наблюдаемость.
- Регуляторные следствия (Директива о пакетных турах — Package Travel Directive 2015/2302).
- Фазы развёртывания.
Документ читается после:
- Tour Builder Domain — каноничные сущности (
TourDraft,TourDraftItem,TourVariant,TourProposal,TourVersion,TourArtifact). Этот документ закрывает Дыру 3 из манифеста переосмысления. - Манифест переосмысления § 1.5 — Tour Builder как ядро платформы.
- Поверхности взаимодействия → Tour Builder Closed Surface (поверхность 6) + контур композиции тура.
- Business Services → 6. Tour Builder Service.
- Семантика предложений, цены, бронирования — для понимания связи коммерческой фиксации с компонентами.
- Поиск и обнаружение — поиск кандидатов компонентов.
- Платёжный домен — атомарность платежа за пакетный тур.
- Жизненный цикл пост-бронирования — обработка отмен и изменений компонентов тура.
- Соответствие требованиям регуляторов — Директива о пакетных турах.
В корневых документах зафиксировано что Tour Builder делает и где живёт. Этот документ описывает как — операционные правила, поток состояний, drift handling, атомарность.
Tour Builder — главная архитектурная новизна Vitiana. Большинство платформ — каталоги отелей с возможностью бронирования (один отель за раз). Tour Builder позволяет в одной операции собрать сложный тур из нескольких сегментов — проживание в нескольких городах, переезды между ними, экскурсии, страховку. Это основа дифференциации платформы.
Реальное состояние реализации (home-to-go-api): пока работает поиск и бронирование одного отеля. Tour Builder как операционная модель — новая зона, требующая собственной реализации.
Правило 00000 (платформа главенствует над поставщиками) применяется здесь напрямую: правила композиции тура — каноничные правила платформы, не наследие от поставщиков. Поставщик отеля не диктует, какие переезды совместимы с проживанием в его отеле; платформа сама задаёт правила совместимости компонентов разных типов от разных поставщиков.
Главное решение
Tour Builder — closed-system raw API constructor по модулям с явными состояниями черновика, явными правилами композиции и атомарным подтверждением пакетного тура.
Это означает:
- Закрытая система — программный интерфейс Tour Builder не открыт всем партнёрам; доступен только тарифам с явной поддержкой (Professional с paid Tour Builder access, Enterprise).
- Raw API constructor по модулям — партнёр оперирует примитивами композиции (добавить сегмент, заменить вариант, проверить совместимость), а не «магической функцией построения тура». Это даёт максимальную гибкость и контроль.
- Явные состояния черновика —
TourDraftимеет каноничную статусную машину; никаких «случайных» состояний. - Правила композиции — каноничные сущности — не зашиты в код, а конфигурируются через
CompositionRule. - Drift handling — first-class — каждое изменение цены или доступности компонента после добавления в черновик отслеживается и обрабатывается явно.
- Атомарность пакетного тура — подтверждение тура из нескольких компонентов либо проходит полностью, либо не проходит вообще. Промежуточные состояния (часть забронирована, часть нет) недопустимы.
- Партнёрский интерфейс — стабильный контракт — изменение правил композиции не ломает существующих партнёров.
Каноничные сущности (углубление существующих)
Уже зафиксированные сущности
В Tour Builder Domain уже зафиксированы:
TourDraft— черновик тура.TourDraftItem— элемент черновика (один сегмент).TourVariant— вариант компонента в сегменте (несколько вариантов отеля для одной даты).TourProposal— оформленное предложение тура клиенту.TourVersion— версия черновика (с историей изменений).TourArtifact— артефакт публикации (сгенерированный документ, презентация).
Этот документ не дублирует эти определения, а расширяет операционными сущностями.
Новые операционные сущности
CompositionRule — правило композиции
Сущность, представляющая именованное правило совместимости и композиции компонентов тура.
{
rule_id: UUID,
rule_key: string,
rule_class: enum,
applicable_segment_types: array,
applicable_segment_pairs: array,
rule_logic: object,
severity: enum,
applicable_tenants: array,
applicable_phases: array,
is_active: boolean,
created_at: timestamp
}
Поля:
rule_class— класс правила:temporal_consistency— временная согласованность (даты сегментов не пересекаются, нет разрывов больше N часов между сегментами без явного намерения).geographic_consistency— географическая согласованность (между сегментами в разных городах должен быть переезд; обратное неверно).mandatory_pairing— обязательное сочетание (если есть международный сегмент, обязательна страховка).mutual_exclusion— взаимное исключение (несовместимые компоненты).capacity_consistency— согласованность по числу путешественников (все сегменты должны быть на одинаковое число гостей).legal_consistency— юридические правила (например, по Директиве о пакетных турах — пакет должен иметь >= 2 разных компонента).quality_threshold— пороги качества (например, для премиум-тура — все компоненты с минимальнымquality_score).
severity— серьёзность нарушения (error— блокирует публикацию;warning— отображается, но не блокирует;info— для информации).
CompositionViolation — нарушение правила композиции
Сущность, представляющая конкретное нарушение правила в конкретном TourDraft.
{
violation_id: UUID,
draft_id: UUID,
rule_id: UUID,
triggered_by_item_ids: array,
detected_at: timestamp,
resolved_at: timestamp,
resolution: enum,
display_message_payloads: object,
severity: enum
}
Поля:
triggered_by_item_ids— какие элементы черновика вызвали нарушение.resolution— как разрешено (item_replaced/item_removed/dates_adjusted/tour_abandoned/manual_overrideдля случаев corporate Enterprise).display_message_payloads— многоязычные сообщения об ошибке для пользователя.
DriftEvent — событие расхождения
Сущность, представляющая факт изменения цены или доступности компонента после добавления в черновик.
{
drift_id: UUID,
draft_id: UUID,
item_id: UUID,
drift_class: enum,
detected_at: timestamp,
previous_value: object,
current_value: object,
significance: enum,
resolution: enum,
user_notified_at: timestamp,
user_decision_at: timestamp,
user_decision: enum
}
Поля:
drift_class— класс расхождения:price_increased— цена выросла.price_decreased— цена упала (обычно благоприятное, но требует пересчёта).availability_lost— компонент стал недоступен.availability_partial— частичная доступность (например, не все номера).policy_changed— изменилась политика отмены или другие условия.metadata_changed— изменилось что-то в описании компонента.
significance— значимость (negligible— менее 1% изменения цены;noticeable— 1–5%;material— более 5% или потеря доступности).resolution— как разрешено (auto_accepted— для negligible;user_confirmed— пользователь явно подтвердил;user_rejected— пользователь отказался;auto_replaced— автоматическая замена на эквивалентный вариант для определённых тарифов).
TourBookingTransaction — транзакция бронирования пакета
Сущность, представляющая атомарную транзакцию подтверждения многокомпонентного тура.
{
transaction_id: UUID,
draft_id: UUID,
proposal_id: UUID,
state: enum,
component_bookings: array,
saga_step: enum,
saga_log: array,
payment_intent_id: UUID,
rollback_required: boolean,
rollback_log: array,
started_at: timestamp,
completed_at: timestamp,
failed_at: timestamp,
failure_reason: string
}
Поля:
component_bookings— массив идентификаторов отдельных бронирований по каждому компоненту тура.saga_step— текущий шаг саги (см. секцию атомарности).saga_log— журнал шагов саги для аудита и replay.rollback_required— нужен ли откат при провале одного из компонентов.rollback_log— журнал откатов (какие компоненты успели забронироваться и были отменены).
Связь с другими каноничными сущностями
CompositionRuleссылается наTenantчерезapplicable_tenants— разные тенанты могут иметь разные правила (например, для корпоративного партнёра — особые правила).DriftEvent.item_id— ссылка наTourDraftItemиз Tour Builder Domain.TourBookingTransaction.payment_intent_id— единое платёжное намерение для всего пакета (см. Платёжный домен).TourBookingTransaction.component_bookings— отдельныеBookingсущности для каждого компонента.Quoteфиксируется на уровне всего тура (см. ниже секцию ценообразования пакета), не отдельных компонентов.
Каноничная классификация типов тура
Платформа поддерживает три типа компоновки тура с разной семантикой:
Тип 1. Линейный тур (linear tour)
Несколько сегментов разных типов (проживание + переезд + экскурсия) в одной географической последовательности.
Пример: «3 дня Прага в отеле A + переезд поездом + 4 дня Карловы Вары в отеле B».
Правила:
- Сегменты идут строго по времени без пересечений.
- Между сегментами разных географических точек обязателен переезд.
- Все сегменты на одно и то же число путешественников.
Тип 2. Тематический тур (themed tour)
Тур с центральной темой (свадьба, корпоратив, лечение) и набором согласованных компонентов.
Пример: «свадебный тур — отель с залом + кейтеринг + транспорт для гостей + фотограф».
Правила:
- Центральный компонент (главное событие) обязателен.
- Дополнительные компоненты опциональны.
- Все компоненты привязаны к единой дате центрального события.
Тип 3. Пакетный тур (package tour)
Готовый шаблон тура от платформы или партнёра с фиксированным набором компонентов и параметрами.
Пример: «Пражский weekend-pack: отель 4* + завтраки + обзорная экскурсия + трансфер из аэропорта».
Правила:
- Шаблон фиксирован, но конкретные варианты компонентов выбираются под даты.
- Стоимость рассчитывается как сумма с возможной скидкой от платформы.
- Шаблоны управляются через
TourTemplate(отдельная сущность, в Tour Builder Domain).
Различение для Директивы о пакетных турах (EU 2015/2302)
По Директиве:
- «Package» — комбинация >= 2 разных типов туристических услуг (проживание + переезд, проживание + экскурсия и т.д.) в одной транзакции от одного продавца.
Все три типа платформенного тура с двумя или более компонентами разных типов попадают под Директиву и требуют:
- Преддоговорной информации (pre-contractual information).
- Финансовой гарантии (insolvency protection) платформы или продавца-тенанта.
- Право клиента на возврат при существенных изменениях (см. drift handling material).
- Ответственность продавца (платформа или тенант — зависит от модели отвечающего за платежи продавца, см. Соответствие требованиям регуляторов).
Поток состояний черновика тура
Каноничная статусная машина TourDraft:
created — пользователь начал композицию
↓
in_composition — добавляются и удаляются элементы
↓
(любое изменение → CompositionRule валидация → возможны CompositionViolation)
↓
validated — все правила прошли, готов к фиксации цены
↓
priced — цены каждого компонента зафиксированы (Quote на каждый компонент)
↓
proposed — TourProposal сгенерирован (документ для клиента)
↓
├── client_review — пользователь / клиент рассматривает
│ ↓
│ ├── approved — клиент согласен, переход к подтверждению
│ └── rejected — клиент отказался
│
└── direct_commit — без отдельного клиентского одобрения (для агента, оформляющего сразу)
↓
committing — выполняется TourBookingTransaction
↓
├── confirmed — все компоненты успешно забронированы
├── partial_failure — один компонент провалился, ведётся rollback
│ ↓
│ rolled_back — все ранее успешные компоненты отменены
│
└── permanent_failure — невозможно завершить
Переходы и операции
created → in_composition
Когда: после первого add_item запроса.
in_composition → validated
Когда: пользователь явно вызвал validate_composition или автоматически перед request_pricing.
Действия:
- Применяются все активные
CompositionRule. - Создаются
CompositionViolationдля нарушений. - Если есть нарушения уровня
error— переход вvalidatedблокируется, остаёмся вin_composition. - Только при отсутствии
error-нарушений — переход.
validated → priced
Когда: пользователь вызвал request_pricing.
Действия:
- Для каждого
TourDraftItemсоздаётсяQuote(через стандартный поток, см. Семантика предложений, цены, бронирования). - Quote'ы привязаны к черновику через специальный идентификатор
tour_draft_id. - Сумма цен компонентов плюс маржа платформы (для пакетных туров) даёт общую стоимость тура.
priced → proposed
Когда: пользователь вызвал generate_proposal.
Действия:
- Генерируется
TourProposal— документ для клиента. - Создаётся
TourArtifact(PDF или другая форма) с полным описанием тура. - Документ доставляется клиенту (через Уведомления и коммуникации, типично email).
proposed → client_review или direct_commit
Зависит от поверхности:
- Tour Builder Closed Surface — типично
direct_commit(партнёр знает, что клиент уже согласен). - Agency Working Surface — типично
client_review, агент ждёт подтверждения клиента. - B2C Storefront Surface — типично
client_reviewчерез интерактив на сайте.
committing → confirmed
Атомарное подтверждение всех компонентов через TourBookingTransaction (см. ниже).
Запрет на «зомби»-черновики
Черновики, не доведённые до публикации в течение N дней (типично 7 для конечных клиентов, 30 для агентов), помечаются как expired и архивируются. Это защищает:
- От накопления устаревших данных.
- От использования устаревших цен (Quote истекают раньше).
- От потенциальных регуляторных рисков (необработанные пакеты, формально подпадающие под Директиву).
Drift handling — обработка расхождения цен и доступности
Главный принцип
Между моментом добавления компонента в черновик и моментом подтверждения тура проходит время. За это время цена компонента может измениться (drift), доступность может пропасть, политики могут измениться.
Платформа обязана отслеживать все такие изменения и информировать пользователя. Молчаливое использование устаревших данных — недопустимо.
Поток обнаружения
TourDraftItem добавлен в черновик
↓
Платформа подписывает item на каноничные события источника:
- offer.price_changed
- offer.availability_changed
- offer.policy_changed
- offer.withdrawn
↓
Событие приходит — создаётся DriftEvent
↓
Расчёт significance:
- изменение менее 1% — negligible
- 1–5% — noticeable
- более 5% или потеря — material
↓
Применение политики разрешения:
- negligible → auto_accepted (без уведомления)
- noticeable → user_notified, требуется явное подтверждение
- material → user_notified, обязательное явное решение
↓
Уведомление пользователю через все доступные каналы
↓
Пользователь решает:
- принять новую цену → DriftEvent.user_decision = accepted, item обновляется
- отказаться → user_decision = rejected, item помечается требующим замены
- заменить вариант → новый TourVariant выбирается, item обновляется
↓
Если есть нерешённые material drifts — переход в priced/proposed заблокирован
Политика автоматических замен (для определённых тарифов)
Для тарифов Professional и Enterprise — опциональная политика автоматической замены при material drift:
- Платформа автоматически предлагает эквивалентный вариант с похожими параметрами.
- Сохраняется на основе ML-модели похожести (см. Платформа машинного обучения).
- Пользователь получает уведомление с предложенным вариантом и опцией принять/отклонить/выбрать другой.
Для базовых тарифов — только пользовательское решение, без автоматической замены.
Защита от спекулятивного использования
Drift handling не позволяет партнёру:
- «Заморозить» цену простым удержанием черновика —
Quoteистекает по своему сроку независимо от черновика. - Получить arbitrage между ценой в момент добавления и моментом подтверждения — ценовая фиксация на уровне
Quote, а неTourDraftItem.
Каскадный drift и auto-replace для смежных компонентов
Раздел добавлен после внешнего архитектурного ревью 30.04.2026, в котором отмечено: «Если в туре из 5 элементов изменилась цена одного, это может сделать невыгодным весь тур. Не хватает логики автоматического переподбора при дрифте, которая была бы бесшовной для агента».
Раздел расширяет уже описанный механизм Drift handling сценарием каскадного влияния: дрейф одного элемента тура может потребовать пересмотра остальных элементов — либо для сохранения экономической целесообразности, либо для сохранения логистической целостности (bundle integrity).
Принцип
Тур — это связанная композиция, не сумма независимых элементов. Дрейф одного элемента может:
- сделать тур экономически невыгодным (например, авиабилет подорожал на 30%, и теперь общий тур дороже чем альтернативный отдельный пакет);
- нарушить логистическую связность (например, изменилось расписание авиарейса, и теперь transfer не успевает к новому времени);
- сделать ineligible применённую коммерческую политику (например, total tour price упал ниже минимального threshold для commission-based pricing партнёра).
Каноничный подход: при любом DriftEvent обязательно запускается каскадный анализ — оценка влияния на весь тур и предложение переподбора, если требуется.
Каскадный анализ — поток
DriftEvent создан для item X
↓
Запускается CascadeAnalysisJob для всего тура:
Шаг 1: пересчёт total_price тура с новой ценой item X
Шаг 2: проверка bundle integrity:
- время рейса vs время transfer
- регион проживания vs регион activity
- capacity transfer vs party_size
- period purchase insurance vs trip dates
Шаг 3: оценка экономической целесообразности:
- сравнение new_total с original_total
- сравнение new_total с estimated_alternative_packages
Шаг 4: классификация cascade severity:
- cascade_negligible (< 5% impact на общую цену И bundle integrity ОК)
- cascade_noticeable (5–15% impact ИЛИ minor integrity issue решаемый replacement)
- cascade_material (> 15% impact ИЛИ major integrity break ИЛИ компонент unavailable)
↓
Применение политики разрешения cascade
Каноничные threshold'ы
| Cascade severity | Total price impact | Bundle integrity | Auto-action |
|---|---|---|---|
| cascade_negligible | < 5% | OK | auto_accept без уведомления |
| cascade_noticeable | 5–15% | OK или minor | auto_replace с уведомлением (для Professional/Enterprise tariffs); user_notified для базовых |
| cascade_material | > 15% или unavailable | major break | требуется явное решение пользователя |
Threshold'ы конфигурируемые на уровне tenant_configuration — премиум партнёры могут получать более низкий порог auto_accept (более агрессивный auto-replace).
Replacement candidates — ranked alternatives
Для каждого item в туре в момент его добавления в черновик платформа предзаранее подготавливает ranked-список replacement candidates (10-20 alternatives). Кандидаты сохраняются в TourDraftItem.replacement_candidates[] и обновляются:
- При каждом drift event на этом item.
- При запуске revalidation тура.
- По расписанию (каждые N часов в течение жизни черновика).
Источник кандидатов
Replacement candidates — это похожие offer'ы, отобранные ML-моделью similarity:
- Для accommodation: alternative properties в том же geo-кластере, той же property class, с похожим amenity-набором, в том же date range.
- Для flights: alternative carriers, alternative time slots в day window ±4 часа, alternative routing (direct vs 1-stop), alternative cabin class (с явной маркировкой downgrade).
- Для transfers: alternative supplier'ы, alternative vehicle classes, alternative pickup time в window ±30 минут.
- Для activities: alternative time slots в день, alternative venues в том же geo-кластере, alternative activity types same theme.
- Для insurance: alternative underwriter с похожим coverage scope.
Ranking
Каждый кандидат имеет similarity_score (0–1) и price_delta (relative to original). Ranking учитывает:
- semantic similarity (similar amenities/features) — 50% weight;
- price proximity (closer to original price = higher rank) — 30% weight;
- supplier reliability (history of low drift) — 15% weight;
- partner-specific preferences (если партнёр-tenant имеет saved preferences) — 5% weight.
В Stage 1-2 ML-модели нет — ranking использует rule-based similarity (point-counting по amenity match). На Stage 3+ переход на ML-модель.
Bundle integrity — каноничные правила связности
Bundle integrity проверяется при cascade analysis обязательно — это strong invariant тура.
1. Temporal connectivity (временная связность)
- Flight → Transfer: между моментом landing и pickup transfer должно быть минимум 60 минут (обычно), 90 минут (для international с customs), 120 минут (если есть baggage delays).
- Transfer → Hotel checkin: pickup transfer должен прибыть в check-in window отеля или иметь explicit late_arrival arrangement.
- Activity → Activity в один день: минимум 30 минут transition time между activities.
2. Geographic connectivity (географическая связность)
- Hotel → Activity: расстояние не должно превышать пороги, заданные partner-policy (по умолчанию 50 км в day-trip радиусе).
- Multi-night accommodation: если тур включает несколько отелей в разных локациях, transfers между ними обязательны в bundle.
3. Capacity consistency
- Все capacity-полa должны соответствовать party_size тура: hotel rooms ≥ party_size, flight seats = party_size, transfer seats ≥ party_size.
4. Date consistency
- Insurance period должен покрывать весь trip dates — start no later than first booked date, end no earlier than last booked date.
- Activities должны попадать в trip dates (не до flight arrival, не после departure).
При нарушении любого из этих правил после drift — bundle_integrity_break event. Если нарушение можно исправить через replacement — auto_replace срабатывает; если нет — требуется явное решение.
Auto-replace для cascade_noticeable — поток
cascade_noticeable выявлен
↓
Выборка top-3 replacement candidates с наименьшим price_delta
↓
Application каждого кандидата:
Шаг 1: temporary swap в копии тура
Шаг 2: пересчёт cascade с replacement
Шаг 3: проверка bundle integrity после swap
Шаг 4: классификация new severity
↓
Если хотя бы один кандидат снижает severity до cascade_negligible:
→ выбирается best (highest similarity_score)
→ автоматическая замена с публикацией DraftItemReplacedEvent
→ партнёр получает notification с ДО/ПОСЛЕ компарацией
→ партнёр имеет 24 часа для отката замены через explicit reject action
↓
Если ни один кандидат не помогает:
→ escalation до cascade_material (требуется ручное решение)
Manual review для cascade_material
При cascade_material:
- Никакая автоматическая замена не выполняется — слишком велика вероятность что партнёр имеет специфические preferences, которые ML/rules не учли.
- Партнёр получает notification с полной информацией:
- Что изменилось (item, scope изменения).
- Полный новый total_price и delta vs original.
- Top-5 ranked replacement candidates с обоснованием.
- Опции: «принять текущий drift с новой ценой», «выбрать кандидата», «полностью отменить тур», «связаться с support».
- Тур переходит в
awaiting_partner_decisionsubstate; commit вpriced/proposedзаблокирован. - Если партнёр не отвечает в течение
tenant_configurable_window(обычно 24-48 часов) — тур автоматически переходит вexpiredс возвратом всех зарезервированных средств.
Атомарность auto-replace для bundle integrity
Когда replacement requires множественной замены (например, замена hotel A автоматически требует обновления transfer'ов и activities привязанных к hotel A's геолокации) — все замены выполняются атомарно через Saga:
- Либо все компоненты успешно заменены и тур пересчитан;
- Либо ничего не меняется, и тур переходит в
cascade_materialдля manual review.
Частичная замена (заменили hotel но не updated transfer) запрещена — это создаёт broken bundle.
Метрики качества cascade handling
Для мониторинга:
cascade_event_rate— DriftEvent'ов, перешедших в cascade analysis (обычно 100%, но если ниже — это bug).auto_replace_success_rate— доля cascade_noticeable, успешно auto-replaced без partner intervention.auto_replace_revert_rate— доля auto-replacements, которые партнёр потом отменил (показатель качества similarity model).cascade_material_rate— доля cascade events, переходящих в material (показатель supplier reliability).bundle_integrity_break_rate— частота нарушений bundle integrity (показатель quality replacement candidates).
При auto_replace_revert_rate > 20% — review similarity ranking model или ослабление threshold для auto_replace.
Связь с экономической моделью партнёра
Auto-replace может изменить commission баланс партнёра:
- Если replacement дешевле original → партнёрская commission меньше → loss-of-commission compensation policy (partner-tier-зависимая).
- Если replacement дороже original → партнёрская commission больше, но превышение должно быть явно одобрено партнёром (auto-replace не повышает total price без явного consent).
Каноничное правило: auto_replace не увеличивает total_price тура без явного consent партнёра. Если все top-3 candidates дороже original — это автоматически escalation в cascade_material.
Обоснование (тезисы)
Тезис 1. Drift одного элемента тура — это сигнал к пересмотру всего тура.
Альтернативы: (а) обрабатывать каждый item drift независимо; (б) каскадный анализ при любом drift.
Trade-off: вариант (а) допускает ситуацию «партнёр получил уведомление об одном drift, согласился, а потом обнаружил что весь тур стал невыгодным или нарушена связность»; вариант (б) — каноничный, обеспечивает целостность опыта партнёра.
Тезис 2. Bundle integrity — strong invariant, не optional check.
Альтернативы: (а) проверять integrity только в момент booking commit; (б) проверять integrity при каждом drift event.
Trade-off: вариант (а) откладывает проблему до checkout, что приводит к потере конверсии; вариант (б) — каноничный, проблемы выявляются и решаются заблаговременно.
Тезис 3. Auto-replace ограничен strict guardrails.
Альтернативы: (а) auto-replace для всех cascade severities включая material; (б) auto-replace только для cascade_noticeable, material — manual; (в) никакого auto-replace.
Trade-off: вариант (а) приводит к недовольству партнёров «вы заменили мой тур без спроса»; вариант (в) теряет UX-преимущество и нагружает партнёра ручными решениями для тривиальных случаев; вариант (б) — каноничный баланс.
Тезис 4. Replacement candidates ранжируются ML-моделью, но fallback на rule-based ranking гарантирован с Stage 1.
Альтернативы: (а) auto-replace доступен только с Stage 3 после ML-активации; (б) Stage 1 — rule-based ranking, Stage 3 — переход на ML.
Trade-off: вариант (а) откладывает значимую UX-фичу на 2 года; вариант (б) — каноничный rolling rollout.
Атомарность бронирования пакетного тура
Проблема
Пакетный тур из 3 компонентов = 3 отдельных бронирования у потенциально 3 разных поставщиков. При попытке подтверждения один поставщик может ответить успехом, второй — отказом, третий — задержкой.
Это классическая проблема распределённой транзакции — требуется атомарность «всё или ничего».
Решение: паттерн саги (saga pattern)
Платформа использует саги с компенсирующими действиями (compensating transactions):
TourBookingTransaction.start
↓
Step 1: lock_payment — блокирует платёжное намерение клиента (через PaymentIntent)
↓
├── failure → finish: payment_locked_failed
↓
Step 2: confirm_component_1 — отправка бронирования первому поставщику
↓
├── failure → compensate: unlock_payment, finish: failed
↓
Step 3: confirm_component_2 — отправка бронирования второму поставщику
↓
├── failure → compensate: cancel_component_1, unlock_payment, finish: rolled_back
↓
Step 4: confirm_component_3 — отправка бронирования третьему поставщику
↓
├── failure → compensate: cancel_component_1, cancel_component_2, unlock_payment, finish: rolled_back
↓
Step 5: capture_payment — фиксация платежа клиента
↓
├── failure → compensate: cancel_all_components, finish: payment_failure
↓
Step 6: finish: confirmed
Замок платежа перед компонентами
Ключевое решение: платежное намерение блокируется (authorized, но не captured) до попытки бронирования компонентов. Это:
- Гарантирует, что у клиента есть средства до начала операции.
- Позволяет откат платежа без возврата (просто
void) при провале. - Минимизирует время, в которое деньги клиента «висят» в платёжной системе.
См. Платёжный домен для деталей PaymentIntent.authorized и captured.
Компенсирующие действия
При провале на любом шаге выполняются компенсирующие транзакции в обратном порядке:
- Каждый успешный
Bookingотменяется через стандартный поток отмены поставщика. PaymentIntentпереводится вvoidedилиrefund_pendingв зависимости от состояния.- Клиент уведомляется о провале с разъяснением.
Журнал саги
TourBookingTransaction.saga_log хранит полный журнал всех шагов и компенсаций для:
- Аудита и расследования инцидентов.
- Replay в случае частичного сбоя инфраструктуры (саги — replay-safe, см. Событийная шина и асинхронная дисциплина).
- Доказательной базы для регулятора при спорах.
Обработка частичных задержек
Поставщики иногда отвечают с задержкой (pending_supplier_confirmation, см. API Contracts, состояния бронирования). Сага не блокируется ожиданием на каждом компоненте — переходит в асинхронный режим:
- Шаги с быстрыми ответами завершаются сразу.
- Шаги с задержкой переводятся в
awaiting_supplier. - Сага продолжает обработку других шагов параллельно.
- При окончательном ответе поставщика — продолжение или компенсация.
Это требует более сложной саги, но даёт значительно лучший пользовательский опыт при работе с поставщиками с разной скоростью.
Партнёрская модель интеграции
Программный интерфейс конструктора туров (Tour Builder API)
Партнёрский интерфейс — набор примитивных операций, не одна «магическая» функция:
| Операция | Назначение | Тариф |
|---|---|---|
create_draft | Создать пустой черновик | Все с Tour Builder access |
add_item | Добавить компонент | Все с Tour Builder access |
replace_item_variant | Заменить вариант компонента | Все с Tour Builder access |
remove_item | Удалить компонент | Все с Tour Builder access |
validate_composition | Проверить правила композиции | Все |
list_violations | Получить список нарушений | Все |
request_pricing | Запросить цены (создать Quote'ы) | Все |
generate_proposal | Сгенерировать документ предложения | Все |
commit_tour | Подтвердить тур (запустить TourBookingTransaction) | Все |
cancel_tour | Отменить тур после подтверждения | Все |
query_drift | Получить список текущих drift events | Все |
resolve_drift | Принять решение по drift event | Все |
apply_template | Применить шаблон пакетного тура | Все с шаблонной поддержкой |
clone_draft | Скопировать черновик | Professional+ |
export_artifact | Экспортировать артефакт (PDF) | Professional+ |
bulk_pricing | Запросить цены для множества вариантов одновременно | Enterprise |
Каждая операция — отдельный эндпоинт с явным контрактом. Это даёт партнёру полный контроль над процессом.
Webhooks для async-уведомлений
Партнёр подписывается на события Tour Builder:
tour.draft.violation_detected— обнаружено нарушение правила.tour.drift.detected— обнаружен drift одного из компонентов.tour.booking.committing_started— началоTourBookingTransaction.tour.booking.confirmed— успешное подтверждение.tour.booking.rolled_back— откат при провале.
Все webhooks следуют общему механизму из Уведомления и коммуникации.
Стабильность контракта
Программный интерфейс Tour Builder следует общим правилам стабильности из Программный интерфейс как продукт:
- Минимум 12 месяцев гарантированной поддержки версии.
- Запрет на ломающие изменения внутри версии.
- Контрактное тестирование на каждом релизе.
Ограничение скорости
Tour Builder — дорогостоящий контур: каждая операция композиции потенциально обращается к множеству поставщиков, ML-моделей, проверок правил. Применяется отдельное ограничение скорости для Tour Builder API, более строгое чем для базового партнёрского интерфейса:
- Максимум N черновиков в активном состоянии на тенанта одновременно.
- Максимум M вызовов
validate_compositionв минуту. - Максимум K параллельных
commit_tourтранзакций.
Это защищает платформу от злоупотреблений и обеспечивает справедливое распределение ресурсов.
Учёт потребления
Метрика tour_builder_operations (см. Учёт потребления и квоты) — основная метрика тарификации Tour Builder доступа. Каждая значимая операция (commit_tour, request_pricing, generate_proposal) учитывается отдельно.
Ценообразование пакетного тура
Каноничная формула
Стоимость тура для клиента:
tour_price =
Σ (component_quote.price × applicable_fx_rate)
+ platform_package_margin
+ applicable_taxes (per-jurisdiction, через TOMS для пакетных туров в EU)
- applied_discounts (промо, скидка партнёра, скидка за объём)
Маржа платформы на пакетных турах
Платформа применяет отдельную маржу для пакетных туров (platform_package_margin), отличную от индивидуальных бронирований. Обоснование:
- Tour Builder — дополнительная ценность (агрегация, проверка совместимости, атомарное бронирование, drift handling).
- Эту ценность платформа монетизирует через маржу.
- Размер маржи зависит от тарифа партнёра (для Enterprise — может быть нулевой или revenue share моделью).
Налоговый режим
Пакетный тур в EU подпадает под маржинальный режим Tour Operators (TOMS) — платформа платит НДС только с маржи, не с полной стоимости. Это существенно влияет на структуру цены и требует:
- Явного разделения cost-of-goods-sold и маржи в финансовом учёте.
- Корректного оформления чеков и счетов.
- Соответствующей конфигурации провайдера платёжных услуг.
Подробности — в Соответствие требованиям регуляторов.
Согласованность валют
Все компоненты тура должны быть в одной валюте отображения для клиента, даже если поставщики выставляют счета в разных валютах. Конвертация — через FxRateSnapshot (см. Интернационализация и локализация).
Метрики и наблюдаемость
Каноничные метрики операционной модели
- Время от создания черновика до подтверждения тура — медианное и 95-й процентиль. Позволяет обнаружить деградацию пользовательского опыта.
- Доля черновиков, доходящих до подтверждения (conversion rate) — главная продуктовая метрика.
- Доля провалов
TourBookingTransaction— операционная метрика. Цель — менее 2%. - Среднее число drift events на черновик — индикатор актуальности данных платформы.
- Доля автоматически разрешённых drift events vs ручных — характеристика тарифа.
- Среднее число элементов в подтверждённом туре — индикатор сложности использования.
- Распределение типов туров (linear/themed/package) — продуктовый сигнал.
- Среднее время выполнения саги
TourBookingTransaction— операционный показатель, влияющий на пользовательский опыт.
Алерты
Через Аналитика и бизнес-аналитика и Наблюдаемость и реагирование на инциденты:
- Алерт при превышении 5% провалов
TourBookingTransactionза час — оперативная команда расследует. - Алерт при появлении новых видов нарушений
CompositionRule— продуктовая команда оценивает. - Алерт при росте
materialdrift events выше нормального уровня — анализ источников (поставщик деградирует?).
События операционной модели Tour Builder
| Событие | Когда | Главные потребители |
|---|---|---|
tour.draft.created | Создание черновика | Платформа данных, партнёрская консоль |
tour.draft.item_added | Добавление элемента | Контур наблюдаемости, drift tracker |
tour.draft.item_replaced | Замена варианта | Контур наблюдаемости |
tour.draft.violation_detected | Обнаружение нарушения | Партнёр (через webhook), оперативная команда |
tour.draft.violation_resolved | Разрешение нарушения | Платформа данных |
tour.drift.detected | Обнаружение drift | Партнёр (через webhook), пользователь |
tour.drift.resolved | Разрешение drift | Платформа данных |
tour.proposal.generated | Генерация предложения | Контур уведомлений, аналитика |
tour.booking.transaction_started | Начало TourBookingTransaction | Платёжный контур, наблюдаемость |
tour.booking.saga_step_completed | Шаг саги | Наблюдаемость, аудит |
tour.booking.confirmed | Успешное подтверждение | Все потребители |
tour.booking.rolled_back | Откат | Контур уведомлений, оперативная команда |
tour.booking.partial_failure | Частичный провал требующий внимания | Срочный алерт оперативной команде |
Полная таксономия — в Первоначальная таксономия событий.
Регуляторные следствия
Директива о пакетных турах (EU 2015/2302, Package Travel Directive)
Применима ко всем пакетным турам, продаваемым в EU. Главные обязательства:
- Преддоговорная информация — клиент получает обязательный набор информации о туре до подписания.
- Финансовая гарантия — продавец (платформа или тенант) обязан иметь обеспечение на случай неплатёжеспособности.
- Право клиента на возврат при существенных изменениях (
materialdrift в нашей модели — основание для права возврата). - Ответственность продавца за исполнение всех компонентов тура — клиент обращается только к продавцу, не к каждому поставщику.
Модель отвечающего за платежи продавца определяет, кто именно несёт эти обязательства — платформа (модель А) или тенант (модель Б). Подробности — в Соответствие требованиям регуляторов.
Маржинальный режим НДС (TOMS)
В EU для пакетных туров применяется маржинальный режим. Каноничная архитектура платформы поддерживает это через:
- Явное разделение
cost_of_goods_soldиplatform_package_marginвTourBookingTransaction. - Корректные форматы счетов (с указанием маржинального режима).
- Журнал для НДС-отчётности.
Юрисдикционные особенности
Не-EU юрисдикции имеют свои правила:
- Украина — национальное законодательство о туристическом продукте, лицензирование туроператоров.
- Казахстан — национальные правила.
Каждое расширение географии — добавление юрисдикционной модели в реестр.
Фазы развёртывания
Фаза Bootstrap (0–6 месяцев)
Что разворачивается:
- Каноничная модель (
CompositionRule,CompositionViolation,DriftEvent,TourBookingTransaction) зафиксирована в схеме хранения. - Базовый прототип потока создания черновика на одном типе тура (
linear). - Простые правила композиции (temporal_consistency, capacity_consistency).
- Прототип атомарного подтверждения для тура из 2 компонентов.
Триггер выхода:
- Готовность к запуску партнёрского доступа Tour Builder в production.
Фаза 2 — Production Tour Builder (6–18 месяцев)
Что разворачивается:
- Полный программный интерфейс Tour Builder для партнёров с Tour Builder access.
- Все 3 типа туров (linear, themed, package).
- Полный набор
CompositionRuleдля каждого типа. - Полный drift handling с уведомлениями.
- Атомарное подтверждение через сагу для туров до 5 компонентов.
- Webhooks для всех событий Tour Builder.
- Партнёрская консоль для управления черновиками.
- Правила маржинального режима (TOMS) в EU.
Триггер выхода:
- Появление спроса на туры с большим числом компонентов (более 5).
- Появление partner-specific правил композиции.
Фаза 3 — Smart Tour Builder (18–30 месяцев)
Что разворачивается:
- ML-модели похожести для автоматических замен при drift.
- ML-модели рекомендаций (что добавить к туру).
- Шаблоны пакетных туров с динамической генерацией под параметры.
- Атомарное подтверждение для туров с большим числом компонентов через расширенную сагу.
- Расширенная аналитика операционной модели для партнёров.
- Tenant-specific
CompositionRuleдля корпоративных партнёров.
Фаза 4 — Maturity (30+ месяцев)
Что разворачивается:
- Conversational интерфейс конструктора туров (LLM-based).
- Multi-region обработка с локализацией данных.
- Маркетплейс шаблонов туров (партнёры могут публиковать шаблоны).
- ML-driven динамическое ценообразование пакетов.
Архитектурные решения с тезисным обоснованием
Решение 1. Raw API constructor по модулям, не «магическая функция»
Цель: дать партнёру максимальный контроль и обеспечить стабильность контракта.
Тезисы:
- «Магическая» функция (например,
build_tour(parameters)) скрывает логику и затрудняет отладку. Партнёр не знает, почему получился именно такой тур. - Примитивные операции дают партнёру полный контроль и прозрачность.
- Стабильность контракта проще обеспечить для набора простых операций, чем для одной сложной.
- Современные платформы (Stripe Connect, Twilio Studio) — все строят сложные потоки из примитивных операций. Это база.
Решение 2. CompositionRule как отдельная сущность
Цель: обеспечить расширяемость правил без релизов кода.
Тезисы:
- Правила композиции эволюционируют по мере роста платформы и требований партнёров. Жёсткое зашивание в код блокирует развитие.
- Tenant-specific правила (для корпоративных партнёров) невозможны без отдельной сущности.
- Аудит правил для регулятора — единственный способ через структурированную модель.
Решение 3. Drift handling как first-class
Цель: обеспечить честность с пользователем и соответствие регуляторам.
Тезисы:
- Молчаливое использование устаревших данных — нарушение Директивы о пакетных турах (которая требует уведомлять клиента о существенных изменениях).
- Без структурированной модели DriftEvent невозможно доказать регулятору, что пользователь был уведомлён.
- Прозрачность повышает доверие партнёров и пользователей.
Решение 4. Атомарность через сагу с компенсирующими действиями
Цель: обеспечить «всё или ничего» для пакетного тура.
Тезисы:
- Распределённые транзакции невозможны через несколько внешних поставщиков. Сага — единственный практический паттерн.
- Замок платежа перед компонентами минимизирует финансовый риск для платформы и клиента.
- Журнал саги обеспечивает replay-safety и аудит.
- Современные платформы (Uber, Netflix) — все строят критичные потоки на сагах.
Решение 5. Отдельное ограничение скорости и тарифика для Tour Builder
Цель: защита от злоупотребления дорогостоящим контуром и справедливое распределение.
Тезисы:
- Tour Builder — самый дорогой партнёрский контур (множество вызовов поставщикам, ML, проверок).
- Без отдельных ограничений недобросовестный партнёр может монополизировать ресурсы.
- Отдельная тарификация даёт справедливую модель — кто использует, тот и платит.
Открытые развилки
Развилка 1. Глубина ML-driven автоматических замен
В фазе 3 — ML-driven автозамены при material drift. Глубина (от предложения вариантов до полностью автоматической подмены без подтверждения) — открытая развилка.
Эскалируется: при создании ML-стратегии в фазе 3.
Развилка 2. Поддержка туров с большим числом компонентов (более 10)
Сложные корпоративные туры могут содержать 10+ компонентов. Атомарность через сагу для такого числа компонентов сложна.
Эскалируется: при появлении корпоративных партнёров с такими требованиями.
Развилка 3. Маркетплейс шаблонов туров
В фазе 4 — возможность партнёрам публиковать собственные шаблоны на платформе с разделением выручки. Существенная коммерческая возможность, требующая отдельной коммерческой модели.
Эскалируется: при стратегическом пересмотре в фазе 4.
Развилка 4. Conversational интерфейс Tour Builder
Через LLM — возможность строить тур через диалог («хочу романтический weekend в Праге для двоих в июне»). В фазе 4.
Эскалируется: при создании conversational-стратегии в Платформа машинного обучения.
Развилка 5. Поддержка туров с динамическими ценами от поставщиков
Некоторые поставщики (например, авиабилеты) имеют высоковолатильные цены, меняющиеся минута к минуте. Текущая модель Quote с фиксированной ценой может не подойти. Требуется отдельная стратегия — quote-on-commit или специальные high-volatility варианты.
Эскалируется: при добавлении поставщиков авиабилетов или подобных.
Связанная документация
Корневые архитектурные документы
- Манифест переосмысления § 1.5 — Tour Builder как ядро платформы; § 3.1 Дыра 3 — закрывается этим документом.
- Архитектурный якорь и бизнес-модель — Tour Builder как core для всех направлений.
- Поверхности взаимодействия — Tour Builder Closed Surface (поверхность 6), контур композиции тура.
- Каноничная доменная ось — каноничные сущности
TourDraft,TourProposal. - Платформа как продукт — Tour Builder как продуктовый атрибут.
- Операционная ось — наблюдаемость операционной модели.
Связанные доменные документы
- Tour Builder Domain — каноничные сущности (
TourDraft,TourDraftItem,TourVariant,TourProposal,TourVersion,TourArtifact). - Семантика предложений, цены, бронирования —
Quoteдля каждого компонента,Bookingпосле подтверждения. - Платёжный домен —
PaymentIntentдля всего пакета, замок платежа в саге. - Жизненный цикл пост-бронирования — отмены и изменения компонентов после подтверждения.
- Поиск и обнаружение — поиск кандидатов компонентов.
- Программный интерфейс как продукт — стабильность контракта Tour Builder API.
- Учёт потребления и квоты —
tour_builder_operationsметрика. - Уведомления и коммуникации — webhooks Tour Builder событий.
- Медиа и контент — медиа компонентов тура и
TourArtifact. - Платформа машинного обучения — ML для рекомендаций и автозамен.
- Соответствие требованиям регуляторов — Директива о пакетных турах, TOMS.
- Интернационализация и локализация — multi-currency и локализация артефактов тура.
- Тенантная настройка — конфигурация Tour Builder для тенанта.
- Тенантная идентичность и изоляция — изоляция черновиков по тенанту.
- Партнёрские взаиморасчёты — расчёты по пакетным турам.
- API Contracts — состояния
Booking, на которые опирается сага. - Событийная шина и асинхронная дисциплина — replay-safe саги.
Документы развития
- Первоначальная таксономия событий — таксономия событий Tour Builder.
- Скелеты OpenAPI и семейства ресурсов — спецификация Tour Builder API.
- Codex Architecture Review § 5.3 — оригинальный анализ слабостей модели Tour Builder.
Операционная сторона
- Наблюдаемость и реагирование на инциденты — наблюдаемость операционной модели.
- Релизы и совместимость — стабильность контракта Tour Builder API.
Архитектурные правила
- Закон 00000 — платформа главенствует над поставщиками — каноничные правила композиции задаются платформой.
- Современные лучшие практики верхнеуровневых платформ — Stripe Connect, Twilio Studio как ориентиры raw API constructor.
- Развитие без деградации — фазы как расширение, не миграция.
- Эластичное масштабирование и упаковка по фазам — фазы Tour Builder.
- Тезисное обоснование архитектурных решений — формат принятия решений.