Event Contract: pin.invest-analytics.model-changed (EconomicModelChangedKafkaEvent)
Версионируемый контракт интеграционного события «эконом-модель доходности изменена». Producer —
invest-analytics; событие анонсирует правку конфигурации экономической модели, из-за которой меняются
расчётные метрики доходности объектов недвижимости и требуется их пересчёт/инвалидация вниз по потоку.
Назначение
Правка эконом-модели админом (глобальный дефолт EconomicModelConfig
или параметры типа объекта недвижимости PropertyObjectTypeConfig
- помесячные оверрайды) меняет вычисляемые метрики доходности для затронутых объектов недвижимости.
Метрики не хранятся (решение №3 ../README.md) — событие несёт не значения, а факт
инвалидации с указанием scope-а: подписчик (internal recompute job) пересчитывает затронутые объекты
недвижимости и переиздаёт
pin.invest-analytics.property-object-metrics.v1.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.invest-analytics.model-changed |
| Тип события | EconomicModelChangedKafkaEvent |
| Topic | pin.invest-analytics.model-changed.v1 |
| Partitions / key | 1 (правки модели редки — admin-операции; строгий порядок инвалидаций) / key = ChangeScope (или константа платформы) |
| ContractVersion | 1 |
| Producer | invest-analytics: admin-операции PUT model-config / upsert-delete типа объекта недвижимости / replace-all помесячных оверрайдов — см. «Когда публикуется» |
| Статус | draft |
Событие публикуется через транзакционный outbox — атомарно с мутацией агрегата конфигурации, той же
операцией, что и доменное событие аудита. Ключ дедупликации сообщения используется потребителем;
идентификатор и тип агрегата-источника — ConfigId ?? GroupId / EconomicModel.
Payload EconomicModelChangedKafkaEvent
Событие несёт scope инвалидации, а не снапшот коэффициентов: эффективные значения подписчик получит, перечитав актуальную модель при пересчёте (модель — Internal, наружу не раскрывается — см. «Безопасность»). Дом полей уровней модели — economic-model-config.md, property-object-type-config.md.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
ChangeScope | string (enum ModelChangeScope: Global/PropertyObjectType) | Да | уровень изменённой конфигурации | Additive only (новые значения) | Global — правка singleton EconomicModelConfig (затронуты все объекты недвижимости); PropertyObjectType — правка типа/оверрайдов по GroupId. |
ChangeKind | string (enum ModelChangeKind: Upserted/Deleted/MonthOverridesReplaced) | Да | вид admin-операции | Additive only (новые значения) | Что произошло: правка значений / удаление настройки типа (откат к платформе) / замена помесячных оверрайдов. |
ConfigId | Guid? | Нет | EconomicModelConfig.Id | Immutable | Идентификатор singleton-конфигурации при ChangeScope = Global; null для PropertyObjectType. |
GroupId | long? | Нет | PropertyObjectTypeConfig.Id (GroupId/LayoutGroup) | Immutable | Тип объекта недвижимости при ChangeScope = PropertyObjectType; null для Global. |
Version | long | Да | Version изменённой конфигурации | Immutable semantics | Ревизия конфигурации после мутации — монотонный guard подписчика (правка с меньшей/равной версией того же scope — skip). |
ChangedAt | DateTimeOffset | Да | UpdatedAt мутации | Immutable | Бизнес-время правки модели. |
ChangedByUserId | Guid? | Нет | UpdatedByUserId | Additive only | Актор-админ (диагностика/аудит; null для seed/системной правки). |
Вложенных моделей нет — payload плоский. Развилка scope: ровно одно из (ConfigId, GroupId)
заполнено согласно ChangeScope.
Почему нет developer/project-scope. Экономическая модель платформенная и не tenanted (ADR-0052 п.3, economic-model-config.md): scope-ы ограничены двумя реальными уровнями модели — платформа (
Global) и тип объекта недвижимости/LayoutGroup(PropertyObjectType). Per-developer/per-project настройки в v1 нет; при появлении — additive-значение enumModelChangeScope.
Когда публикуется
| Условие | ChangeScope / ChangeKind | Согласованность |
|---|---|---|
Успешный PUT model-config (глобальный дефолт), доменное событие EconomicModelConfigUpdatedEvent | Global / Upserted | агрегат + outbox в одном согласованном изменении |
Upsert настройки типа объекта недвижимости (PropertyObjectTypeConfigUpsertedEvent) | PropertyObjectType / Upserted | та же |
Удаление настройки типа (PropertyObjectTypeConfigDeletedEvent, откат к платформе) | PropertyObjectType / Deleted | та же |
Replace-all помесячных оверрайдов типа (PropertyObjectTypeMonthOverridesReplacedEvent) | PropertyObjectType / MonthOverridesReplaced | та же |
| Не публикуется: команда не изменила данные (no-op upsert без дельты) | — | — |
Потребители (consumers)
| Consumer-контекст | Consumer | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|---|
invest-analytics (self) | EconomicModelChangedRecomputeConsumer | Пересчёт метрик доходности затронутых объектов недвижимости (Global — все / PropertyObjectType — по GroupId) и переиздание pin.invest-analytics.property-object-metrics.v1 | ключ сообщения + монотонный guard по (ChangeScope, GroupId, Version) | < 15 мин (пересчёт батчем) |
Прямых внешних потребителей нет: витрина (catalog) инвалидирует бейджи доходности не этим событием, а
переизданными метриками объектов недвижимости (property-object-metrics.v1) — так каталог остаётся
denormalized-конечной точкой без знания об эконом-модели (ADR-0056). Появление нового подписчика —
additive-строка в этой таблице + в README.md.
Отказы и восстановление
| Сценарий | Поведение |
|---|---|
| Сбой recompute-подписчика на сообщении | retry с backoff; сообщение не подтверждается, пока пересчёт scope-а не зафиксирован (at-least-once) |
| Повторная доставка того же сообщения | монотонный guard по (ChangeScope, GroupId, Version): правка с версией ≤ применённой того же scope — skip, пересчёт не откатывается |
| Исчерпание retry | сообщение в DLQ + алерт; витринные метрики затронутого scope остаются на прежних значениях (не обнуляются) до ручного переигрывания — деградация «устаревший бейдж», не потеря данных |
Гонка правок Global и PropertyObjectType | пересчёты по scope независимы (Global покрывает все объекты, PropertyObjectType — подмножество по GroupId); повторное применение идемпотентно по snapshot метрик |
| Крах между коммитом агрегата и отправкой outbox | outbox-диспетчер публикует событие после восстановления (at-least-once); правка модели уже сохранена и читается расчётом сразу |
Промежуточное состояние для пользователя: между admin-правкой и завершением пересчёта (окно до 15 мин)
страница аналитики объекта недвижимости (investor-series, читает модель вживую) уже показывает новые
числа, а бейджи каталога — прежние; расхождение самоустраняется после переиздания
property-object-metrics.v1.
Версионирование и совместимость
- Версия —
ContractVersion = 1; при несовпадении версии сообщения consumer логирует warning и продолжает (без upcasting/schema-migration). Топик кодирует версию конвенцией суффикса.v1. - Additive-only для minor: новые поля / новые значения enum (
ChangeScope,ChangeKind) — с безопасным default, без bumpContractVersion. - Breaking (удаление/переименование/смена типа/смысла) —
ContractVersion = 2+ новый топикpin.invest-analytics.model-changed.v2+ dual-publish + deadline миграции consumers + sunset.v1. - Unknown enum (
ChangeScope/ChangeKind) у подписчика — fallback: консервативный полный пересчёт (fail-safe: инвалидируем шире) + warning.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
ChangeScope, ChangeKind, ConfigId, GroupId, Version, ChangedAt | Internal | include |
ChangedByUserId | Internal (идентификатор актора; не PII) | include (внутренний топик) |
Коэффициенты эконом-модели (MarketPremium, PriceGrowthMonthly, …) | Internal (не раскрываются наружу) | exclude — снапшота значений в payload нет; подписчик читает модель напрямую |
PII в событии отсутствует: событие описывает конфигурацию, а не пользователя (ChangedByUserId — только
диагностический id актора-админа). Событие НЕ содержит эконом-коэффициенты, secrets, tokens, credentials,
платёжных реквизитов, unbounded-вложений.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (3): Сущность / агрегат: EconomicModelConfig, Сущность / агрегат: PropertyObjectTypeConfig, Сущность: PropertyObjectTypeMonthOverride
- API (3): PUT /api/admin/v1/model-config — Обновление платформенной экономической модели (admin), PUT /api/admin/v1/property-object-types/{groupId} — Upsert настройки типа объекта недвижимости (admin), PUT /api/admin/v1/property-object-types/{groupId}/month-overrides — Replace-all помесячных оверрайдов типа объекта недвижимости (admin)
- Use Cases (2): UC-PRC-003. Admin: настройка экономической модели платформы + investor-preview, UC-PRC-004. Admin: настройка типа объекта недвижимости и помесячных оверрайдов
- События (2): События invest-analytics: карта контрактов, Event Contract: pin.invest-analytics.property-object-metrics-updated (PropertyObjectMetricsUpdatedKafkaEvent)
- Консюмеры (1): Consumer: Invest-Analytics Enrichment (pin-catalog-analytics-enrichment)
- index.md (1): InvestAnalytics Service
- registries (1): Реестр событий PIN
Связанные документы
- Источники (уровни модели): economic-model-config.md (
Global), property-object-type-config.md, property-object-type-month-override.md (PropertyObjectType). - Следствие: property-object-metrics.md (переиздаётся recompute job-ом).
- Карта событий контекста: README.md; обзор — ../README.md.
- ADR-0052, ADR-0056.