POST /api/v1/property-objects/{propertyObjectId}/analytics-report — PDF-отчёт «Скачать расчёт» (реализация отложена)
Статус реализации: отложена. Настоящим документом фиксируется контракт (route, вход, формат
ответа, ошибки); до реализации endpoint отвечает 501 INVEST_ANALYTICS_REPORT_NOT_IMPLEMENTED.
Формирование файла — средствами отчётного модуля платформы; выбор PDF-библиотеки — при реализации,
отдельным решением.
Назначение
Скачивание PDF-файла с расчётом доходности объекта недвижимости: параметры расчёта, сводка выбранного
месяца (AsideMetrics), три графика и помесячная таблица (InvestorMonth[]) — тот же расчёт, что
../public/get-property-object-investor-series.md,
отрендеренный в документ. Вызывается аутентифицированным пользователем портала (инвестор/агент/менеджер);
реф-гость отчёт скачивать не может.
| Поле | Значение |
|---|
| Сервис/контекст | invest-analytics |
| API area | developers (аутентифицированный портал; реф-гость исключён) |
| Feature group | investor-series |
| Статус | draft (контракт зафиксирован; реализация отложена) |
| Документ | docs/06-services/invest-analytics/api/developers/create-analytics-report.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Bearer JWT) — только аутентифицированный; реф-гость readOnly скачивать отчёт не может → 403 |
| Auth policy / Permissions | authenticated + permission invest-analytics.series.view (отчёт — проекция серии, отдельная permission не заводится) |
| Scope (RBAC) | платформа |
| Tenant isolation | не применяется (вход платформенный); request не tenanted |
| Audit | факт выгрузки — лог analytics-report-downloaded (userId, propertyObjectId); audit-записи БД нет |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST (генерация файла с телом параметров; не safe) |
| Route | /api/v1/property-objects/{propertyObjectId}/analytics-report |
| Success status | 200, Content-Type: application/pdf, Content-Disposition: attachment; filename="pin-analytics-{propertyObjectId}-{yyyyMMdd}.pdf" |
| Idempotency header | не применяется (генерация детерминирована — INV-CALC-6, побочных эффектов в данных нет) |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
PropertyObjectId | route | Guid | Да | != Guid.Empty; существование — через chessboard (как в серии) | — |
Entity | body entity | EntityKind | Нет | wire legal / individual, default legal; unknown → ValidationError | — |
Tax | body tax | TaxRegime | Нет | wire usn-6 / usn-15 / osno, default usn-6 | — |
MonthIndex | body monthIndex | int? | Нет | 0..360; выбранный слайдером месяц для секции сводки; default — последний месяц серии (HorizonMonths); значение за пределами фактической серии → ValidationError | — |
UserId | identity claims (sub) | Guid | Да | валидный JWT — 401 до расчёта | для лога выгрузки |
Модель ответа AnalyticsReportFileResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
FileName | string | Да | pin-analytics-{propertyObjectId}-{yyyyMMdd}.pdf | — | Имя файла (в Content-Disposition). |
ContentType | string | Да | константа application/pdf | — | MIME-тип. |
Content | byte[] | Да | рендер отчётного модуля | — | Бинарное содержимое PDF (отдаётся как файл-стрим). |
Состав PDF (контракт содержимого)
| Секция | Содержимое | Источник |
|---|
| Шапка | Название/номер объекта недвижимости, проект, дата формирования, параметры (entity, tax — человекочитаемо) | chessboard analytics-params + вход |
| Сводка выбранного месяца | Все 21 поле AsideMetrics выбранного MonthIndex (прогнозная стоимость, ROI, EBITDA, налоги, ипотечный платёж…) | MetricsByMonth[MonthIndex] |
| Графики | Три серии gross-profit / ebitda / price-per-meter (растровый рендер) | AnalyticsSeries |
| Помесячная таблица | InvestorMonth[] — 18 колонок по месяцам горизонта | Months |
| Дисклеймер | «Расчёт не является налоговой/инвестиционной консультацией; ставки — модельные допущения платформы» (../../domain/tax-entity-params.md, Безопасность) | константа |
Алгоритм
Шаги описывают целевую (отложенную) реализацию; до неё выполняются только шаги 1–2, затем возвращается
ошибка INVEST_ANALYTICS_REPORT_NOT_IMPLEMENTED → 501.
- Контекст и доступ. Actor =
User по Bearer JWT (реф-гость → 403 FORBIDDEN); только
аутентифицированный пользователь портала.
- Проверка входа.
PropertyObjectId != Guid.Empty; entity/tax — как в
../public/get-property-object-investor-series.md
(unknown → INVEST_ANALYTICS_INVALID_TAX_PARAMS); MonthIndex ∈ 0..360. Существование объекта
недвижимости и допустимость состояния — при загрузке (chessboard, как в серии).
- Что читаем. Полностью переиспользует загрузку алгоритма серии (объект недвижимости из chessboard,
платформенная модель, тип объекта, помесячные оверрайды, ипотека crm с fallback 21 %/240, история
ClickHouse с synthetic-fallback) — общий расчётный компонент.
- Правила и расчёт. Тот же детерминированный расчёт (каскад коэффициентов → помесячный цикл →
AsideMetrics → серии) → серии доходности. Дополнительно:
MonthIndex = min(MonthIndex ?? HorizonMonths, HorizonMonths); рендер PDF: маппинг секций по таблице
«Состав PDF».
- Результат. Файл-стрим
application/pdf (Content-Disposition: attachment); ошибки → ProblemDetails
(JSON). До реализации — 501. Файл не сохраняется (генерация на лету; персист/повторное скачивание по
ссылке — вне v1).
Диаграмма
Побочные эффекты
Интеграционных и доменных событий метод не публикует.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
INVEST_ANALYTICS_REPORT_NOT_IMPLEMENTED | 501 | Реализация отложена (текущее поведение всех запросов после валидации) | Формирование отчёта пока недоступно | no retry |
ValidationError | 400 | Невалидные monthIndex/тело | Ошибка валидации запроса | no retry |
INVEST_ANALYTICS_INVALID_TAX_PARAMS | 400 | Неизвестное entity/tax | Недопустимые параметры расчёта | no retry |
UNAUTHORIZED | 401 | Нет/просрочен JWT | Требуется авторизация | no retry |
FORBIDDEN | 403 | Реф-гость (readOnly) | Скачивание доступно после входа | no retry |
INVEST_ANALYTICS_PROPERTY_OBJECT_NOT_FOUND | 404 | Объект недвижимости не найден в chessboard | Объект не найден | no retry |
INVEST_ANALYTICS_PROPERTY_OBJECT_SOURCE_UNAVAILABLE | 503 | chessboard недоступен | Параметры объекта временно недоступны | retry с backoff |
Совместимость и наблюдаемость
- Контракт зафиксирован до реализации: route/вход/формат не меняются при включении генерации (замена
501 на 200 — не breaking). Состав секций PDF — additive (новые секции добавляются без версии);
смена формата файла (PDF → иной) — breaking.
- Logs:
analytics-report-downloaded (userId, propertyObjectId, entity, tax, monthIndex, размер файла, elapsed);
до реализации — analytics-report-requested-not-implemented (счётчик спроса на фичу). Metrics:
invest_analytics_report_requests_total{implemented}, duration рендера. Traces — сквозной trace через
traceparent.
Acceptance Criteria
- Given реализация отложена, When аутентифицированный пользователь шлёт валидный запрос, Then
после прохождения валидации возвращается
501 INVEST_ANALYTICS_REPORT_NOT_IMPLEMENTED; фиксируется
счётчик спроса analytics-report-requested-not-implemented.
- Given актор — реф-гость (readOnly, без Bearer JWT), When POST, Then
403 FORBIDDEN (до
генерации; скачивание доступно только после входа).
- Given неизвестное значение
entity/tax, When POST, Then 400 INVEST_ANALYTICS_INVALID_TAX_PARAMS.
- Given
monthIndex вне 0..360 или за пределами фактической серии, When POST, Then 400 ValidationError.
- Given объект недвижимости не найден в
chessboard, When POST, Then 404 INVEST_ANALYTICS_PROPERTY_OBJECT_NOT_FOUND; chessboard недоступен → 503 INVEST_ANALYTICS_PROPERTY_OBJECT_SOURCE_UNAVAILABLE.
- Given целевая (включённая) реализация и существующий объект, When аутентифицированный пользователь
шлёт POST, Then
200 application/pdf (Content-Disposition: attachment) с секциями по таблице «Состав
PDF»; route/вход/формат не меняются (замена 501 → 200 — не breaking), файл не персистится.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы