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

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 typepin.invest-analytics.model-changed
Тип событияEconomicModelChangedKafkaEvent
Topicpin.invest-analytics.model-changed.v1
Partitions / key1 (правки модели редки — admin-операции; строгий порядок инвалидаций) / key = ChangeScope (или константа платформы)
ContractVersion1
Producerinvest-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Описание
ChangeScopestring (enum ModelChangeScope: Global/PropertyObjectType)Дауровень изменённой конфигурацииAdditive only (новые значения)Global — правка singleton EconomicModelConfig (затронуты все объекты недвижимости); PropertyObjectType — правка типа/оверрайдов по GroupId.
ChangeKindstring (enum ModelChangeKind: Upserted/Deleted/MonthOverridesReplaced)Давид admin-операцииAdditive only (новые значения)Что произошло: правка значений / удаление настройки типа (откат к платформе) / замена помесячных оверрайдов.
ConfigIdGuid?НетEconomicModelConfig.IdImmutableИдентификатор singleton-конфигурации при ChangeScope = Global; null для PropertyObjectType.
GroupIdlong?НетPropertyObjectTypeConfig.Id (GroupId/LayoutGroup)ImmutableТип объекта недвижимости при ChangeScope = PropertyObjectType; null для Global.
VersionlongДаVersion изменённой конфигурацииImmutable semanticsРевизия конфигурации после мутации — монотонный guard подписчика (правка с меньшей/равной версией того же scope — skip).
ChangedAtDateTimeOffsetДаUpdatedAt мутацииImmutableБизнес-время правки модели.
ChangedByUserIdGuid?НетUpdatedByUserIdAdditive 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-значение enum ModelChangeScope.

Когда публикуется

УсловиеChangeScope / ChangeKindСогласованность
Успешный PUT model-config (глобальный дефолт), доменное событие EconomicModelConfigUpdatedEventGlobal / Upsertedагрегат + outbox в одном согласованном изменении
Upsert настройки типа объекта недвижимости (PropertyObjectTypeConfigUpsertedEvent)PropertyObjectType / Upsertedта же
Удаление настройки типа (PropertyObjectTypeConfigDeletedEvent, откат к платформе)PropertyObjectType / Deletedта же
Replace-all помесячных оверрайдов типа (PropertyObjectTypeMonthOverridesReplacedEvent)PropertyObjectType / MonthOverridesReplacedта же
Не публикуется: команда не изменила данные (no-op upsert без дельты)

Потребители (consumers)

Consumer-контекстConsumerSide 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 метрик
Крах между коммитом агрегата и отправкой outboxoutbox-диспетчер публикует событие после восстановления (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, без bump ContractVersion.
  • 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, ChangedAtInternalinclude
ChangedByUserIdInternal (идентификатор актора; не PII)include (внутренний топик)
Коэффициенты эконом-модели (MarketPremium, PriceGrowthMonthly, …)Internal (не раскрываются наружу)exclude — снапшота значений в payload нет; подписчик читает модель напрямую

PII в событии отсутствует: событие описывает конфигурацию, а не пользователя (ChangedByUserId — только диагностический id актора-админа). Событие НЕ содержит эконом-коэффициенты, secrets, tokens, credentials, платёжных реквизитов, unbounded-вложений.

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

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

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