Event Contract: pin.rewards.reward-rule-changed (RewardRuleChangedKafkaEvent)
Версионируемый контракт интеграционного события «правило вознаграждения изменено».
Consumer-документы ссылаются сюда, не переописывая payload.
Дом полей — entity: RewardRuleEntity.
Назначение
Фиксирует изменение действующего правила вознаграждения застройщика, влияющее на комиссию агента в
витрине: активацию (Draft → Active), деактивацию (Active → Draft), архивацию (* → Archived) и
досрочное завершение периода (EffectiveTo активного правила). Producer — rewards; событие публичное,
потому что catalog держит денормализованную комиссию агента по объекту недвижимости (блок «сколько заработает
агент») и должен обновлять/инвалидировать её без синхронного опроса rewards на каждый показ объекта недвижимости.
Создание черновика и правки в Draft события не публикуют — они не меняют подбор правил и витрину
(домашний reward-rule.md: RewardRuleCreatedEvent — только внутреннее
доменное событие).
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.rewards.reward-rule-changed |
| C# type | RewardRuleChangedKafkaEvent |
| Topic | pin.rewards.reward-rule-changed.v1 |
| Partitions / key | 3 / key = Id правила (порядок событий per-rule; объём — число правил, десятки на застройщика, ADR-0056) |
| ContractVersion | 1 |
| Producer | rewards: api/admin/activate-reward-rule.md, api/admin/archive-reward-rule.md, деактивация и смена EffectiveTo — api/admin/update-reward-rule.md (по конвенции README) |
| Статус | draft |
Публикуется атомарно с мутацией правила (сообщение и изменение RewardRuleEntity фиксируются в одном
согласованном изменении). Ключ Kafka = Id, порядок событий — per-rule; дедупликация у потребителя —
по идентификатору сообщения.
Payload RewardRuleChangedKafkaEvent
Snapshot-поля ссылаются на дом reward-rule.md — перечисляется только то, что
реально в событии. Payload несёт полное текущее состояние правила (self-contained snapshot), чтобы
catalog пересчитал комиссию без обратного запроса. Аудитные/служебные поля (Name, Description,
CreatedBy/UpdatedBy) в payload не входят — витрине не нужны.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
Id | Guid | Да | RewardRuleEntity.Id | Immutable | Ключ агрегата (= Kafka key). |
TenantId | Guid | Да | RewardRuleEntity.TenantId | Immutable | Компания-застройщик (CompanyId, Type = Developer) — владелец правила и scope витрины (ADR-0052). |
Status | string (enum RewardRuleStatus) | Да | RewardRuleEntity.Status | Additive only (новые значения) | Draft / Active / Archived. Active — правило участвует в подборе; Draft/Archived — сигнал инвалидации проекции. |
CalculationBase | string (enum CommissionCalculationBase) | Да | RewardRuleEntity.CalculationBase | Additive only | BasePrice (до ремонта/скидки) или FinalPrice (базовая + ремонт). |
CommissionPercent | decimal | Да | RewardRuleEntity.CommissionPercent | Additive only | Процент вознаграждения агента, 0 < x <= 100, scale 2. |
Priority | int | Да | RewardRuleEntity.Priority | Additive only | Приоритет разрешения конфликтов (наибольший среди подошедших активных выигрывает; уникален среди Active, INV-3). |
EffectiveFrom | DateTimeOffset? | Нет | RewardRuleEntity.EffectiveFrom | Additive only | Начало периода; null — сразу после активации. |
EffectiveTo | DateTimeOffset? | Нет | RewardRuleEntity.EffectiveTo | Additive only | Конец периода; null — бессрочно. Смена этого поля в Active — отдельная точка публикации. |
Conditions | RewardRuleConditionsModel | Да (может быть «пустым» — все измерения any) | RewardRuleEntity.Conditions | Additive only | Условия-предикаты подбора (проекция ниже). Пустой = правило-default, подходит любому объекту недвижимости застройщика. |
Version | long | Да | RewardRuleEntity.Version | Immutable semantics | Доменная версия; монотонный guard у потребителя (порядок per-Id). |
Вложенная модель RewardRuleConditionsModel
Проекция typed jsonb RewardRuleConditionsDocument (дом — reward-rule.md,
раздел «Модели JSONB»). Служебное поле ContractVersion документа наружу не отдаётся — семантика
контракта самого события описывается его версией (ContractVersion). Семантика измерений: значение null/пустой
список = «любое значение» (условие не проверяется); непустое — атрибут объекта недвижимости обязан попасть в
список/диапазон; между измерениями — логическое И. Ограничения и правила валидации — по дому.
| Поле | Тип | Обязательность | Ограничения (дом) | Описание |
|---|---|---|---|---|
PropertyTypes | IReadOnlyList<string> (enum PropertyType) | Нет (пусто = any) | элементы уникальны, ≤ 3 | Apartment / Parking / Commercial. |
LayoutIds | IReadOnlyList<Guid> | Нет (пусто = any) | уникальны, ≤ 500; Layout.Id того же застройщика | Планировки (chessboard). |
LayoutGroupIds | IReadOnlyList<Guid> | Нет (пусто = any) | уникальны, ≤ 200; LayoutGroup.Id того же застройщика | Группы планировок. |
RoomsRange | NullableRange<int> | Нет (пусто = any) | Min/Max в [0; 20], Min <= Max | Диапазон комнат. |
AreaRangeM2 | NullableRange<decimal> | Нет (пусто = any) | Min/Max в (0; 10000], scale 2 | Диапазон площади, м². |
Когда публикуется
| Условие | Переход состояния | Согласованность |
|---|---|---|
Активация (api/admin/activate-reward-rule.md): правило вводится в подбор | Draft → Active | Правило и факт публикации — в одном согласованном изменении |
Деактивация (возврат в черновик для правки, api/admin/update-reward-rule.md) | Active → Draft | Та же |
Архивация (api/admin/archive-reward-rule.md): вывод из подбора навсегда (Status = Archived, DeletedAt set, INV-6) | Draft → Archived / Active → Archived | Та же |
Смена EffectiveTo активного правила (досрочное завершение периода; единственное поле, изменяемое в Active, INV-5) | Active → Active (без смены Status) | Та же |
Создание правила и любые правки полей в Draft (кроме перехода в Active) события не публикуют —
черновик в подборе не участвует и на витрину не влияет.
Потребители (consumers)
| Потребитель | Эффект (событие → действие) | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
catalog (дом ../../catalog/consumers/rewards-commission.md) | Per-rule дельта-сигнал об изменении одного правила застройщика TenantId; батч-пересчёт диапазона комиссии каталог выполняет по снапшот-событию набора (ниже) | MessageId + монотонный guard по Version (порядок per-Id) | < 15 мин (обновление витрины) |
Соотношение со снапшот-контрактом. Основной канал распространения комиссий в
catalog— per-developer снапшот набора правилCommissionRulesChangedKafkaEvent(топикpin.rewards.commission-rules.v1, key =DeveloperCompanyId): он несёт весь действующий набор и позволяет каталогу собрать диапазонPercentMin..PercentMaxбатч-пересчётом без обратного запроса (ADR-0068). Это per-rule событие (pin.rewards.reward-rule-changed.v1, key =Id) публикуется параллельно тем же изменением как точечная дельта об одном правиле; оба события порождаются одной мутацией правила.
Версионирование и совместимость
- Версия — один
ContractVersion = 1; при несовпадении версии контракта в сообщении consumer логирует warning и продолжает (upcasting/schema-migration нет).RewardRuleConditionsModel.ContractVersionнаружу не отдаётся — версионируется только сам контракт события. - Additive-only для minor: новое измерение условия или optional-поле с безопасным default (
null= any у старых записей) — без bump. - Breaking (удаление/переименование/смена типа/смысла поля или семантики измерения) —
ContractVersion = 2+ новый топикpin.rewards.reward-rule-changed.v2+ dual-publish на период миграции + deadline миграции consumers + sunset старого топика. - Unknown enum:
Statusу потребителя — fail-closed (трактовать как «правило не активно» → инвалидация + warning); неизвестныйPropertyType/CalculationBase— по правилу дома (правило с непустым условием считается не подошедшим, reward-rule.md). - Deprecation — по общему sunset-правилу событийного реестра ../README.md.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
Id, TenantId, Status, Priority, EffectiveFrom, EffectiveTo, Version | Internal | include |
CalculationBase, CommissionPercent, Conditions.* | Internal (коммерческие условия застройщика) | include в payload (внутренний топик, m2m ACL Kafka); catalog отдаёт итоговую сумму комиссии агенту/менеджеру, но не проценты/предикаты правила; инвестору комиссия не показывается (reward-rule.md, «Индексы, хранение, безопасность») |
Правило PII не содержит (домашний reward-rule.md: «PII нет»), поэтому
маскирование ФИО/телефонов неприменимо. Событие НЕ содержит secrets, tokens, raw credentials, платёжные
реквизиты, суммы выплат, unbounded-вложения. TenantId/Id — идентификаторы, в URL/query не помещаются.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: RewardRuleEntity
- Use Cases (1): UC-RWD-015. Настройка индивидуальных условий вознаграждения по области и сроку (акции и boost)
- События (1): Event Contract: pin.rewards.commission-rules (CommissionRulesChangedKafkaEvent)
- registries (1): Реестр событий PIN
- index.md (1): Rewards Service
Связанные документы
- Сущность: reward-rule.md (дом полей,
RewardRuleConditionsDocument, stateDiagram, инварианты INV-1..7, алгоритмResolveRule). - Источники (операции, по конвенции README):
api/admin/activate-reward-rule.md,api/admin/archive-reward-rule.md,api/admin/update-reward-rule.md; внутренний расчёт —api/internal/calculate-property-object-commission.md. - Снапшот набора (основной канал в каталог): commission-rules.md
(
pin.rewards.commission-rules.v1, key =DeveloperCompanyId). - Потребитель:
catalog— rewards-commission.md; карта событий — ../README.md.