Перейти к основному содержимому

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 typepin.rewards.commission-rules
C# typeCommissionRulesChangedKafkaEvent
Topicpin.rewards.commission-rules.v1
Partitions / key3 / key = DeveloperCompanyId (порядок событий per-developer; объём — число застройщиков × редкие правки набора, ADR-0056)
ContractVersion1
Producerrewards: api/admin/activate-reward-rule.md, api/admin/archive-reward-rule.md, деактивация и смена EffectiveToapi/admin/update-reward-rule.md (по конвенции README)
Статусdraft

Публикуется атомарно с мутацией правила (сообщение и изменение RewardRuleEntity фиксируются в одном согласованном изменении). Ключ Kafka = DeveloperCompanyId, порядок событий — per-developer; дедупликация у потребителя — по идентификатору сообщения, монотонность — по RulesetVersion.

Payload CommissionRulesChangedKafkaEvent

Self-contained снапшот: несёт все активные правила застройщика на момент OccurredAt, чтобы catalog пересчитал комиссию без обратного запроса. Аудитные/служебные поля правил (Name, Description, CreatedBy/UpdatedBy) не входят — витрине не нужны.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
DeveloperCompanyIdGuidДаRewardRuleEntity.TenantIdImmutableКомпания-застройщик (CompanyId, Type = Developer) — владелец набора и scope витрины (= Kafka key, ADR-0052).
RulesetVersionlongДанабор (монотонный счётчик застройщика)Immutable semanticsМонотонная версия набора правил застройщика; инкремент при каждой публикации. Guard-порядок у потребителя (отбрасывание устаревшего снапшота при replay/переупорядочивании).
RulesIReadOnlyList<CommissionRuleSnapshot>Да (может быть пуст)активные RewardRuleEntity застройщикаAdditive only (поля элемента)Все правила застройщика со Status = Active и DeletedAt is null на OccurredAt. Пустой список = у застройщика нет действующих правил → комиссия снимается со всех его объектов недвижимости.
OccurredAtDateTimeOffsetДамомент фиксации измененияImmutableСнапшот-момент набора; используется потребителем для проверки периода правил (EffectiveFrom/EffectiveTo) и курсора свежести объектов без комиссии.

Вложенная модель CommissionRuleSnapshot

Снимок одного активного правила (дом — reward-rule.md). В список входят только Active-правила; статус в элемент не выносится (снапшот содержит лишь действующие). Служебных ссылок/PII нет.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
RuleIdGuidДаRewardRuleEntity.IdImmutableИдентификатор правила (для трассировки применённого правила в карточке; без FK — чужой контекст).
RuleVersionlongДаRewardRuleEntity.VersionImmutable semanticsДоменная версия правила (аудит; отличать от RulesetVersion набора).
PriorityintДаRewardRuleEntity.PriorityAdditive onlyТай-брейк среди правил одной специфичности; уникален среди Active застройщика (INV-3). Определяет «представительное» правило при сборке диапазона.
CalculationBasestring (enum CommissionCalculationBase)ДаRewardRuleEntity.CalculationBaseAdditive onlyБаза расчёта: BasePrice (до ремонта/скидки) или FinalPrice (базовая + ремонт).
CommissionPercentdecimalДаRewardRuleEntity.CommissionPercentAdditive onlyПроцент вознаграждения агента, 0 < x <= 100, scale 2.
EffectiveFromDateTimeOffset?НетRewardRuleEntity.EffectiveFromAdditive onlyНачало периода действия; null — с момента активации.
EffectiveToDateTimeOffset?НетRewardRuleEntity.EffectiveToAdditive onlyКонец периода (исключительно); null — бессрочно. Правило вне периода на OccurredAt потребитель считает не подошедшим.
ConditionsRewardRuleConditionsModelДа (может быть «пустым» — все измерения any)RewardRuleEntity.ConditionsAdditive onlyУсловия-предикаты подбора (проекция ниже). Пустой = правило-default, подходит любому объекту недвижимости застройщика.

Вложенная модель RewardRuleConditionsModel

Проекция typed jsonb RewardRuleConditionsDocument (дом — reward-rule.md, раздел «Модели JSONB»). Служебное поле ContractVersion документа наружу не отдаётся — семантика контракта версионируется самим событием (ContractVersion). Семантика измерений: значение null/пустой список = «любое значение» (условие не проверяется); непустое — атрибут объекта недвижимости обязан попасть в список/диапазон; между измерениями — логическое И. Ограничения и правила валидации — по дому.

ПолеТипОбязательностьОграничения (дом)Описание
PropertyTypesIReadOnlyList<string> (enum PropertyType)Нет (пусто = any)элементы уникальны, ≤ 3Apartment / Parking / Commercial. Unknown-тип при непустом списке → правило не подошло (fail-closed).
ProjectIdsIReadOnlyList<Guid>Нет (пусто = any)уникальны, ≤ 200; Project.Id (ЖК) того же застройщикаПроекты-ЖК (chessboard); наименее специфичное измерение оси каталога.
LayoutGroupIdsIReadOnlyList<Guid>Нет (пусто = any)уникальны, ≤ 200; LayoutGroup.Id того же застройщикаГруппы планировок; средняя специфичность оси каталога.
LayoutIdsIReadOnlyList<Guid>Нет (пусто = any)уникальны, ≤ 500; Layout.Id того же застройщикаПланировки; наиболее специфичное измерение оси каталога.
RoomsRangeNullableRange<int>Нет (пусто = any)Min/Max в [0; 20], Min <= MaxДиапазон комнат (открытая граница = сторона не ограничена).
AreaRangeM2NullableRange<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[].EffectiveToInternalinclude
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 не помещаются.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы