UC-RWD-015. Настройка индивидуальных условий вознаграждения по области и сроку (акции и boost)
Назначение
Описывает единый scoped + time-bounded паттерн настройки условий вознаграждения (ADR-0068) в трёх слоях, каждый из которых задаётся на область специфичности и период действия, а вместе они складываются в итоговую комиссию агента по сделке:
- Ставка застройщика — правило вознаграждения по оси каталога
Застройщик → Проект → Группа планировок → Планировка; временная акция застройщика (например 10% → 12% на неделю) выражается второй, более специфичной записью с ограниченным периодом. - Удержание ПИН — условия платформенной комиссии по оси
Застройщик → Проект → Группа планировокс уточнением по агентству; временная акция ПИН (пониженное удержание на срок промо) — обычной записью с ограниченнымEffectiveTo. - Boost агентства — мотивационная кампания, поднимающая долю набора агентов на период (например 4% → 7%), поверх постоянной комиссионной политики агентства.
Каждый слой резолвится по правилу «наиболее специфичное активное на момент asOf»; более специфичная и/или
более поздняя (по периоду) запись перекрывает базовую на время своего действия, не переписывая её. Применённая
область и период снимаются в снапшот начисления — расчёт объясним постфактум и не пересчитывается при
последующей смене условий. Связан с контекстом rewards.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-RWD-015 |
| Контекст | rewards (см. docs/06-services/rewards/) |
| Тип | основной (кросс-слойная настройка условий вознаграждения) |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Менеджер застройщика (слой 1); Менеджер ПИН (слой 2); Руководитель/администратор агентства (слой 3); System — резолюция при завершении сделки и превью в каталоге |
| Permissions / scope | Слой 1 — rewards.rules.manage (scope организация-застройщик); слой 2 — rewards.platform-terms.manage (платформенный scope); слой 3 — rewards.commission-policy.manage (scope организация-агентство) |
| Tenant | Слой 1 — TenantId = CompanyId застройщика (Type = Developer); слой 2 — вне tenant-модели (платформенная сущность); слой 3 — TenantId = CompanyId агентства (ADR-0052) |
| Record-level | Не применяется (все три — конфигурация уровня области/tenant) |
Предусловие
- Справочник каталога (
Project/LayoutGroup/Layout) наполнен в репликеrewardsconsumer-амиchessboard— нужен для валидации ссылок области при активации (INV-4 правила, ссылки условий ПИН). - Для слоя 3: состав агентов агентства синхронизирован (реплика
identity) — нужен для проверки набора boost при активации (INV-11). - Каждый актор аутентифицирован и имеет право своего слоя; компании-акторы
Status = Active.
Триггер
| Поле | Значение |
|---|---|
| Источник | UI button |
| Триггерящий элемент | ЛК застройщика → «Вознаграждения агентов» → «Создать правило/акцию»; админ-панель ПИН → «Условия платформенной комиссии» → «Создать/акция»; ЛК агентства → «Комиссии» → «Boost-кампании» → «Создать» |
Основной Поток
Слой 1. Застройщик настраивает scoped-ставку и акцию
- Менеджер застройщика заводит базовую ставку без измерений каталога (подходит любому объекту застройщика):
POST /rewards/developer/reward-rulesсcalculationBase(BasePrice/FinalPrice),commissionPercent,priority,effectiveFrom,effectiveTo? = null(бессрочно), пустымиconditions, и активирует правило (/activate) — переходDraft → Active(UC-RWD-001). - На неделю акции заводит более специфичную запись:
conditions.projectIds = [ЖК-X](илиlayoutGroupIds/layoutIdsдля более узкой области),commissionPercent = 12,effectiveFrom/effectiveToна границы недели, и активирует её. Система при активации проверяет: диапазон процента (INV-1), существование ссылок области в реплике каталога (INV-4), свободностьPriorityсреди активных (INV-3) и непересечение периодов среди правил той же сигнатуры специфичности (INV-9, иначеREWARDS_REWARD_RULE_OVERLAP). Публикуется снапшот набора активных правилpin.rewards.commission-rules.v1(и per-rulepin.rewards.reward-rule-changed.v1) →catalogпересчитывает денормализованную комиссию (UC-RWD-002). - Резолвер ставки для объекта ЖК-X в период акции выберет запись 12% (более специфичный
Project+ покрывающий период перекрывает базовые 10%); вне периода и для объектов других ЖК — базовые 10%. По завершении акции запись архивируется или истекает поEffectiveTo; базовая ставка ретроактивно не менялась.
Слой 2. ПИН настраивает scoped-удержание и акцию
- Менеджер ПИН заводит глобальные страховочные условия удержания (все измерения
null):POST /api/v1/platform/commission-termsсretentionPercentagePoints(0.25–1.50 п.п.),effectiveFrom, и активирует их (UC-RWD-007). Модель монетизации — только процентные пункты ставки застройщика (ADR-0066): при ставке 10% и удержании 1.00 п.п. платформа получает 1% цены, пул агентства — 9%. - Для точечной настройки заводит более специфичные условия на пересечении оси объекта
(
developerCompanyId → projectId → layoutGroupId) и измеренияagencyCompanyId; ось объекта заполняется сверху вниз (проект требует застройщика, группа — проект; иначеREWARDS_PLATFORM_TERMS_SCOPE_INVALID, INV-PT-5). Акция ПИН (пониженное удержание на срок) — запись с ограниченнымEffectiveTo. Активация проверяет диапазон пунктов (INV-PT-1) и непересечение периодов той же комбинации измерений (INV-PT-2, иначеREWARDS_PLATFORM_TERMS_OVERLAP). - При завершении сделки consumer резолвит удержание на
asOf = CompletedAt: наиболее специфичная активная запись по рангуAgencyLayoutGroup → … → Global; при отсутствии подошедших — fallback 0 п.п. (метрикаrewards_platform_terms_missing_total). Guard конфигурации (INV-PT-4): если удержание ≥CommissionPercentприменённого правила застройщика (пул ≤ 0) — начисление не создаётся, событие в DLQ (REWARDS_PLATFORM_RETENTION_EXCEEDS_RULE, разбор ПИН).
Слой 3. Агентство настраивает boost на период
- Руководитель агентства заводит постоянную комиссионную политику (удержание агентства в %/фикс, с оверрайдами по агентам; делит пул = комиссия − удержание ПИН) и активирует её (UC-RWD-005 фаза 1).
- Поверх политики заводит временный agent-boost:
POST /api/v1/agency/agent-boost-campaignsсboostRule(Kind ∈ {AgentPercentOfPool, AgentPercentOfPrice}, повышенная доля агента напрямую),agentUserIds(1..1000),effectiveFrom/effectiveTo, и активирует кампанию (/activate). Активация проверяет существование/активность агентов набора (INV-11,REWARDS_AGENT_BOOST_TARGETS_INVALID) и непересечение периодов по общим агентам с другими активными кампаниями (INV-9,REWARDS_AGENCY_POLICY_OVERLAP) — на любойasOfк агенту применима не более одной кампании. В отличие от политики, активная кампания предыдущую не архивирует. - При подтверждении начисления (
Confirm, UC-RWD-004) резолвится удержание агентства: если сделка попала в период boost и агент входит в набор — доля агента считается поBoostRuleвместо базового правила политики; иначе — по политике. Boost применяется только внутриrewards(интеграционных событий не публикует).
Резолюция и снапшот (System)
- При завершении сделки итоговое распределение складывается по слоям (в каталоге — та же резолюция для превью,
UC-RWD-002):
CommissionAmount= база × ставка застройщика (слой 1) →PlatformFeeAmount= база × удержание ПИН (слой 2) → пул =CommissionAmount − PlatformFeeAmount→ долиAgency/Agentпо политике с учётом boost (слой 3). В начисление снимается снапшот применённой области и периода каждого слоя (AppliedRuleSnapshot,AppliedPlatformTermsId+ область/период,AppliedBoostCampaignId) — последующая смена условий существующие начисления не пересчитывает (неретроактивность, ADR-0068).
Альтернативные Потоки
A1. Пересечение периодов внутри одной специфичности
- Активация правила застройщика/условий ПИН/boost-кампании с периодом, пересекающим уже активную запись той
же сигнатуры специфичности (для boost — по общему агенту), отклоняется: слой 1 —
REWARDS_REWARD_RULE_OVERLAP, слой 2 —REWARDS_PLATFORM_TERMS_OVERLAP, слой 3 —REWARDS_AGENCY_POLICY_OVERLAP(все 409). Гарантирует детерминизм резолюции: на любойasOfв одной области активна не более одной записи. Актор сдвигает период (или набор агентов) и повторяет активацию.
A2. Некорректная область
- Слой 2: ось объекта заполнена не сверху вниз (
projectIdбезdeveloperCompanyId,layoutGroupIdбезprojectId) →REWARDS_PLATFORM_TERMS_SCOPE_INVALID(400, INV-PT-5). Слой 1: ссылкиprojectIds/layoutGroupIds/layoutIdsне найдены в реплике или чужого застройщика →REWARDS_RULE_LAYOUT_NOT_FOUND(400, INV-4). Актор исправляет область и повторяет.
A3. Удержание съедает ставку правила
- На момент
CompletedAtприменимое удержание ПИН ≥CommissionPercentприменённого правила застройщика (пул ≤ 0) → ни одно начисление по сделке не создаётся, событие в DLQ (REWARDS_PLATFORM_RETENTION_EXCEEDS_RULE, INV-PT-4). Менеджер ПИН правит условия либо согласует ставку с застройщиком; после исправления событие переигрывается из DLQ (ретроактивное исправление требует условий с периодом, покрывающимasOf).
A4. Область без условий — fallback
- Слой 2: на
asOfнет ни специфичных, ни глобальных условий → удержание 0 п.п. (PlatformFeeAmount = 0,AppliedPlatformTermsId = null), пул = всей комиссии; warn-метрикаrewards_platform_terms_missing_total. Слой 1: нет подошедшего правила застройщика → комиссия в каталоге не показывается, агентское начисление по сделке не создаётся (UC-RWD-003). Слой 3: сделка вне периода boost или агент не в наборе → доля считается базовой политикой. Не ошибки.
A5. Досрочное завершение и архив
- Активную запись любого слоя можно досрочно завершить сокращением
EffectiveTo(единственная разрешённая мутация вActive: слой 1 — INV-5, слой 2 — INV-PT-3, слой 3 — INV-10; сокращение пересечений не создаёт) или архивировать. С моментаEffectiveTo/архива запись выбывает из резолюции; уже созданные начисления по снапшотам не пересчитываются. Слои 1–2 публикуют изменение вcatalog; boost событий не публикует.
A6. Гонка активаций
- Две параллельные активации в одной области разрешаются блокировкой ключа специфичности (слои 1–2 —
непересечение периодов; слой 1 дополнительно — partial-unique по
Priority; слой 3 — блокировка по общим агентам): проходит одна, проигравший получает соответствующий overlap-/priority-код (409) и повторяет после пересмотра. Гонка активации boost сConfirmначисления сериализуется:Confirmприменяет boost, только если прочитал кампанию уже активной (иначеAppliedBoostCampaignId = null), промежуточного состояния с двумя пересекающимися кампаниями по агенту не наблюдается.
Постусловие
- В каждой области каждого слоя на любой
asOfактивна не более одной записи данной специфичности (детерминизм промо-акций и boost); история — цепочка записей со своими периодами и областями. - Итоговая комиссия по сделке считается сквозь три слоя по наиболее специфичным активным записям на
CompletedAt; применённые область и период каждого слоя зафиксированы снапшотом в начислении. - Смена/архив условий меняет только будущие расчёты (каталог и новые начисления); созданные начисления неретроактивны (объяснимы своими снапшотами).
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
403 FORBIDDEN | 403 | Нет права своего слоя (rewards.rules.manage / rewards.platform-terms.manage / rewards.commission-policy.manage) | Скрыть управление |
REWARDS_RULE_PERCENT_OUT_OF_RANGE | 400 | Слой 1: commissionPercent вне (0; 100] (INV-1) | Подсветить процент |
REWARDS_RULE_RANGE_INVALID | 400 | Слой 1: некорректный диапазон комнат/площади или effectiveTo <= effectiveFrom (INV-2) | Подсветить диапазон |
REWARDS_RULE_LAYOUT_NOT_FOUND | 400 | Слой 1: ссылка области не в реплике/чужого застройщика (INV-4, A2) | Показать отсутствующие id |
REWARDS_RULE_PRIORITY_TAKEN | 409 | Слой 1: priority занят активным правилом (INV-3, A6) | Предложить другой приоритет |
REWARDS_REWARD_RULE_OVERLAP | 409 | Слой 1: пересечение периодов правил одной специфичности (INV-9, A1) | Сдвинуть период |
REWARDS_PLATFORM_TERMS_RETENTION_OUT_OF_RANGE | 400 | Слой 2: пункты вне 0.25–1.50 (INV-PT-1) | Подсветить поле пунктов |
REWARDS_PLATFORM_TERMS_SCOPE_INVALID | 400 | Слой 2: ось объекта не сверху вниз (INV-PT-5, A2) | Исправить область |
REWARDS_PLATFORM_TERMS_OVERLAP | 409 | Слой 2: пересечение периодов условий одной специфичности (INV-PT-2, A1) | Сдвинуть период |
REWARDS_AGENT_BOOST_RULE_INVALID | 400 | Слой 3: boostRule не форма доли агента / value вне 0..100 (INV-8) | Исправить правило boost |
REWARDS_AGENT_BOOST_TARGETS_INVALID | 400 | Слой 3: набор агентов пуст/дубликат/> 1000 / недоступный агент при активации (INV-11, A6) | Исправить набор |
REWARDS_AGENT_BOOST_PERIOD_INVALID | 400 | Слой 3: effectiveTo <= effectiveFrom (INV-12) | Исправить период |
REWARDS_AGENCY_POLICY_OVERLAP | 409 | Слой 3: пересечение периодов кампаний по общему агенту (INV-9, A1) | Сдвинуть период/набор |
REWARDS_PLATFORM_RETENTION_EXCEEDS_RULE | — (DLQ) | Резолюция: удержание ≥ ставки правила (INV-PT-4, A3) | Разбор ПИН + replay |
REWARDS_CONCURRENCY_CONFLICT | 409 | Параллельная мутация записи слоя (A6) | Перечитать и повторить |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
RewardRuleCreatedEvent / RewardRuleActivatedEvent / RewardRuleArchivedEvent | Слой 1: создание/активация/архив (в т.ч. акции) | in-proc аудит |
PlatformTermsCreatedEvent / PlatformTermsActivatedEvent / PlatformTermsArchivedEvent | Слой 2: создание/активация/архив условий (в т.ч. акции ПИН) | in-proc аудит |
AgentBoostCampaignCreatedEvent / AgentBoostCampaignActivatedEvent / AgentBoostCampaignArchivedEvent | Слой 3: жизненный цикл boost-кампании | in-proc аудит |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
CommissionRulesChangedKafkaEvent.v1 | pin.rewards.commission-rules.v1 (key = DeveloperCompanyId) | Слой 1: любое изменение набора активных правил застройщика (снапшот набора, ADR-0068) | catalog — атомарная замена денормализованного набора и пересчёт комиссий |
RewardRuleChangedKafkaEvent.v1 | pin.rewards.reward-rule-changed.v1 (key = Id) | Слой 1: Activate/Deactivate/Archive/смена EffectiveTo | catalog — per-rule дельта того же изменения |
Слой 2 (условия ПИН) и слой 3 (boost) Kafka-событий не публикуют — применяются только внутри rewards
(при создании и подтверждении начисления).
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Акция застройщика (более специфичный Project + период) перекрывает базовую ставку на время действия; вне периода — базовая | объект ЖК-X в период → 12%, вне → 10% |
| AC2 | Пересечение периодов одной специфичности отклоняется в каждом слое (INV-9 / INV-PT-2 / INV-9 boost) | активация перекрывающей записи → соответствующий overlap-код 409 |
| AC3 | Ось объекта условий ПИН заполняется сверху вниз (INV-PT-5); наиболее специфичная активная запись побеждает по рангу | projectId без застройщика → 400; резолюция двух условий → выигрывает специфичная |
| AC4 | Boost применяется агенту из набора в период кампании поверх политики; вне периода/набора — базовая доля | Confirm с asOf в периоде boost и агентом набора → доля по BoostRule |
| AC5 | Пересечение периодов boost по общему агенту отклоняется (INV-9) | активация второй кампании на того же агента с пересечением → 409 |
| AC6 | Удержание ≥ ставки правила → событие в DLQ, начисления не созданы (INV-PT-4); после исправления replay создаёт | условия 1.50 п.п. при правиле 1% → DLQ |
| AC7 | Итоговое распределение считается сквозь три слоя; применённая область/период каждого слоя зафиксированы снапшотом | завершение сделки → снапшоты AppliedRule/AppliedPlatformTerms/AppliedBoostCampaign |
| AC8 | Смена/архив условий не пересчитывает существующие начисления (неретроактивность) | активировать новую акцию → перечитать начисление: суммы прежние |
| AC9 | Досрочное сокращение EffectiveTo выводит запись из резолюции с указанного момента, не создавая пересечений | PATCH effective-to → объект после момента резолвится в базовую запись |
| AC10 | Изменения слоя 1 распространяются в каталог снапшотом набора; слои 2–3 внешних событий не публикуют | контракт-тест commission-rules; отсутствие Kafka у условий/boost |
Обратные ссылки
Автоссылки: где используется этот документ.
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- Domain entities: ../domain/reward-rule.md (слой 1: специфичность, период,
INV-1..9,
ResolveRule, промо-акции), ../domain/platform-commission-terms.md (слой 2: осьDeveloper → Project → LayoutGroup+ агентство, ранги, INV-PT-1..5, акции ПИН), ../domain/agency-commission-policy.md (слой 3: под-агрегатAgentBoostCampaignEntity, INV-8..12, применение приConfirm). - Снапшоты применения: ../domain/commission-accrual.md
(
AppliedRuleSnapshot,AppliedPlatformTermsId+ область/период,AppliedBoostCampaignId). - API методы: слой 1 —
../api/admin/create-reward-rule.md,../api/admin/activate-reward-rule.md; слой 2 — ../api/platform/create-platform-terms.md, ../api/platform/activate-platform-terms.md; слой 3 — ../api/agency/create-agent-boost-campaign.md, ../api/agency/activate-agent-boost-campaign.md, ../api/agency/create-agency-policy.md. - События: ../events/commission-rules.md, ../events/reward-rule-changed.md.
- Связанные UC: UC-RWD-001 (правила застройщика), UC-RWD-002 (комиссия в каталоге — та же резолюция),
UC-RWD-003 (начисление по сделке), UC-RWD-004 (строки распределения при
Confirm), UC-RWD-005 (политика агентства и boost), UC-RWD-007 (условия удержания ПИН). - ADR: 0068 (единый scoped + time-bounded паттерн, снапшот области/периода), 0066 (удержание — процентные пункты, три строки распределения), ADR-0056 (резолюция выборкой в память), ADR-0052 (tenancy).