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

Сущность: PropertyObjectTypeMonthOverride

Помесячный оверрайд экономической модели для типа объекта недвижимости.

Назначение

PropertyObjectTypeMonthOverride — точечная правка коэффициентов расчёта для конкретного календарного месяца (Year + Month) конкретного типа объекта недвижимости. Верхний уровень разрешения коэффициентов: PropertyObjectTypeMonthOverridePropertyObjectTypeConfigEconomicModelConfig → дефолты кода. Позволяет админам платформы «прибивать» факт/план по месяцам: рыночную цену м², темпы роста, заполняемость, долю расходов, накопленный EBITDA/м², месячные налоговые ставки. Дочерняя сущность агрегата PropertyObjectTypeConfig (не самостоятельный агрегат): загружается и сохраняется только через родителя — админка присылает полный список месяцев типа (операция replace-all).

Отличие от родительских уровней: помесячный оверрайд задаёт не только коэффициенты-темпы, но и абсолютные значения (MarketPriceM2, Ebitda), которые в расчёте замещают вычисленные величины месяца (см. формулы в investor-series-models.md).

Классификация

ПолеЗначение
Контекстinvest-analytics
Kindдочерняя сущность агрегата PropertyObjectTypeConfig; composite PK (GroupId, Year, Month) — натуральный ключ месяца
Source of truthinvest-analytics
Таблицаinvest_analytics.property_object_type_month_overrides
Tenant isolationне tenanted — платформенная настройка (ADR-0052 п.3)

Аудит и конкурентность — на родителе (PropertyObjectTypeConfig.Version/UpdatedByUserId инкрементируется replace-all-операцией); собственных audit-полей и версии у строки нет.

Поля

Все коэффициенты nullable: null = «поле месяца не оверрайдится» (берётся значение уровня типа объекта недвижимости или платформы). Семантика/единицы наследуемых коэффициентов — economic-model-config.md; ниже полностью описаны только поля, которых нет на родительских уровнях.

ПолеТипОбязательностьОграничения (PK/FK/index/constraint)ИсточникОписание
GroupIdlongДаPK часть 1; FK → invest_analytics.property_object_type_config(id) on delete cascadeродительТип объекта недвижимости (группа планировок).
YearintДаPK часть 2; check >= 2000 AND <= 2100admin APIКалендарный год месяца-оверрайда.
MonthintДаPK часть 3; check >= 1 AND <= 12admin APIКалендарный месяц (1–12).
MarketPriceM2decimal?Нетnumeric(12,2); check > 0 при not nulladmin APIАбсолютная рыночная цена м² в этом месяце, руб: замещает вычисленное marketM2 месяца (применяется только при значении > 0 — правило positiveOr).
PriceGrowthMonthlydecimal?Нетnumeric(9,6); check > -1 AND <= 1admin APIТемп роста цены в этом месяце (замещает уровневый темп при переходе от предыдущего месяца).
DailyRentM2decimal?Нетnumeric(12,2); check > 0 при not nulladmin APIАбсолютная ставка аренды руб/м²/сутки этого месяца (замещает вычисленную, правило positiveOr).
RentGrowthMonthlydecimal?Нетnumeric(9,6); check > -1 AND <= 1admin APIТемп роста аренды в этом месяце.
Occupancydecimal?Нетnumeric(9,6); check >= 0 AND <= 1admin APIЗаполняемость этого месяца.
ExpenseSharedecimal?Нетnumeric(9,6); check >= 0 AND <= 1admin APIДоля расходов этого месяца.
Ebitdadecimal?Нетnumeric(12,2); check > 0 при not nulladmin APIАбсолютный накопленный EBITDA/м² (руб/м²) на этот месяц: замещает вычисленное cumulativeRevenueM2 × (1 − ExpenseShare) (правило positiveOr; применяется только в месяцах после ввода в эксплуатацию).
AppreciationTaxMonthlydecimal?Нетnumeric(9,6); check >= 0 AND <= 1/12admin APIМесячная ставка налога на прирост стоимости для этого месяца; эффективная годовая ставка = значение × 12 — замещает ставку налогового режима (tax-entity-params.md).
EbitdaTaxMonthlydecimal?Нетnumeric(9,6); check >= 0 AND <= 1/12admin APIМесячная ставка налога на EBITDA для этого месяца; годовая = × 12, замещает ставку режима.

Связи

СвязьТипКлючПравило загрузкиDelete behavior
PropertyObjectTypeConfigродитель агрегатаFK GroupIdвсегда в составе агрегата: в админке — вместе с родителем по GroupId; при расчёте — полный список месяцев по GroupId (без пагинации — единицы-десятки строк)cascade от родителя

Инварианты и переходы состояний

ИнвариантУсловиеГде проверяетсяОшибка
INV-MO1 UniqueMonthНа (GroupId, Year, Month) — не более одной строкиcomposite PKнарушение невозможно на уровне БД
INV-MO2 ParentExistsGroupId имеет строку PropertyObjectTypeConfig (админка сперва создаёт настройку типа)FK + проверка родителя при replace-allINVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_NOT_FOUND
INV-MO3 RangesДиапазоны из таблицы полей (месяц 1–12, год 2000–2100, доли 0..1, абсолюты > 0, месячные налоги ≤ 1/12)валидация + check-constraintsValidationError
INV-MO4 AtLeastOneValueСтрока содержит хотя бы одно not null-поле оверрайда (полностью пустой месяц не сохраняется — replace-all отбрасывает)валидация admin-командыValidationError
INV-MO5 NoDuplicatesInPayloadВ payload replace-all нет двух элементов с одинаковыми (Year, Month)валидацияValidationError
INV-MO6 ReplaceAllAtomicЗамена набора месяцев типа: удаление всех строк группы + вставка новых + инкремент Version родителя — в одном согласованном измененииобработка admin-команды

Граничные случаи replace-all

  • Пустой список месяцев (payload []) — валиден: означает «очистить все помесячные оверрайды типа», все строки группы удаляются, Version родителя инкрементируется, публикуется MonthOverridesReplaced. Отличать от полностью пустой строки месяца (все поля-оверрайды null) — та отбрасывается ещё валидацией (INV-MO4) и до записи не доходит.
  • Замена того же набора (payload идентичен текущему состоянию) — трактуется как правка: удаление + вставка тех же строк в одном изменении, Version инкрементируется, событие публикуется (recompute пересчитает те же значения — идемпотентно по результату).
  • Порядок элементов в payload незначим; уникальность пары (Year, Month) внутри payload обязательна (INV-MO5), иначе ValidationError до применения — частичной записи набора не происходит.

Доменные и интеграционные события

СобытиеТипКогдаSnapshot/поля
PropertyObjectTypeMonthOverridesReplacedEventдоменноеReplace-all набора месяцев типаGroupId, число строк до/после, UpdatedByUserId — audit-лог

Интеграционное событие: replace-all набора месяцев (PropertyObjectTypeMonthOverridesReplacedEvent) публикует EconomicModelChangedKafkaEvent (pin.invest-analytics.model-changed.v1, ChangeScope = PropertyObjectType, GroupId, ChangeKind = MonthOverridesReplaced) через outbox — internal recompute job пересчитывает метрики затронутых объектов недвижимости (../events/model-changed.md).

Индексы, хранение, безопасность

АспектЗначение
Индексыcomposite PK (GroupId, Year, Month) — покрывает и выборку всех месяцев типа (GroupId-префикс), отдельные индексы не нужны
Партиционирование / retentionнет — единицы–сотни строк на всю платформу
Concurrencyчерез родителя: инкремент версии PropertyObjectTypeConfig при replace-all — конкурентные replace-all конфликтуют → 409 INVEST_ANALYTICS_PROPERTY_OBJECT_TYPE_CONFLICT
Permissions / PIIкак у родителя: admin invest-analytics.property-object-types.view / invest-analytics.property-object-types.manage (ADR-0053). PII нет
Auditчерез родителя (Version, actor-stamping) + PropertyObjectTypeMonthOverridesReplacedEvent

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

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

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