UC-PRC-002. Расчёт доходности объекта недвижимости с выбором субъекта (юр/физ) и налогового режима
Назначение
Калькулятор доходности на странице /lots/[id]/analytics: пользователь выбирает тип субъекта (юр/физ лицо)
и налоговый режим (УСН-6 / УСН-15 / ОСНО), а система серверно строит InvestorMonth[0..HorizonMonths] +
AnalyticsSeries (gross-profit/ebitda/price-per-meter) + AsideMetrics для каждого месяца. Модели и
формулы — ../domain/investor-series-models.md. Контекст invest-analytics.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-PRC-002 |
| Контекст | invest-analytics |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Инвестор / Агент / Менеджер ПИН (по токену) или Гость по реферальной ссылке (реф-cookie): страница readOnly, но переключатели entity/tax доступны — расчёт read-only и не привилегирован (../domain/tax-entity-params.md) |
| Permissions / scope | invest-analytics.series.view (public, выдаётся ролям каталога; реф-гость — тот же гейт токен/реф-cookie, что и /lots) |
| Tenant | не применяется: коэффициенты платформенные (ADR-0052 п.3), результат публичен |
| Record-level | не применяется; PII отсутствует — параметры entity/tax не персистятся |
Предусловие
chessboardinternal API доступен и отдаёт параметры объекта недвижимости (BasePrice— эффективная цена с учётом каталожных оверрайдов,Area,PricePerMeter,GroupId,DeliveryDate— строка срока сдачи корпуса).- Seed-миграция создала singleton
invest_analytics.model_config(../domain/economic-model-config.md); при отсутствии строки расчёт использует hard-coded дефолты (fallback последнего уровня). - Опционально:
PropertyObjectTypeConfigпоGroupIdобъекта недвижимости и его помесячные оверрайды настроены админом (UC-PRC-003, UC-PRC-004). - Пользователь прошёл гейт каталога (JWT или реф-cookie).
Триггер
| Поле | Значение |
|---|---|
| Источник | UI (открытие вкладки / переключатель) + API call |
| Триггерящий элемент | Страница /lots/[id]/analytics: первичная загрузка (дефолты entity=legal, tax=usn-6) и каждое переключение «юр/физ лицо» / «УСН-6 / УСН-15 / ОСНО»; слайдер месяца запросов не порождает — фронт листает готовый MetricsByMonth[i] |
Основной Поток
-
Пользователь открывает вкладку аналитики; фронт вызывает
GET property-objects/{propertyObjectId}/investor-series?entity=legal&tax=usn-6(дефолты). -
Система проверяет вход:
PropertyObjectIdне пустой;entity ∈ {legal, individual},tax ∈ {usn-6, usn-15, osno}; отсутствующий параметр → дефолт (legal,usn-6); неизвестное значение → ошибкаINVEST_ANALYTICS_INVALID_TAX_PARAMS(HTTP 400), молчаливый откат к дефолту запрещён (../domain/tax-entity-params.md);Individual+ любойtax— не ошибка:taxпринимается и игнорируется в ставке.
-
Система собирает входы расчёта (порядок фиксирован):
- а) объект недвижимости: m2m-вызов
chessboard(../../chessboard/api/public/property-objects/get-property-object.md) →BasePrice,Area,PricePerMeter,GroupId,DeliveryDate; объект не найден →INVEST_ANALYTICS_PROPERTY_OBJECT_NOT_FOUND(404, A2); сервис недоступен → 503 (A3); - б) singleton
EconomicModelConfigпо well-known Id — при отсутствии строки применяются дефолты кода (таблица дефолтов в ../domain/economic-model-config.md); - в)
PropertyObjectTypeConfigпоGroupIdобъекта недвижимости — может быть не настроен (отсутствие допустимо); - г) помесячные оверрайды типа по
GroupId— полный список (единицы–десятки строк), проекция вMonthOverrideValues; - д) параметры ипотеки: purchase-config
crm(MortgageAnnualRate,MortgageMonths); недоступен/не настроен → fallback-константы21/240(A5), расчёт не блокируется; - е) историческая часть серии
price-per-meter: помесячная агрегация фактов цен из ClickHouse (argMax(NewPricePerMeter, (EventTime, IngestionTime))доAnchorTime);< 2фактов или CH недоступен → synthetic-fallback (A4).
- а) объект недвижимости: m2m-вызов
-
Система выполняет расчёт (движок — ../domain/investor-series-models.md):
- разрешение эффективных коэффициентов: для каждого из 9 полей независимо
тип → модель → дефолт кода(первое not-null конечное) →InvestorCoefficients; regimeTaxRateпо (EntityKind,TaxRegime):Individual → 0.13;Legal:Usn6 → 0.06,Usn15 → 0.15,Osno → 0.43(константы модели);AnchorTime= первый день текущего месяца;DeliveryTime— парсингDeliveryDate(ISO / «N кв. YYYY» / месяц-год / год → правила дат дома; не распарсилось →null);- цикл
i = 0..HorizonMonths:marketM2,dailyRent,gainM2, налоги (ovr(i).*TaxMonthly × 12замещаетregimeTaxRateпоточечно — INV-CALC-5),monthRevenueM2(0 доDeliveryTime— INV-CALC-3),cumRevenueM2,ebitdaM2, правилоpositiveOrдля абсолютных оверрайдов (INV-CALC-2) → 18 полейInvestorMonth[i]; AnalyticsSeries:gross-profit(на м², все месяцы),ebitda(на м², только месяцы сEbitda > 0),price-per-meter= историческая часть (шаг 3.е) + прогнозround2(MarketPriceM2)отAnchorTime;MortgageCalculation(аннуитет) — одна величина на все срезы;AsideMetricsдля каждого месяца (21 поле — таблица дома, включаяRoiPerYear,Growth12mPercent,CreditPayment); приHorizonMonths < 12RoiPerYearсчитается по доступным месяцам1..min(12, HorizonMonths), аGrowth12mPercentу краевых срезов =0(нет ниi+12, ниi−12) — см. «Граничные случаи серии» дома;- финальный guard: не-конечное →
0(INV-CALC-1); длиныMonths.Count == MetricsByMonth.Count == HorizonMonths + 1(INV-CALC-4).
Расчёт ничего не персистит (без кэша и без сохранения результатов, ADR-0056).
- разрешение эффективных коэффициентов: для каждого из 9 полей независимо
-
Система возвращает
{ months[], series {gross-profit, ebitda, price-per-meter}, metricsByMonth[], isSyntheticHistory }(camelCase, дефисные ключи серий); полеCumulativeEbitdaсохранено (дубльEbitda, совместимость). -
Фронт рисует графики и правую панель
AsideMetrics(мобильный calc-block); слайдер месяца листаетmetricsByMonth[i]локально. -
Пользователь переключает «физ лицо» → фронт повторяет шаги 1–5 с
entity=individual(меняются только налоговые поля и производные:AppreciationTax,EbitdaTax,GrossProfit, ROI,TaxRatePercent).
Альтернативные Потоки
A1. Неизвестное значение entity/tax
- Ручной вызов API с
tax=ausn→ 400INVEST_ANALYTICS_INVALID_TAX_PARAMS(шаг 2). Фронт таких значений не шлёт. Сценарий завершается.
A2. Объект недвижимости не найден в chessboard
- Параметры объекта недвижимости не найдены → 404
INVEST_ANALYTICS_PROPERTY_OBJECT_NOT_FOUND; фронт показывает 404-страницу карточки. Сценарий завершается.
A3. chessboard недоступен
- m2m-вызов падает (timeout/5xx) → 503
INVEST_ANALYTICS_PROPERTY_OBJECT_SOURCE_UNAVAILABLE(расчёт по объекту недвижимости недоступен). Ретраев внутри запроса нет — фронт показывает плейсхолдер с retry. История цен (UC-PRC-001) продолжает работать независимо.
A4. ClickHouse недоступен или < 2 фактов по объекту недвижимости
- Историческая часть
price-per-meterстроится синтетикой: годовые точки01-01с 2022 по прошлый год, экстраполяция назад отmarketM2(0)факторомannualFactor = marketM2(12)/marketM2(0); пустой список, если серия короче 13 месяцев или значения ≤ 0 (формула — дом серий). Ответ помечаетсяisSyntheticHistory = true. - Feature flag
invest-analytics.synthetic-history-fallbackвыключен → историческая часть просто пустая (только прогноз), ошибки нет. Возврат в основной поток на шаг 4.
A5. Purchase-config crm недоступен / не настроен
MortgageAnnualRate = 21,MortgageMonths = 240(fallback-константы);AsideMetrics.CreditPaymentсчитается по ним. Возврат на шаг 4; деградация не сигналится пользователю (модельная величина).
A6. Singleton model_config отсутствует (до первого сохранения админом)
- Все коэффициенты — hard-coded дефолты кода (
MarketPremium 0.1,PriceGrowthMonthly 0.02,DailyRentM2 800,RentGrowthMonthly 0.01,ExpenseShare 0.4,Occupancy 0.6,HorizonMonths 60). Возврат на шаг 4. В штатном рантайме недостижимо — seed-миграция создаёт строку.
Постусловие
- Состояние системы не изменилось (read-only): PG/CH/outbox не мутированы, результат не кэширован и не сохранён.
- Пользователь видит серии и сводку, соответствующие выбранным
entity/taxи актуальной трёхуровневой модели; правки админа (UC-PRC-003/004) видны немедленно — со следующего запроса.
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
INVEST_ANALYTICS_INVALID_TAX_PARAMS | 400 | Неизвестное значение entity/tax (A1) | Исправить параметры; фронт шлёт только известные |
ValidationError | 400 | PropertyObjectId пуст | Исправить запрос |
Unauthorized | 401 | Нет JWT и нет реф-cookie | Auth-модалка (гейт каталога) |
INVEST_ANALYTICS_PROPERTY_OBJECT_NOT_FOUND | 404 | Объект недвижимости отсутствует в chessboard (A2) | 404-страница карточки |
INVEST_ANALYTICS_PROPERTY_OBJECT_SOURCE_UNAVAILABLE | 503 | chessboard internal API недоступен (A3) | Плейсхолдер + retry с backoff |
Недоступность ClickHouse (A4) и crm (A5) ошибками не являются — graceful degradation.
События
Сценарий (read-only расчёт) событий не публикует; produced-события контекста — ../events/README.md.
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Ответ детерминирован по входу; ключи camelCase, серии gross-profit/ebitda/price-per-meter, cumulativeEbitda присутствует | golden-тест ответа на фиксированном входе |
| AC2 | Переключение entity/tax меняет ставку по таблице (0.13/0.06/0.15/0.43); Individual игнорирует tax без ошибки | параметризованный тест 4 комбинаций + individual&tax=osno |
| AC3 | Months.Count == MetricsByMonth.Count == HorizonMonths + 1; до DeliveryTime аренда/EBITDA = 0 и точек ebitda нет | unit-тесты INV-CALC-3/4 |
| AC4 | Помесячные оверрайды применяются поточечно: абсолюты только при > 0 (positiveOr), налоговые × 12 замещают ставку режима | unit-тесты INV-CALC-2/5 |
| AC5 | Ни одно поле результата не NaN/Infinity при нулевых BasePrice/Area | unit-тест INV-CALC-1 (крайние входы) |
| AC6 | ≥ 2 фактов в CH → историческая часть из реальных точек, isSyntheticHistory=false; < 2 или CH down → синтетика/пусто по feature flag | интеграционный тест A4 |
| AC7 | Недоступный chessboard → 503; недоступный crm → расчёт с константами 21/240 | тесты A3/A5 |
| AC8 | Реф-гость получает расчёт и может менять entity/tax (страница readOnly не блокирует чтение) | e2e-тест реф-cookie |
| AC9 | HorizonMonths < 12: RoiPerYear по доступному хвосту 1..min(12,Horizon), Growth12mPercent краевых = 0, серия длины HorizonMonths+1, поля не NaN | unit-тест малого горизонта |
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (4): UC-PRC-001. Просмотр графика роста цены объекта недвижимости (гость / инвестор / реф-гость readOnly), UC-PRC-003. Admin: настройка экономической модели платформы + investor-preview, UC-PRC-004. Admin: настройка типа объекта недвижимости и помесячных оверрайдов, UC-PRC-005. Скачивание PDF-отчёта «Скачать расчёт» (контракт; реализация отложена)
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API метод (дом контракта): ../api/public/get-property-object-investor-series.md.
- Формулы и модели (дом): ../domain/investor-series-models.md.
- Уровни коэффициентов: ../domain/economic-model-config.md, ../domain/property-object-type-config.md, ../domain/property-object-type-month-override.md.
- Налоги: ../domain/tax-entity-params.md.
- История для
price-per-meter: ../domain/price-history-fact.md, UC-PRC-001. - Параметры объекта недвижимости: ../../chessboard/api/public/property-objects/get-property-object.md.
- Параметры ипотеки:
docs/06-services/crm/domain/purchase-program.md. - Admin-настройка модели: UC-PRC-003, UC-PRC-004; PDF-отчёт — UC-PRC-005.
- Решения: ADR-0052 (платформенные данные), ADR-0056 (без кэша/персиста).