Skip to content

Логика и алармы

Логика объекта — конвейер «событие владельца → реакция/аларм → кейсы-условия → действия над целью».

Реакции (trigger) и алармы (alarms) хранятся на самом объекте-владельце в таблице project_objects. Реакция срабатывает при событии владельца, если выполнены условия-кейсы, и выполняет действия над объектами-целями. Аларм устроен так же, но его действия адресуются пользователю (текст, URL, мнемосхема, звук), а не объекту. Какие события, команды и параметры команд есть у типа — объявляют его декларации (см. страницы типов и Additional data команд).

Страницы

Additional data команд

Декларации типов, адаптерный конвейер, persistence-виды, projections и три вида значений: editor / persisted / wire.

Хранение и legacy

actionData (schemaVersion 1) — единственный persisted-формат; миграции BMSBUIL-1220 и BMSBUIL-1386.

Декларации событий/команд каждого типа — на его странице в группе «Объекты». Mnemoscheme не реализует контракт логики: событий и команд у неё нет.

Контракт владельца

Что тип объекта объявляет редактору логики и алармов:

ts
/**
 * Контракт «логикоспособного» типа объекта: что тип объявляет редактору
 * логики/алармов. Реакции и алармы хранятся на самом объекте-владельце
 * (таблица project_objects), а их содержание набирается из деклараций ниже.
 */
export interface IObjectReactable<
  TReaction extends ConcreteReaction = ConcreteReaction,
  TStates extends string = string,
> {
  /** реакции владельца: событие → условия-кейсы → действия над целями */
  trigger: ConcreteReaction[];
  /** связи объекта с драйверами шин */
  driverLinks: IDriverLink[];
  /** алармы владельца: событие + условия + приоритет + действия аларма */
  alarms: IAlarm<ExtractReactionType<TReaction>>[];

  /** лейблы событий, которые объект может породить (ключ — значение objectEvent реакции) */
  readonly availableEvents: Record<ExtractReactionType<TReaction>, string>;
  /** лейблы команд, доступных над объектом-целью (ключ — значение type действия) */
  readonly availableActions: Partial<Record<ExtractActionType<TReaction>, string>>;
  /** ключи специализированных адаптеров additional data по командам (см. registry адаптеров) */
  readonly actionDataAdapters?: Partial<Record<string, ActionDataAdapterKey>>;
  /** Параметры команд типа для редактора логики; есть не у всех — типы без
   *  параметризованных команд деклараций не экспортируют. */
  readonly actionDeclarations?: IActionDeclarations;
  /** поля объекта, доступные в кейсах реакций/алармов владельца */
  readonly availableObjectFields: Record<string, string>;
  /** поля объекта, доступные как поля кейса на мнемосхеме */
  readonly availableMnemoFields: Record<string, string>;
  /** поля объекта, связываемые с драйверами: лейбл + направления READ/WRITE */
  readonly availableDriverLinkFields: TDriverLinkFieldDeclarations;
  /** операторы сравнения, доступные для каждого поля кейса */
  readonly availableOperators: Record<string, Partial<Record<LogicCase, string>>>;
  /** значения-состояния поля State кейса (у mode-object — данные пользователя) */
  readonly availableStates: Record<TStates, string>;
  /** лейблы типов значений кейса: INT (число) / STATE (строка состояния) */
  readonly availableStateLables: Record<StateCase, string>;

  /**
   * @anchor <additional_data_case_display>
   * @description Возвращает дополнительные параметры для отображения и валидации условий логики и алармов.
   * Используется UI компонентами (например, CaseItem.vue) и рантаймом алармов,
   * чтобы понять, какие диапазоны, списки состояний или подсказки нужно показать для выбранного поля и типа проверки.
   * @param field - Ключ поля (например, FieldCase.Value), за который отвечает условие.
   * @param type - Тип условия (например, StateCase.INT или StateCase.STATE).
   * @returns Объект с настройками UI (min/max, списки опций и т.п.); пустой объект — без ограничений.
   */
  getCaseAdditionalData: (field: string, type: string) => Record<string, any>;
  /** команда, выбранная по умолчанию при создании нового действия над целью этого типа */
  defaultAction: ExtractActionType<TReaction>;
  /** событие по умолчанию при создании новой реакции/аларма владельца */
  defaultEvent: ExtractReactionType<TReaction>;
  /** поле кейса по умолчанию при создании нового условия */
  defaultObjectField: FieldCase;
  defaultDriverLinkField: string;
  /** оператор сравнения по умолчанию в новом кейсе */
  defaultLogicCase: LogicCase;
  /** тип значения (INT/STATE) по умолчанию в новом кейсе */
  defaultStateLabel: StateCase;
  defaultState: ExtractCaseType<TReaction>;
}

Реакция и кейсы

ts
/**
 * Реакция владельца: «при событии objectEvent, если выполнены кейсы cases,
 * выполнить действия actions над объектами-целями». Хранится в trigger
 * объекта-владельца; конкретные типы сужают строки через свои enum'ы.
 */
export interface IReaction<T extends string, U extends string, S extends string> {
  /** идентификатор (uuid) */
  id: string;
  /** имя реакции в редакторе логики */
  name: string;
  /** событие владельца, запускающее реакцию (ключ из availableEvents владельца) */
  objectEvent: T;
  /** действия над объектами-целями; null — ещё не редактировались */
  actions: IAction<U>[] | null;
  /** условия срабатывания; null — реакция безусловная */
  cases: ICase<S>[] | null;
}
ts
/** Связка кейсов внутри одной реакции/аларма: первый кейс — START, далее AND/OR. */
export enum Operator {
  START = "START",
  AND = "AND",
  OR = "OR",
}

/** Оператор сравнения значения поля кейса с caseValue. */
export enum LogicCase {
  more = "more",
  less = "less",
  eq = "eq",
  neq = "neq",
  less_eq = "less_eq",
  more_eq = "more_eq",
}

/** Тип значения кейса: INT — число, STATE — строка состояния. */
export enum StateCase {
  INT = "INT",
  STATE = "ENUM",
}

/** Общие поля объектов, доступные в кейсах; типы сужают своими enum'ами. */
export enum FieldCase {
  State = "State",
  Value = "Value",
  LastClickCount = "LastClickCount",
  Undefined = "",
}

/** Значение кейса: число (INT) или строка состояния (STATE). */
export interface ICaseValue<T> {
  type: StateCase;
  value: T;
}

export interface ICaseValueInt extends ICaseValue<number> {
  type: StateCase.INT;
  value: number;
}

export interface ICaseValueState extends ICaseValue<string> {
  type: StateCase.STATE;
  value: string;
}

/**
 * Условие срабатывания реакции/аларма: поле объекта (objectField) в связке
 * operator сравнивается по logicCase со значением caseValue.
 */
export interface ICase<T extends string> {
  id: string;
  /** место кейса в связке условий (START/AND/OR) */
  operator: Operator;
  /** объект, поле которого проверяется; пусто — сам владелец */
  objectId?: string;
  /** проверяемое поле (из availableObjectFields владельца или цели) */
  objectField: FieldCase;
  /** оператор сравнения с caseValue */
  logicCase: LogicCase;
  /** INT-кейсы хранят число, STATE — строку состояний; T — только строковая часть */
  caseValue?: ICaseValue<T | number>;
}

Действие

Действие — команда над объектом-целью. Параметры команды (если есть) живут в actionData; какие ключи попадают в values, определяет persistence поля команды.

ts
/**
 * Действие реакции: команда `type` над объектом-целью `objectId`.
 * Параметры команды (если есть) живут в `actionData`.
 *
 * Persisted additional data живет только в actionData (schemaVersion 1);
 * формат legacy IAdditionalDataUI читает исключительно DB-миграция
 * BMSBUIL-1220, рантайм его не использует.
 */
export interface IAction<T extends string> {
  /** идентификатор (uuid) */
  id: string;
  /** команда из availableActions цели */
  type: T;
  /** объект-цель команды; undefined — цель ещё не выбрана */
  objectId?: string;
  /** значения полей команды; undefined — у команды нет параметров или они не заданы */
  actionData?: IActionDataV1;
}
ts
/**
 * Persisted формат нового additional data pipeline.
 *
 * `actionData.values` — единственный persisted-формат;
 * легаси-поле `data` удалено из БД миграцией BMSBUIL-1386
 * (подъём значений из него — миграция BMSBUIL-1220).
 */
export interface IActionDataV1<
  TValues extends Record<string, unknown> = Record<string, unknown>,
> {
  /** версия формата; сейчас всегда 1 (гвард isActionDataV1) */
  schemaVersion: 1;
  /** значения полей команды в persisted-виде (какие ключи — решает FieldPersistence поля) */
  values: TValues;
}
ts
/**
 * Что именно сохраняется в `actionData.values`.
 *
 * `literal` - сохраняем значение как есть.
 * `root` - поле само является объектом (виджет-таймеры): его значения
 *   пишутся в корень `values`, без обёртки в ключ поля.
 * `reference` - сохраняем stable id доменной сущности, а не ее index/name.
 * `virtual` - поле нужно только редактору и не пишется в БД.
 */
export type FieldPersistence =
  | {
      kind: "literal";
      valueKey: string;
    }
  | {
      kind: "root";
    }
  | {
      kind: "reference";
      valueKey: string;
      refType: string;
    }
  | {
      kind: "virtual";
    };

Жизненный цикл действия

Инициализирует additional data при выборе action target или смене типа действия. Значения вычисляются из дефолтов команды (определения additional data цели), а не наследуются от предыдущей команды.

Отправка на контроллер

Преобразует действие из базы в формат контроллера, включая additional data. Additional data уезжает на контроллер только из actionData (values); экшены без actionData (созданные до его появления) синхронизируются с дефолтами команды. Перед отправкой значения прогоняются через zod-декларацию типа цели — битые значения не уходят на контроллер.

Алармы

ts
/** Тип действия при срабатывании аларма (что показать/открыть пользователю). */
export enum EAlarmActionType {
  /** открыть URL из text */
  URL = "url",
  /** открыть выбранную мнемосхему (objectId) */
  MNEMOSCHEME = "mnemoscheme",
  /** показать текст из text */
  TEXT = "text",
  /** воспроизвести звуковой файл (audioId) */
  AUDIO = "audio",
}

/**
 * Действие аларма — реакция интерфейса на срабатывание. В отличие от
 * IAction, адресуется не объекту-цели, а пользователю (портал/мнемосхема).
 */
export interface IAlarmAction {
  id: string;
  /** что сделать при срабатывании (см. EAlarmActionType) */
  actionType: EAlarmActionType;
  /** ссылка на выбранную мнемосхему или звуковой файл */
  objectId?: string;
  /** url и текст*/
  text: string;
  /** открывать в новой вкладке? */
  newTab: boolean;
  /** id файла из RepositoryAudio */
  audioId: string;
}

/**
 * Аларм владельца: «при событии objectEvent, если выполнены кейсы cases,
 * выполнить действия actions». В отличие от реакции, несёт приоритет
 * (шествие в журнале алармов) и порог срабатывания.
 *
 * TEvent — тип события объекта-владельца (сужается классами через
 * IObjectReactable<ReactionX>; дефолт — широкое хранилище).
 */
export interface IAlarm<TEvent extends string = string> {
  id: string;
  /** имя аларма в редакторе */
  name: string;
  /** приоритет срабатывания (см. EAlarmPriority) */
  priority: EAlarmPriority;
  /** событие владельца, запускающее аларм (ключ из его availableEvents) */
  objectEvent: TEvent;
  /** описание, показываемое при срабатывании */
  description: string;
  /** условия срабатывания; null — аларм безусловный */
  cases: ICase<string>[] | null;
  /** действия при срабатывании */
  actions: IAlarmAction[];
  /** фильтр (мс): окно удержания аварийного условия до перехода аварии; 0–60000 */
  threshold: number;
}

/**
 * Приоритет аларма. Значения приходят из рантайма (IAlarmsFromRuntime):
 * "0" — самый высокий (Emergency), "7" — самый низкий (Debug);
 * точная семантика уровней определяется рантаймом, не этим репо.
 */
export enum EAlarmPriority {
  Emergency = "0",
  Alert = "1",
  Critical = "2",
  Error = "3",
  Warning = "4",
  Notice = "5",
  Informational = "6",
  Debug = "7",
}

Алармы из хранилища (широкий IAlarm) → алармы владельца с его типом событий. Событие вне перечисления владельца — битые данные (смена перечислений между версиями, ручные правки БД): аларм исключается из домена с записью в лог.

Mentioned In