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