Перейти к содержанию

Вычисления

Функция Вычисления (Администрирование > Метамодель > вкладка «Вычисления») позволяет определять формулы, автоматически вычисляющие значения полей при сохранении карточек. Это мощный инструмент для получения метрик, оценок и агрегаций из ваших архитектурных данных.

Как это работает

  1. Администратор определяет формулу, нацеленную на конкретный тип карточки и поле
  2. При создании или обновлении любой карточки этого типа формула запускается автоматически
  3. Результат записывается в целевое поле
  4. Целевое поле помечается как только для чтения на странице карточки (пользователи видят метку «вычисляемое»)

Создание вычисления

Нажмите + Новое вычисление и настройте:

Поле Описание
Название Описательное имя вычисления
Целевой тип Тип карточки, к которому применяется вычисление
Целевое поле Поле, в которое записывается результат
Формула Выражение для вычисления (синтаксис описан ниже)
Порядок выполнения Порядок выполнения при наличии нескольких вычислений для одного типа (меньшее значение выполняется первым)
Активно Включить или отключить вычисление

Синтаксис формул

Формулы используют безопасный изолированный язык выражений. Вы можете ссылаться на поля текущей карточки, на связанные и дочерние карточки, на родительскую карточку и на даты жизненного цикла.

Используйте ключ поля, а не его подпись

Поля указываются по их ключу, обычно в camelCase (costTotalAnnual), а не по подписи, отображаемой на карточке (Общая годовая стоимость). Несуществующее имя разрешается в None, и любая арифметика над None завершается общей ошибкой вычисления.

Ключ можно посмотреть в Администрирование > Метамодель > (тип карточки), открыв поле и прочитав его Ключ. Проще: в редакторе формул чипы под полем ввода перечисляют data.<ключ> для каждого поля выбранного типа, а ввод data. открывает автодополнение.

Контекстные переменные

Переменная Описание Пример
data.<ключПоля> Любое пользовательское поле текущей карточки, по его ключу data.costTotalAnnual
data.name, data.description, data.status, data.subtype, data.approval_status, data.reference Встроенные свойства карточки data.subtype
data.lifecycle.<фаза> Даты жизненного цикла, где фаза — это plan, phaseIn, active, phaseOut или endOfLife data.lifecycle.endOfLife
relations.<ключТипаСвязи> Массив карточек, связанных этим типом связи, в обоих направлениях relations.relAppToITC
relation_count.<ключТипаСвязи> Количество карточек, связанных этим типом связи relation_count.relAppToITC
children Массив прямых дочерних карточек (иерархические типы) SUM(PLUCK(children, "attributes.costTotalAnnual"))
children_count Количество прямых потомков children_count
parent Родительская карточка (объект с id, name, type, subtype, attributes) или None для корневой карточки IF(parent, parent.attributes.businessCriticality, data.businessCriticality)
hierarchy_level Глубина текущей карточки в иерархии «родитель-потомок» (1 = корень, без ограничения). 1 для неиерархических типов карточек hierarchy_level * 10

Ключ типа связи — это ключ из раздела Администрирование > Метамодель > Связи, например relAppToITC или relInitiativeToApp. Направление не имеет значения: карточка находит тип связи по одному и тому же ключу независимо от того, стоит ли она на стороне источника или цели. Архивные карточки исключаются из relations, relation_count и children.

Чтение полей связанной карточки

Каждый элемент в relations.<ключТипаСвязи> и в children — это объект-обёртка, а не поля связанной карточки напрямую:

{
  "id": "8f1c…",
  "name": "NexaCore ERP",
  "type": "Application",
  "attributes":     { "costTotalAnnual": 45000, "businessCriticality": "missionCritical" },
  "rel_attributes": { "costTotalAnnual": 12000 }
}
  • attributes содержит значения собственных полей связанной карточки.
  • rel_attributes содержит значения, сохранённые на самой связи, если тип связи определяет схему атрибутов. Например, relAppToITC несёт собственный costTotalAnnual, что позволяет зафиксировать, сколько одно приложение тратит на один ИТ-компонент.

Это важно для PLUCK и FILTER: они принимают путь к ключу, поэтому для доступа к полю нужен префикс attributes.:

# Сумма годовой стоимости ИТ-компонентов, которые использует это приложение
SUM(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual"))

# Сумма стоимости, записанной на каждой связи «приложение — компонент»
SUM(PLUCK(relations.relAppToITC, "rel_attributes.costTotalAnnual"))

Голый ключ вида "costTotalAnnual" ищется на объекте-обёртке, не находится и возвращает список из None, который SUM показывает как 0. Формула по связям, упорно возвращающая 0, почти всегда означает пропущенный префикс attributes..

Работа с пустыми значениями

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

COALESCE(data.licenseCost, 0) + COALESCE(data.supportCost, 0) + COALESCE(data.infraCost, 0)

SUM, AVG, MIN и MAX уже пропускают нечисловые элементы, поэтому в защите не нуждаются.

Данные PPM на карточках Initiative

Корень ppm открывает формулам строки бюджета и затрат модуля PPM, разделённые на capex и opex и разбитые по финансовым годам — детализацию, которую сведённые атрибуты data.costBudget / data.costActual на карточке дать не могут.

Переменная Описание
ppm.capexBudget, ppm.opexBudget, ppm.totalBudget Плановый бюджет, из строк бюджета PPM
ppm.capexPlanned, ppm.opexPlanned, ppm.totalPlanned Плановые суммы в строках затрат PPM
ppm.capexActual, ppm.opexActual, ppm.totalActual Фактические суммы в строках затрат PPM
ppm.byYear Те же девять показателей по финансовым годам, в виде списка {year, capexBudget, …}
ppm.currentFiscalYear Финансовый год, в который попадает сегодняшняя дата
ppm.unscheduledPlanned, ppm.unscheduledActual Строки затрат без даты: учитываются в итогах, но не относятся ни к одному году

byYear — это список, а не объект с ключами-годами, поэтому на нём работают обычные функции FILTER и PLUCK:

# Общий бюджет capex за все годы
ppm.capexBudget

# Бюджет capex только текущего финансового года
SUM(PLUCK(FILTER(ppm.byYear, "year", ppm.currentFiscalYear), "capexBudget"))

# Бюджет capex каждой инициативы, связанной с этой карточкой
SUM(PLUCK(relations.relInitiativeToApp, "ppm.capexBudget"))
  • Финансовый год называется по календарному году, в котором он заканчивается. При начале в октябре 15 окт 2025 относится к ФГ2026, а 30 сен 2025 — к ФГ2025. При январском начале по умолчанию финансовый год совпадает с календарным.
  • Строки бюджета и строки затрат берут год из разных источников. Строка бюджета несёт тот финансовый год, который Вы ввели; год строки затрат выводится из её даты. Если в Вашей организации год называют по началу периода, эти два расчёта разойдутся.
  • total* — это сумма всех строк, а не capex + opex. Строка с иной категорией (например, из импорта) всё равно учитывается в итоге.
  • Карточка, не являющаяся инициативой, читает все показатели ppm как 0 с пустым byYear, поэтому формула на неподходящем типе вернёт ноль, а не ошибку.

Изменение строки бюджета или затрат PPM перезапускает вычисления инициативы, поэтому всё производное обновляется сразу. Карточки, которые читают данные PPM другой карточки через связь, не обновляются.

Встроенные функции

Функция Описание Пример
IF(condition, true_val, false_val) Условная логика. Вычисляется только выбранная ветвь IF(data.businessCriticality == "missionCritical", 100, 25)
SUM(array) Сумма числовых значений SUM(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual"))
AVG(array) Среднее числовых значений AVG(PLUCK(children, "attributes.numberOfUsers"))
MIN(array) Минимальное значение MIN(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual"))
MAX(array) Максимальное значение MAX(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual"))
COUNT(array) Количество элементов COUNT(relations.relAppToInterface)
ROUND(value, decimals) Округление числа ROUND(data.costTotalAnnual / 12, 2)
ABS(value) Абсолютное значение ABS(data.budgetVariance)
LN(value) Натуральный логарифм. Возвращает None для нуля, отрицательных и нечисловых значений LN(data.numberOfUsers)
COALESCE(a, b, ...) Первое ненулевое значение COALESCE(data.customScore, 0)
LOWER(text) Текст в нижнем регистре LOWER(data.productName)
UPPER(text) Текст в верхнем регистре UPPER(data.subtype)
CONCAT(a, b, ...) Объединение строк CONCAT(data.name, " (", data.subtype, ")")
CONTAINS(text, search) Проверка вхождения подстроки CONTAINS(data.description, "legacy")
PLUCK(array, путь) Извлечение пути к ключу из каждого элемента PLUCK(relations.relAppToITC, "attributes.costTotalAnnual")
FILTER(array, путь, value) Оставить элементы, у которых путь к ключу равен значению FILTER(relations.relOrgToApp, "attributes.hostingType", "onPremise")
MAP_SCORE(value, mapping) Сопоставление категориальных значений с оценками MAP_SCORE(data.businessCriticality, {"missionCritical": 3, "businessCritical": 2})

Также доступны безопасные встроенные функции Python len, str, int, float, bool, abs, round, min, max и sum, а также обычные операторы и сравнения.

Примеры формул

Сумма нескольких стоимостных полей одной карточки:

COALESCE(data.licenseCost, 0) + COALESCE(data.supportCost, 0) + COALESCE(data.infraCost, 0)

Суммарная годовая стоимость ИТ-компонентов, используемых приложением:

SUM(PLUCK(relations.relAppToITC, "attributes.costTotalAnnual"))

Оценка риска на основе критичности:

IF(data.businessCriticality == "missionCritical", 100, IF(data.businessCriticality == "businessCritical", 75, 25))

Количество связанных интерфейсов:

relation_count.relAppToInterface

Количество on-premise приложений в организации:

COUNT(FILTER(relations.relOrgToApp, "attributes.hostingType", "onPremise"))

Свод стоимости из дочерних карточек:

SUM(PLUCK(children, "attributes.costTotalAnnual"))

Размещение в TIME Model (Tolerate / Invest / Migrate / Eliminate), тот же пример, который Вы увидите в панели Formula Reference внутри Админ → Метамодель → Вычисления при создании нового вычисления. Target type = Application, target field = timeModel. Предполагается, что Вы добавили два поля single_select с именами businessFit и technicalFit с опциями excellent, adequate, insufficient, unreasonable:

# ── TIME Model (Tolerate / Invest / Migrate / Eliminate) ──
# Assumes single_select fields: businessFit and technicalFit
# with options: excellent, adequate, insufficient, unreasonable.
#
# Scoring: Map each dimension to 1-4 numeric scale.
# Business Fit  = Y-axis (how well does it serve the business?)
# Technical Fit = X-axis (how healthy is the technology?)
#
# Quadrant logic (threshold at score 2.5):
#   Invest    = high business + high technical
#   Migrate   = high business + low technical
#   Tolerate  = low business  + high technical
#   Eliminate = low business  + low technical
#
bf = MAP_SCORE(data.businessFit, {"excellent": 4, "adequate": 3, "insufficient": 2, "unreasonable": 1})
tf = MAP_SCORE(data.technicalFit, {"excellent": 4, "adequate": 3, "insufficient": 2, "unreasonable": 1})
IF(bf is None or tf is None, None, IF(bf >= 2.5, IF(tf >= 2.5, "invest", "migrate"), IF(tf >= 2.5, "tolerate", "eliminate")))

Как видно из примера, формула может занимать несколько строк. Строка вида имя = выражение сохраняет промежуточное значение, доступное последующим строкам, а значение последней строки записывается в целевое поле.

Это также рабочий пример, на который ссылается Руководство для начинающих EA.

Комментарии поддерживаются с помощью #:

# Вычислить взвешенную оценку риска
IF(data.businessCriticality == "missionCritical", data.riskScore * 2, data.riskScore)

Проверка и тестирование

Редактор формул предлагает две разные проверки, и работают они по-разному:

  • Проверить выполняет формулу на синтетической карточке. Каждому числовому полю присваивается фиктивное значение 1, при этом у карточки нет связей, нет потомков и нет собственных родительских данных. Это подтверждает, что синтаксис разбирается и что использованные имена существуют, но формула, агрегирующая relations или children, здесь всегда покажет 0 или пустой результат. Так и должно быть, это не признак сломанной формулы.
  • Тест, доступный для сохранённого вычисления, выполняется на реальной карточке, которую Вы выбираете. Именно его следует использовать для всего, что связано со связями, потомками или родителем. В карточку ничего не записывается, результат только показывается Вам.

Как читать результаты ручного запуска

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

  • По блоку на каждое вычисление, с числом карточек, рассчитанных без ошибок, и числом неудачных. Все активные вычисления типа выполняются вместе, поэтому именно здесь видно, какое из них даёт сбой.
  • По строке на каждую отдельную ошибку, с числом карточек, на которых она возникла. Ошибочная формула ошибочна одинаково везде, поэтому двадцать одна неудача — это обычно одно исправление, а не двадцать одно.
  • Сами карточки, перечисленные под каждой ошибкой в виде ссылок, чтобы открыть карточку и посмотреть на данные, которые её сломали. На одну ошибку выводится не более десяти; если их больше, остаток показан числом.

Скопировать отчёт помещает весь разбор в буфер обмена в виде обычного текста.

Индикатор состояния в списке вычислений отражает тот же запуск: красный, если хотя бы одна карточка не прошла, и зелёный, только когда рассчитаны все.

Когда выполняются вычисления

Вычисления карточки пересчитываются, когда:

  • карточка создаётся или сохраняется;
  • связь, затрагивающая карточку, создаётся, изменяется или удаляется (пересчитываются оба конца связи);
  • карточке назначается новый родитель, при этом пересчитывается всё её поддерево;
  • Вы запускаете вычисление вручную из списка, что вычисляет его для каждой карточки целевого типа и сохраняет результаты.

Они не пересчитываются при редактировании другой карточки, из которой формула читает данные. Если Вы измените стоимость на ИТ-компоненте, приложение, которое её агрегирует, не изменится, пока это приложение не будет сохранено, не изменится одна из его связей или Вы не запустите вычисление для типа. Для агрегатов по данным, которые ведут другие люди, запускайте вычисление периодически или после массового импорта.

Примечание

То же относится к значениям, производным от parent и hierarchy_level: они обновляются при смене родителя и при ручном запуске, а не при каждом редактировании родительской карточки. Всегда защищайте ссылку на parent через IF(parent, …), чтобы корневые карточки, где parent равен None, не вызывали ошибку.

Порядок выполнения

Когда несколько вычислений нацелены на один и тот же тип карточки, они выполняются в порядке, определённом их значением порядка выполнения. Это важно, когда одно вычисление зависит от результата другого: задайте зависимому вычислению более позднее выполнение (большее число).

Turbo EA отклоняет набор вычислений, который образовал бы цикл, например поле A, вычисляемое из поля B, тогда как B вычисляется из A.