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

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 typepin.rewards.reward-rule-changed
C# typeRewardRuleChangedKafkaEvent
Topicpin.rewards.reward-rule-changed.v1
Partitions / key3 / key = Id правила (порядок событий per-rule; объём — число правил, десятки на застройщика, 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 = Id, порядок событий — per-rule; дедупликация у потребителя — по идентификатору сообщения.

Payload RewardRuleChangedKafkaEvent

Snapshot-поля ссылаются на дом reward-rule.md — перечисляется только то, что реально в событии. Payload несёт полное текущее состояние правила (self-contained snapshot), чтобы catalog пересчитал комиссию без обратного запроса. Аудитные/служебные поля (Name, Description, CreatedBy/UpdatedBy) в payload не входят — витрине не нужны.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
IdGuidДаRewardRuleEntity.IdImmutableКлюч агрегата (= Kafka key).
TenantIdGuidДаRewardRuleEntity.TenantIdImmutableКомпания-застройщик (CompanyId, Type = Developer) — владелец правила и scope витрины (ADR-0052).
Statusstring (enum RewardRuleStatus)ДаRewardRuleEntity.StatusAdditive only (новые значения)Draft / Active / Archived. Active — правило участвует в подборе; Draft/Archived — сигнал инвалидации проекции.
CalculationBasestring (enum CommissionCalculationBase)ДаRewardRuleEntity.CalculationBaseAdditive onlyBasePrice (до ремонта/скидки) или FinalPrice (базовая + ремонт).
CommissionPercentdecimalДаRewardRuleEntity.CommissionPercentAdditive onlyПроцент вознаграждения агента, 0 < x <= 100, scale 2.
PriorityintДаRewardRuleEntity.PriorityAdditive onlyПриоритет разрешения конфликтов (наибольший среди подошедших активных выигрывает; уникален среди Active, INV-3).
EffectiveFromDateTimeOffset?НетRewardRuleEntity.EffectiveFromAdditive onlyНачало периода; null — сразу после активации.
EffectiveToDateTimeOffset?НетRewardRuleEntity.EffectiveToAdditive onlyКонец периода; null — бессрочно. Смена этого поля в Active — отдельная точка публикации.
ConditionsRewardRuleConditionsModelДа (может быть «пустым» — все измерения any)RewardRuleEntity.ConditionsAdditive onlyУсловия-предикаты подбора (проекция ниже). Пустой = правило-default, подходит любому объекту недвижимости застройщика.
VersionlongДаRewardRuleEntity.VersionImmutable semanticsДоменная версия; монотонный guard у потребителя (порядок per-Id).

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

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

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

Соотношение со снапшот-контрактом. Основной канал распространения комиссий в catalogper-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, VersionInternalinclude
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 не помещаются.

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

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

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