POST /api/admin/v1/investor-preview — Расчёт серий доходности по произвольным параметрам (admin preview / growth-chart)
Назначение
Серверный расчёт помесячных серий доходности по произвольным параметрам объекта недвижимости (не по
реальному PropertyObjectId): InvestorMonth[] + AnalyticsSeries (графики gross-profit/ebitda/price-per-meter) +
AsideMetrics для каждого месяца. Питает admin-кнопку «Предпросмотр» и growth-chart — проверку эффекта
правок экономической модели (put-model-config.md). Использует тот же расчётный
движок, что и public ../public/get-property-object-investor-series.md
(UC-PRC-002), c отличиями входа: параметры объекта недвижимости приходят из тела запроса, а
chessboard/crm/ClickHouse не вызываются. Wire-модель ответа идентична public-серии. Формулы и
инварианты расчёта — дом ../../domain/investor-series-models.md.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | invest-analytics |
| API area | admin (ADR-0053) |
| Feature group | admin-economic-model |
| Статус | draft |
| Документ | docs/06-services/invest-analytics/api/admin/investor-preview.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | Operator(Admin) — администратор платформы (доступ через admin API, m2m client-credentials, клиент pin-admin-system) |
| Auth policy / Permissions | policy AdminSystemOnly; каталожное соответствие — permission invest-analytics.model-config.view (read-only расчёт доступен и без .manage) |
| Scope (RBAC) | платформа |
| Tenant isolation | не применяется — вход платформенный, request не tenanted (ADR-0052 п.3) |
| Audit | none (read-only расчёт; экономическая модель наружу не раскрывается — уходят только вычисленные серии) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Success status | 200 |
| Route | /api/admin/v1/investor-preview |
| Idempotency header | не применяется — read-only расчёт, результат не персистится; повтор детерминирован (INV-CALC-6) |
| Correlation | сквозной trace через traceparent; X-Admin-User-Id — обязателен |
Входные данные / параметры запроса
Параметры объекта недвижимости — из тела (имитируют реальный объект недвижимости). Коэффициенты —
опциональный полный набор из девяти nullable-полей: переданное значение имитирует уровень
PropertyObjectTypeConfig, не переданное разрешается из EconomicModelConfig → дефолт кода.
JSON тела — camelCase.
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
BasePrice | body basePrice | decimal | Да | >= 0, руб; конечное число | — |
Area | body area | decimal | Да | > 0, м²; конечное число | — |
PricePerMeter | body pricePerMeter | decimal | Да | > 0, руб/м²; конечное число | — |
Coefficients | body coefficients | InvestorCoefficientsInput? | Нет | 9 nullable-полей, диапазоны INV-MC2 при not null (дом economic-model-config.md) | — |
MonthOverrides | body monthOverrides | IReadOnlyList<MonthOverrideInput> | Нет (может быть пуст) | схема MonthOverrideValues: (year,month) + nullable-поля; INV-MO3 диапазоны + INV-MO5 нет дублей (year,month) (property-object-type-month-override.md) | — |
Entity | body entity | EntityKind | Нет | wire legal/individual; default legal; неизвестное → INVEST_ANALYTICS_INVALID_TAX_PARAMS (tax-entity-params.md) | — |
Tax | body tax | TaxRegime | Нет | wire usn-6/usn-15/osno; default usn-6; при Entity = Individual принимается и игнорируется в ставке (не ошибка) | — |
AdminUserId | header X-Admin-User-Id | Guid | Да | != Guid.Empty | наблюдаемость (не PII) |
InvestorCoefficientsInput — девять nullable-полей marketPremium?, priceGrowthMonthly?,
dailyRentM2?, rentGrowthMonthly?, expenseShare?, occupancy?, horizonMonths?,
appreciationTaxMonthly?, ebitdaTaxMonthly? (семантика/единицы/диапазоны — дом
economic-model-config.md). MonthOverrideInput — проекция
MonthOverrideValues один-в-один (investor-series-models.md).
Модель ответа InvestorSeriesResponse
Та же wire-модель, что у ../public/get-property-object-investor-series.md
(дом полей — там; JSON camelCase, ключи серий дословно gross-profit/ebitda/price-per-meter). Здесь —
только дельта значений admin-режима:
| Поле | Тип | Значение в admin-preview | Описание |
|---|---|---|---|
months | IReadOnlyList<InvestorMonthModel> | всегда полный список (HorizonMonths + 1 строк, INV-CALC-4) | Admin всегда видит помесячную детализацию (урезания реф-гостя нет). |
series | AnalyticsSeriesModel | три серии | Growth-chart рисует gross-profit/ebitda/price-per-meter. |
metricsByMonth | IReadOnlyList<AsideMetricsModel> | все поля заполнены | Налоговая детализация и creditPayment видны (admin — полный расчёт). |
readOnly | bool | всегда false | Admin — полный расчёт (не урезанный режим реф-гостя). |
isSyntheticHistory | bool | всегда true | Реального PropertyObjectId нет — историческая часть price-per-meter синтетическая (ClickHouse не вызывается). |
Алгоритм
Расчётный движок — тот же, что в UC-PRC-002; отличия — на шаге чтения (единственное чтение — платформенная модель).
- Контекст и доступ. Администратор платформы через admin API (policy
AdminSystemOnly, право.view); scope — платформа;X-Admin-User-Id→ контекст наблюдаемости. - Проверка входа.
BasePrice >= 0,Area > 0,PricePerMeter > 0; переданные коэффициенты — INV-MC2; оверрайды — INV-MO3 (диапазоны) + INV-MO5 (нет дублей(year,month));entity/tax— неизвестное значение →INVEST_ANALYTICS_INVALID_TAX_PARAMS(400, молчаливый откат к дефолту запрещён); отсутствующиеentity/tax→ дефолтыlegal/usn-6. Существование сущностей по id не проверяется (реального объекта недвижимости нет). - Что читаем. Единственное чтение — платформенная модель
EconomicModelConfigпо well-known-id, только для НЕ переданных коэффициентов; при отсутствии строки — дефолты кода (таблица в economic-model-config.md). УровеньPropertyObjectTypeConfigв preview не выбирается (переданные коэффициенты имитируют этот уровень).chessboard/crm/ClickHouse не вызываются: параметры объекта недвижимости — из тела; ипотека = константы21% /240мес; историческая частьprice-per-meterсинтетическая,IsSyntheticHistory = true. - Бизнес-правила и расчёт (детерминированный, чистый, без I/O — INV-CALC-6):
- Каскад коэффициентов (per-field): для каждого из девяти полей — переданное значение →
EconomicModelConfig.поле→ дефолт кода. - Даты:
AnchorTime= первый день текущего месяца сервера (yyyy-MM-01);DeliveryTime = null→ аренда/EBITDA считаются с месяца 0 (inOperation(i)всегда true). - Налог режима:
regimeTaxRateпо (Entity,Tax) —Individual→0.13;Legal:Usn6→0.06,Usn15→0.15,Osno→0.43(tax-entity-params.md). - Словарь оверрайдов:
overrideMap[(Year, Month)] → MonthOverrideValuesиз тела. - Помесячный цикл
i = 0..HorizonMonths(HorizonMonths + 1итераций) — формулы таблицыInvestorMonthдома моделей (правилаpositiveOr/??/× 12; INV-CALC-2/3/5). - Ипотека: аннуитет (
S = BasePrice,r = 21/100/12,Months = 240) — одна величина для всех срезов. - AsideMetrics для каждого
i— 21 поле по таблице дома (все заполнены,readOnly = false). - AnalyticsSeries:
gross-profit(все месяцы),ebitda(только точки> 0),price-per-meter= синтетическая история + прогнозныеround2(marketM2(i)). - Финальный guard INV-CALC-1: любое не-конечное число результата →
0.
- Каскад коэффициентов (per-field): для каждого из девяти полей — переданное значение →
- Результат. Wire-модель серии (camelCase, ключи серий
gross-profit/ebitda/price-per-meter);readOnly = false,isSyntheticHistory = true. Результат не персистится, повтор детерминирован.
Диаграмма
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Area <= 0 / BasePrice < 0 / PricePerMeter <= 0; коэффициенты вне INV-MC2; оверрайды вне INV-MO3; дубль (year,month) INV-MO5 | Ошибка валидации запроса | no retry |
INVEST_ANALYTICS_INVALID_TAX_PARAMS | 400 | Неизвестное значение entity/tax | Недопустимые параметры расчёта | no retry |
UNAUTHORIZED | 401 | Нет m2m-авторизации admin API | Требуется авторизация admin API | no retry |
FORBIDDEN | 403 | Клиент не pin-admin-system / нет permission .view | Доступ запрещён | no retry |
Совместимость и наблюдаемость
- Контракт ответа — тот же, что у public
../public/get-property-object-investor-series.md;
единственное отличие — вход = произвольные параметры, а не
PropertyObjectId. Additive-поля — без breaking; удаление/переименование/смена типа — breaking (migration + rollout). Unknown enum на входе → 400. - Logs:
admin-investor-preview-calculated(adminUserId,entity,tax,horizon,elapsed; PII нет). Metrics:invest_analytics_admin_operations_total{operation="investor-preview"}, latency histogram. Traces: span приёма → span расчёта; correlation через сквозной tracetraceparent.
Acceptance Criteria
- Given валидные
basePrice/area/pricePerMeterи полный набор коэффициентов в теле, When администратор шлёт POST, Then200сmonthsизHorizonMonths + 1строк,readOnly = false,isSyntheticHistory = true; переданные коэффициенты использованы напрямую (уровеньPropertyObjectTypeConfigне выбирается). - Given коэффициенты в теле не переданы, When POST, Then каждое поле разрешается по каскаду
EconomicModelConfig→ дефолт кода;chessboard/crm/ClickHouse не вызываются, ипотека = константы21%/240мес. - Given неизвестное значение
entity/tax, When POST, Then400 INVEST_ANALYTICS_INVALID_TAX_PARAMS(молчаливый откат к дефолту запрещён). - Given
entity/taxотсутствуют, When POST, Then приняты дефолтыlegal/usn-6(не ошибка); приentity = individualпереданныйtaxигнорируется в ставке (ставка0.13), не ошибка. - Given коэффициент вне INV-MC2 или оверрайд вне INV-MO3 или дубль
(year, month)(INV-MO5) илиarea <= 0/basePrice < 0/pricePerMeter <= 0, When POST, Then400 ValidationError. - Given идентичное тело запроса послано дважды, When POST, Then результат побайтово одинаков (детерминизм INV-CALC-6; ничего не персистится).
- Given любое промежуточное не-конечное число в расчёте, When POST, Then оно заменяется на
0(финальный guard INV-CALC-1).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (2): Налоговые параметры расчёта: EntityKind, TaxRegime, Расчётные модели: InvestorMonth, AnalyticsSeries, AsideMetrics
- API (2): GET /api/v1/property-objects/{propertyObjectId}/investor-series — Серии доходности объекта недвижимости, PUT /api/admin/v1/model-config — Обновление платформенной экономической модели (admin)
- Use Cases (1): UC-PRC-003. Admin: настройка экономической модели платформы + investor-preview
- index.md (1): InvestAnalytics Service
- registries (1): Реестр API-методов PIN
Связанные документы
- Дом формул/входа/инвариантов: ../../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.
- Public-аналог по реальному объекту недвижимости: ../public/get-property-object-investor-series.md.
- Правки модели, эффект которых проверяет preview: put-model-config.md.