Назначение
invest-analytics владеет аналитикой доходности недвижимости PIN: (1) append-only историей изменения цен
объектов в ClickHouse (источник — контекст chessboard через события) с public API графиков цены
(грануляция day/month/year + growthPercent); (2) экономической моделью доходности (платформенный уровень
/ тип объекта недвижимости / помесячные оверрайды — PostgreSQL); (3) серверным расчётом серий
InvestorMonth[] + AnalyticsSeries + AsideMetrics по объекту недвижимости с параметрами субъекта (юр/физ
лицо, УСН-6/УСН-15/ОСНО); (4) admin-инструментами (глобальный дефолт, типы объектов недвижимости, помесячные
оверрайды, investor-preview, growth-chart) и контрактом PDF-отчёта «Скачать расчёт» (реализация отложена).
| Поле | Значение |
|---|
| Статус | draft |
| Owning team | Backend PIN (инвест-аналитика) |
| Domain | Инвест-аналитика (каталог и деньги) |
| Data classification | Internal (модель/конфигурация) + Public (цены/серии — публичны в каталоге); PII нет |
| Runtime criticality | medium (страница аналитики объекта недвижимости — ключевая для конверсии, но не блокирует сделки) |
Ownership
| Artifact | Owner/Rule |
|---|
| PostgreSQL schema | invest_analytics |
| ClickHouse database | pin_analytics (только property_object_price_history_facts; другие применения CH — через ADR, ADR-0056/0057) |
| API areas | public (история цен, серии инвестора), admin (модель/типы/оверрайды/preview — за границей ADR-0053), internal (нет в v1) |
| Owned entities | PropertyObjectPriceHistoryFact (CH), EconomicModelConfig, PropertyObjectTypeConfig, PropertyObjectTypeMonthOverride (PG); расчётные модели InvestorMonth/AnalyticsSeries/AsideMetrics (не хранятся) |
| Produced events | pin.invest-analytics.property-object-metrics.v1 (events/property-object-metrics.md), pin.invest-analytics.model-changed.v1 (events/model-changed.md) |
| Consumed events | pin.chessboard.property-object-price-changed.v1; pin.invest-analytics.model-changed.v1 (self — internal recompute job) |
| Search indexes | нет |
| Analytics facts | domain/price-history-fact.md |
Ответственность И Границы
- Что сервис делает: приём и вечное append-only хранение изменений цен объектов недвижимости; API рядов цены
по объекту недвижимости (day/month/year,
growthPercent = рост от первой к последней точке диапазона);
разрешение трёхуровневой экономической модели и серверный расчёт серий доходности по объекту недвижимости и
по произвольным параметрам (admin-preview); admin CRUD модели; контракт PDF-отчёта расчёта.
- Что сервис не делает: не является source of truth текущей цены (это
chessboard); не считает графики
платежей программ покупки (это crm/booking — ипотечные ставка/срок только читаются как вход
CreditPayment); не хранит результаты расчёта; не отдаёт статистику просмотров (это dev-stats);
не управляет каталожными оверрайдами цены (это catalog).
- Source of truth: лента изменений цен (CH), экономическая модель всех трёх уровней (PG).
- Реплики/projections: реплик чужих данных нет — параметры объекта недвижимости (цена, площадь,
GroupId,
срок сдачи) запрашиваются у chessboard в момент расчёта; измерения факта (Developer/Project/
Building/LayoutGroup/Layout) приходят снапшотом в payload события цены.
Зависимости
| Зависимость | Тип | Зачем Нужна | Failure mode |
|---|
Kafka (pin.chessboard.property-object-price-changed.v1) | queue | Поступление изменений цен | Лаг ленты цен: график отстаёт, API работает по накопленному; consumer догоняет по offset |
chessboard | service | Параметры объекта недвижимости для расчёта серий (BasePrice, Area, PricePerMeter, GroupId, DeliveryDate) | Расчёт серий недоступен → 503/502 на запрос; история цен продолжает работать |
crm (purchase-config) | service | Ставка/срок ипотеки для CreditPayment | Fallback на дефолты конфигурации сервиса (константы 21%/240 мес) |
| PostgreSQL | db | Экономическая модель (3 уровня) | Расчёт серий и admin API недоступны |
| ClickHouse | db | История цен | График/growthPercent недоступны; расчёт серий работает (историческая часть price-per-meter — synthetic-fallback) |
identity | service | Аутентификация/permissions (public — токен или реф-cookie readOnly; admin — ADR-0053) | Отказ авторизации → 401/403 |
Проекты
src/services/invest-analytics/
InvestAnalytics.Api/ # public + admin endpoints
InvestAnalytics.Application/ # запросы расчёта, команды admin-модели
InvestAnalytics.Domain/ # EconomicModelConfig, PropertyObjectTypeConfig(+MonthOverride), расчётный движок
InvestAnalytics.Infrastructure/ # PostgreSQL, ClickHouse, клиенты chessboard/crm
InvestAnalytics.Background/ # consumer изменения цены (batch ingestion) + пересчёт метрик и публикация property-object-metrics/model-changed; recompute job по model-changed
InvestAnalytics.Migrator/ # миграции PostgreSQL и ClickHouse
InvestAnalytics.Contracts/ # DTO серий доходности
Основные Сущности
ER-обзор
API
Карта областей:
- Public (actor: пользователь с токеном или реф-гость readOnly; permission
invest-analytics.price-history.view / invest-analytics.series.view):
- Admin (admin API, ADR-0053; permissions
invest-analytics.model-config.*,
invest-analytics.property-object-types.*):
- Internal: не применимо (v1);
- Callbacks: не применимо.
Events И Consumers
| Type | Name | Topic | Назначение | Link |
|---|
| Consumed | PropertyObjectPriceChangedKafkaEvent | pin.chessboard.property-object-price-changed.v1 | Append факта цены в ClickHouse (batch, идемпотентно по FactId) + триггер пересчёта метрик | consumers/chessboard-price-changed.md; контракт события — docs/06-services/chessboard/events/property-object-price-changed.md |
| Consumed (self) | EconomicModelChangedKafkaEvent | pin.invest-analytics.model-changed.v1 | Internal recompute job: пересчёт метрик затронутых объектов недвижимости → переиздание property-object-metrics.v1 | events/model-changed.md |
| Produced | PropertyObjectMetricsUpdatedKafkaEvent | pin.invest-analytics.property-object-metrics.v1 | Snapshot витринных метрик доходности объекта недвижимости (окупаемость/надёжность/рост/аренда/рейтинг) | events/property-object-metrics.md |
| Produced | EconomicModelChangedKafkaEvent | pin.invest-analytics.model-changed.v1 | Инвалидация при правке эконом-модели (scope Global/PropertyObjectType) → пересчёт метрик | events/model-changed.md |
Background-джобы: backfill первичной загрузки цен из Talent Chessboard —
jobs/price-history-backfill.md (будущий док).
Решение о канале синка цен из Talent (отдельного consumer-а нет — всё через chessboard) —
consumers/talent-price-sync.md.
Интеграционные Потоки И Согласованность
Сервис не держит реплик чужих данных: измерения объекта недвижимости приходят снапшотом в событии цены, а
параметры расчёта (цена/площадь/срок сдачи) запрашиваются у chessboard синхронно в момент расчёта. Ниже —
две асинхронные цепочки и синхронные вызовы при расчёте, с окнами согласованности и промежуточным состоянием
для пользователя.
Поток цен (append-only + пересчёт метрик)
chessboard при каждом изменении Price (в т.ч. синком из Talent) публикует
pin.chessboard.property-object-price-changed.v1 (async, key = PropertyObjectId).
- Ingest-consumer добавляет append-only факт в ленту ClickHouse (батч ≤ 1000 строк / 5 с; freshness
ленты ≤ 5 мин). PG не пишется; интеграционных событий не публикует.
- Отдельный recompute-компонент реагирует на то же изменение цены → пересчитывает витринные метрики
объекта и публикует
pin.invest-analytics.property-object-metrics.v1 (async, key = PropertyObjectId,
full snapshot) → catalog денормализует бейджи карточки (SLO ≤ 15 мин).
Промежуточное состояние для пользователя: график истории цен отстаёт от Talent на freshness ленты (≤ 5 мин);
бейджи доходности в каталоге — на окно пересчёта (< 15 мин); а расчёт серий (investor-series) всегда
считается «на лету» по данным chessboard в момент запроса и лагом ленты не ограничен (историческая часть
price-per-meter при отставании ленты мягко деградирует в синтетику).
Поток правки экономической модели (инвалидация → пересчёт)
- Admin-правка (
PUT model-config / upsert-delete типа / replace-all оверрайдов) атомарно с аудит-событием
публикует pin.invest-analytics.model-changed.v1 (async, key = ChangeScope, несёт scope инвалидации,
а не значения).
- Internal recompute job (self-consumer) с монотонным guard по (
ChangeScope, GroupId, Version)
пересчитывает затронутые объекты (Global — все; PropertyObjectType — по GroupId) и переиздаёт
property-object-metrics.v1 → catalog.
Промежуточное состояние: расчёт серий и admin-preview отражают новую модель немедленно (кэша нет,
считается на каждый запрос); бейджи каталога подтягиваются в окне пересчёта (< 15 мин). Порядок:
устаревшая по Version инвалидация того же scope пропускается (idempotent skip).
Синхронные вызовы при расчёте серии
investor-series (и отложенный PDF-отчёт) синхронно обращаются к: chessboard (параметры аналитики
объекта — timeout 2 с, 1 retry; 404 → PROPERTY_OBJECT_NOT_FOUND, недоступность после retry → 503);
crm purchase-config (ставка/срок — timeout 2 с, недоступность → fallback-константы 21 %/240 мес, не
ошибка); ClickHouse (история цены — недоступность/< 2 фактов → синтетика, IsSyntheticHistory = true,
не ошибка). Admin-preview этих трёх зависимостей не вызывает — параметры приходят из тела.
Кросс-сервисные контракты (ожидания и гарантии)
| Направление | Контракт | Ключ/версия | Поведение при отсутствии/устаревании |
|---|
Ожидает от chessboard (событие) | снапшот измерений DeveloperId/ProjectId/BuildingId/LayoutGroupId/LayoutId/AreaTotal в payload цены | key PropertyObjectId, v1 | нет снапшота → строка факта отбраковывается (DLQ); реплика измерений принципиально не заводится |
Ожидает от chessboard (sync) | параметры аналитики BasePrice/AreaTotal/PricePerMeter/GroupId/DeliveryDate | — | chessboard — источник истины; расчёт всегда по актуальному ответу, кэша параметров нет |
Ожидает от crm (sync) | ставка/срок ипотеки для CreditPayment | — | fallback-константы 21 %/240 мес (деградация) |
Предоставляет catalog | property-object-metrics.v1 — полный snapshot метрик объекта | key PropertyObjectId, 3 партиции, v1 | snapshot-семантика: re-read топика безопасен; null-поле очищает сырое значение витрины |
| Предоставляет себе (self) | model-changed.v1 — scope инвалидации | key ChangeScope, 1 партиция, v1 | коэффициенты в payload не уходят — подписчик перечитывает модель |
Принятые Решения
| # | Решение | Обоснование |
|---|
| 1 | История цен — ClickHouse ReplacingMergeTree, PARTITION toYYYYMM(EventTime), ORDER (PropertyObjectId, EventTime, FactId), без TTL | Требование append-only; вечное хранение — график за жизненный цикл + портфель; объём тривиален (обоснование в domain/price-history-fact.md) |
| 2 | Без materialized views/rollup-ов для цен | Запросы всегда по одному объекту недвижимости (сотни строк) — simplicity-first ADR-0056; отличие от dev-stats (ADR-0057) |
| 3 | Расчёт серий — серверный, на каждый запрос, без кэша и без персиста | Детерминированный дешёвый цикл ≤ 361 итерации; ADR-0056 |
| 4 | Ставки налогов — константы кода, а не конфигурация | Платформенные налоговые поля модели легаси (domain/tax-entity-params.md) |
| 5 | Измерения факта — снапшотом в payload события chessboard | Не заводить реплику измерений (в отличие от dev-stats): консюмеру цен достаточно события |
| 6 | Историческая часть графика price-per-meter — из реальных фактов ClickHouse; синтетическая экстраполяция остаётся fallback-ом (< 2 фактов по объекту недвижимости) | Честный график при наличии данных, мягкая деградация при их отсутствии |
| 7 | PDF-отчёт «Скачать расчёт» — контракт сейчас, реализация отложена | Генерация — отчётным модулем платформы |
| 8 | Wire-контракт серий — camelCase, ключи gross-profit/ebitda/price-per-meter | Единый стабильный контракт фронта каталога |
Runtime И Эксплуатация
| Область | Требование |
|---|
| Health checks | live/ready (Api, Background); ready проверяет PG, ClickHouse, Kafka |
| SLO/SLA | API p95 ≤ 300 мс (расчёт серий), ≤ 200 мс (история цен); свежесть фактов ≤ 5 мин |
| Dashboard | борд контекста: ingest lag, RPS/latency методов, ошибки консюмера |
| Alerts | рост invest_analytics_price_ingest_lag_seconds > 15 мин; ошибки batch-вставки CH; 5xx public API |
| Runbooks | переигрывание консюмера с offset (вставка идемпотентна); повторный backfill диапазона PropertyObjectId |
| Feature flags | invest-analytics.synthetic-history-fallback (вкл/выкл синтетическую историю) |
Безопасность
- Основные actor types: гость по реф-ссылке (readOnly страница аналитики), инвестор, агент/менеджер
(та же public-выдача), администратор платформы (admin API, отдельная identity — ADR-0053).
- Permission model:
invest-analytics.price-history.view, invest-analytics.series.view (public, выдаются
ролям каталога), invest-analytics.model-config.view/manage, invest-analytics.property-object-types.view/manage
(admin) — каталог permissions в docs/06-services/identity/domain/permissions.md.
- Sensitive data classes: PII нет; экономическая модель — Internal (не раскрывается public API: наружу
уходят только вычисленные серии).
- Audit: правки модели — actor-stamping +
Version + доменные события (аудит-лог admin API).
- Tenant isolation rule: PG-конфигурация — платформенная, не tenanted (ADR-0052 п.3); CH-факт несёт
TenantId = DeveloperId, но публичное чтение не фильтруется по tenant (цены публичны в каталоге);
запись — только consumer.
Известные Ограничения И Assumptions
- Payload
pin.chessboard.property-object-price-changed.v1 обязан нести снапшот измерений (DeveloperId, ProjectId,
BuildingId, LayoutGroupId, LayoutId, AreaTotal) — требование к ТЗ chessboard.
- Ипотечные параметры (
CreditPayment) читаются из purchase-config crm; до его готовности — константы
21%/240 мес.
- Ставка ОСНО 43% — модельное допущение владельцев платформы, не налоговый расчёт.
- Поле
CumulativeEbitda дублирует Ebitda — сохранено для совместимости контракта.
- PDF-отчёт: только контракт; генерация — отложена.
- Оверрайды заданы на уровне типа объекта недвижимости (LayoutGroup), не отдельного объекта; per-lot
оверрайды при необходимости — отдельным решением.
Связанные Документы
- Домены: domain/price-history-fact.md,
domain/economic-model-config.md,
domain/property-object-type-config.md,
domain/property-object-type-month-override.md,
domain/investor-series-models.md,
domain/tax-entity-params.md.
- Обзор:
../../00-overview/technical-assignment.md, ../../00-overview/service-boundaries.md.
- ADR: ADR-0052 (tenancy), ADR-0053 (admin identity), ADR-0056 (simplicity-first/границы CH),
ADR-0057 (паттерн ClickHouse facts).
- Смежные контексты:
../chessboard/ (события цен, параметры объекта недвижимости — будущие доки), ../catalog/
(каталожные оверрайды цены), ../crm/ (purchase-config), ../investor-owner/ (потребитель истории цен).