Предметная областьАдресуемые данные (черновик)

Черновик спецификации: адресуемые данные проекта для документов

Актуально на: 2026-07-09

Статус: draft для обсуждения.

1. Задача

Нужно уметь получить из проекта любое пользовательское или расчетное значение и вставить его в Word, PowerPoint или другой документ по специальному символическому обозначению.

Документ должен обновляться без ручной правки цифр: если проект изменился, генератор заново разрешает все символы и подставляет актуальные значения.

2. Текущее состояние в проекте

2.1. Что уже хранится в БД

В Prisma уже есть нормализованные таблицы для большой части вводимых данных:

ОбластьТаблицы
ПроектProject, ProjectInfo, ProjectSettings
Продукты и продажиProjectProduct, ProjectProductSalesVolume
НалогиProjectTaxSettings, ProjectTaxItem, ProjectTaxRatePeriod, ProjectTaxManualAmount, ProjectTaxRegimeLimit
ПерсоналProjectPersonnelSettings, ProjectPersonnelEmployee, ProjectPersonnelEmployeePayoutItem, ProjectPersonnelBonus
Общие расходыProjectGeneralExpenseSettings, ProjectGeneralExpense, ProjectGeneralExpenseScheduleItem
Акционерный капиталProjectEquityParticipant, ProjectEquityContributionItem, ProjectEquityPayoutItem
Legacy/backfillProjectWorkspaceState.state

Важно: ProjectWorkspaceState.state остается fallback/legacy-источником. Новая система подстановок не должна напрямую зависеть от UI-state, но должна уметь читать legacy-поля, пока не все области нормализованы.

2.2. Что уже рассчитывается в коде

Расчетные величины сейчас собираются не в БД, а в моделях:

ОбластьИсточник расчета
Cash-flowlib/workspace/use-results-cashflow-model.ts, lib/workspace/results-cashflow-model.ts
P&Llib/workspace/equity-model.ts через buildProfitLossRows
Налогиlib/workspace/tax-model.ts
Займыlib/workspace/results-cashflow-model.ts
Капитал участниковlib/workspace/equity-model.ts
Эффективность инвестицийlib/workspace/investment-efficiency-model.ts

Вывод: для документов нужен единый расчетный слой, который повторяет серверно тот же pipeline, что и экран workspace.

3. Базовый принцип

Любое значение должно иметь стабильный ключ:

domain.metric[selector].property@period | format

Для документов предлагается использовать двойные фигурные скобки:

{{ pf:project.title }}
{{ pf:cashflow.balance_end@2026-08 | money }}
{{ pf:investment.npv[mode=real,scope=before_financing] | money }}

Префикс pf: нужен, чтобы отличать наши токены от обычного текста и от возможных полей Word.

4. Периоды

Значения можно запрашивать тремя способами:

ВидПримерПравило
По индексу периода@p71-based номер периода проекта
По месяцу проекта@2026-08календарный месяц YYYY-MM
По диапазону@2026-02..2026-08агрегат за диапазон
По всем периодам@allагрегат за весь проект
На последний период@lastпоследнее значение ряда

Если период не указан:

  • для scalar-полей возвращается само поле;
  • для рядов ошибка PERIOD_REQUIRED, кроме метрик, где есть естественный агрегат за весь проект.

5. Форматы

Формат указывается после |.

ФорматПример результата
raw3472508.12
number3 472 508
money3 472 508 RUB
percent21,5%
ratio3,21
period2026-08
dateавгуст 2026
textбез числового форматирования

Дополнительные параметры формата:

{{ pf:investment.npv[mode=real] | money:currency=RUB,decimals=0 }}
{{ pf:investment.irr | percent:decimals=1 }}

6. Селекторы

Селекторы идут в квадратных скобках.

{{ pf:sales.revenue[product=product-id]@2026-08 | money }}
{{ pf:cashflow.row[row=28]@p6 | money }}
{{ pf:investment.npv[scope=before_financing,mode=real] | money }}

Общие селекторы:

СелекторЗначения
modenominal, real
scopebefore_financing, after_financing, participant
periodicitymonth, quarter, year
rowid строки отчета, например 28
productid продукта
mikid МиК
employeeid сотрудника
expenseid общей статьи расходов
loanid займа
equityid участника капитала

7. Первый каталог ключей

Это не полный каталог, а стартовый набор для обсуждения.

7.1. Проект

ТокенЧто возвращает
{{ pf:project.id }}id проекта
{{ pf:project.title }}название проекта
{{ pf:project.company }}компания
{{ pf:project.industry }}отрасль
{{ pf:project.region }}регион
{{ pf:project.start_date | date }}дата старта
{{ pf:project.duration_months }}длительность в месяцах
{{ pf:project.currency.code }}код валюты

7.2. Настройки расчета

ТокенЧто возвращает
{{ pf:calc.discount.monthly | percent }}месячная ставка дисконтирования compound
{{ pf:calc.discount.yearly | percent }}годовая ставка дисконтирования compound
{{ pf:calc.simple.yearly | percent }}простая годовая ставка

7.3. Продажи

ТокенЧто возвращает
{{ pf:sales.volume[product=ID]@2026-08 | number }}объем продаж продукта за месяц
{{ pf:sales.price[product=ID]@2026-08 | money }}цена продукта в периоде
{{ pf:sales.revenue[product=ID]@2026-08 | money }}выручка продукта
{{ pf:sales.revenue@2026-08 | money }}общая выручка
{{ pf:sales.revenue@all | money }}выручка за весь проект

7.4. Cash-flow

ТокенЧто возвращает
{{ pf:cashflow.operating@2026-08 | money }}операционный CF периода
{{ pf:cashflow.investing@2026-08 | money }}инвестиционный CF периода
{{ pf:cashflow.financing@2026-08 | money }}финансовый CF периода
{{ pf:cashflow.period@2026-08 | money }}денежный поток периода
{{ pf:cashflow.balance_start@2026-08 | money }}баланс на начало периода
{{ pf:cashflow.balance_end@2026-08 | money }}баланс на конец периода
{{ pf:cashflow.row[row=28]@2026-08 | money }}значение строки cash-flow по id

7.5. P&L

ТокенЧто возвращает
{{ pf:pl.revenue@2026-08 | money }}выручка
{{ pf:pl.gross_profit@2026-08 | money }}валовая прибыль
{{ pf:pl.ebitda@2026-08 | money }}EBITDA
{{ pf:pl.net_profit@2026-08 | money }}чистая прибыль

7.6. Эффективность инвестиций

ТокенЧто возвращает
{{ pf:investment.npv[mode=nominal,scope=before_financing] | money }}NPV без дисконтирования
{{ pf:investment.npv[mode=real,scope=before_financing] | money }}NPV с учетом ставки проекта
{{ pf:investment.pi[mode=nominal,scope=before_financing] | ratio }}PI без дисконтирования
{{ pf:investment.pi[mode=real,scope=before_financing] | ratio }}PI с учетом ставки проекта
{{ pf:investment.pp[scope=before_financing] | period }}простой срок окупаемости
{{ pf:investment.dpp[mode=real,scope=before_financing] | period }}дисконтированный срок окупаемости
{{ pf:investment.irr[scope=before_financing] | percent }}IRR. Не зависит от mode
{{ pf:investment.roi[participant=ID,mode=nominal] | percent }}ROI участника

7.7. Финансирование

ТокенЧто возвращает
{{ pf:loan.draw[loan=ID]@2026-08 | money }}выдача займа
{{ pf:loan.payment[loan=ID]@2026-08 | money }}платеж по займу
{{ pf:loan.interest[loan=ID]@2026-08 | money }}проценты
{{ pf:loan.debt_end[loan=ID]@2026-08 | money }}долг на конец периода
{{ pf:equity.contribution[equity=ID]@2026-08 | money }}взнос участника
{{ pf:equity.payout[equity=ID]@2026-08 | money }}выплата участнику

8. Требования к реализации

Нужны три слоя.

8.1. ProjectDataResolver

Серверный модуль:

resolveProjectValue({
  projectId,
  token: "pf:investment.npv[mode=real,scope=before_financing]",
  period: "2026-08",
  format: "money",
})

Должен возвращать:

{
  ok: true,
  value: 3472508.12,
  formatted: "3 472 508 RUB",
  meta: {
    source: "calculated",
    period: "2026-08",
    dependencies: ["cashflow", "projectCalc"]
  }
}

Ошибки:

КодКогда
UNKNOWN_TOKENключ не зарегистрирован
PERIOD_REQUIREDнужен период, но он не указан
PERIOD_NOT_FOUNDпериод не найден в проекте
ENTITY_NOT_FOUNDне найден product/loan/equity/etc
VALUE_NOT_AVAILABLEзначение невозможно рассчитать
AMBIGUOUS_SELECTORне хватает селектора

8.2. ProjectDataRegistry

Каталог всех ключей:

{
  key: "investment.npv",
  title: "NPV проекта",
  type: "money",
  source: "calculated",
  periodMode: "aggregate",
  selectors: ["mode", "scope"],
  resolver: resolveInvestmentNpv,
}

Этот каталог нужен для:

  • автодополнения токенов;
  • проверки шаблонов перед генерацией;
  • документации;
  • миграции ключей без поломки старых документов.

8.3. ProjectSnapshot

Один объект, который собирает:

  • вводимые данные из БД;
  • legacy fallback из ProjectWorkspaceState;
  • расчетные модели cash-flow, P&L, налоги, займы, капитал, эффективность;
  • карту периодов проекта.

Документ не должен сам собирать расчеты. Он должен только спрашивать resolver.

9. Проверка доступности данных

Для каждого ключа в registry нужны тесты:

ПроверкаОписание
canResolveтокен успешно возвращает значение на тестовом проекте
periodByIndex@pN и @YYYY-MM дают один период
formattingформат money/percent/ratio стабилен
sourceTraceзначение возвращает источник и зависимости
templateValidationнеизвестные токены ловятся до генерации документа

10. Важные решения для обсуждения

  1. Синтаксис токенов: оставить {{ pf:... }} или выбрать другой.
  2. Периоды: использовать @p7, @2026-08, @last, @all.
  3. Нужно ли разрешать русские алиасы, например {{ ПФ:NPV }}, или держать только стабильные английские ключи.
  4. Нужно ли сохранять рассчитанные snapshots в БД для аудита, или всегда пересчитывать live.
  5. Как версионировать ключи, если формула поменялась.

11. Предлагаемый следующий шаг

  1. Создать lib/project-data/registry.ts с первым набором ключей.
  2. Создать lib/project-data/resolver.ts.
  3. Вынести server-safe сборку расчетного ProjectSnapshot из текущих UI-хуков.
  4. Добавить API:
POST /api/projects/:projectId/resolve-values

Тело:

{
  "tokens": [
    "{{ pf:project.title }}",
    "{{ pf:investment.npv[mode=real,scope=before_financing] | money }}",
    "{{ pf:cashflow.balance_end@2026-08 | money }}"
  ]
}

Ответ:

{
  "{{ pf:project.title }}": "Магазин ...",
  "{{ pf:investment.npv[mode=real,scope=before_financing] | money }}": "3 472 508 RUB",
  "{{ pf:cashflow.balance_end@2026-08 | money }}": "..."
}