Technology Editor — справочник

9 мин. чтения

Статус Актуально
Последняя проверка 2026-10-07 (соответствует Git на момент импорта; содержание повторно не проверялось)
Источник Git
Исходный файл docs/TECHNOLOGY_EDITOR.md — открыть на GitHub
Язык статьи оригинал из Git
Git-репозиторий git@github.com:advertech/signage-estimator.git (сервер: /root/signage-estimator)
Git-коммит d22587c1f7e3add9efd54c7ffc0be0a01b296fa3 (master)
Файл последний раз изменён 2f46553c8fe8 от 2026-10-05
Последний импорт 2026-10-07T09:14:19+02:00
Примечание Источник истины — Git. Меняйте файл в репозитории и перезапускайте /root/wiki-sign-expert/import_git_docs.py; правки, сделанные здесь, будут перезаписаны следующим импортом.

Документ описывает фактическую реализацию (источники указаны в скобках):

  • модель — backend/app/models.py (TechnologyMaterial, TechnologyOperation, TechnologyParameter, TechnologyRule, ProductConfiguration);
  • API и валидаторы — backend/app/products.py (component_spec, operation_spec, parameter_spec, v_condition);
  • расчёт — products.plan_components, cost_components, cost_operations, resolve_parameters, matches;
  • UI — frontend/src/products.tsx (ProductAdmin, VersionEditor), справка в UI — frontend/src/technologyHelp.tsx.

1. Как теперь работает редактирование #

  • Product type (например Lightbox) содержит Technologies (например Light Box · profile construction).
  • У технологии есть версии (v1, v2, …). Редактируется текущая версия (status PUBLISHED) — напрямую, без Create draft.
    Обычная правка не создаёт новую версию. Архивные версии (ARCHIVED) — read-only история.
  • Каждая оценка (Estimate) при выборе технологии замораживает её определение в
    ProductConfiguration.technology_snapshot: компоненты, материалы, selector/family, default, waste, condition,
    маршрут операций, коэффициенты, параметры, правила и настройки версии. Все пересчёты оценки (Recalculate,
    изменение геометрии, LED, правка параметров) используют этот снимок. Цены материалов и ставки операций
    заморожены отдельно (price_snapshots), как и раньше.
  • Поэтому правка технологии не меняет существующие оценки. Новые оценки сразу получают отредактированную технологию.
    В старой оценке появляется сообщение “Technology was edited …” и кнопка Update to current technology —
    только она переносит оценку на текущее определение (после этого нужен Recalculate, как раньше при “Update to vN”).
  • Create draft / Publish остались в API (POST /api/technologies/{id}/versions, POST /api/technology-versions/{id}/publish),
    но скрыты из обычного UI. Если черновик уже существует, его можно открыть в Version history и опубликовать.

2. Поля компонента (Components & materials) #

Поле Хранится как Значение
Component component_code, строка UPPER_SNAKE_CASE (2–60 символов, regex ^[A-Z][A-Z0-9_]{1,59}$) Системный код роли. Используется расчётом: имя nesting-группы, ключ строки стоимости, ссылки из операций (колонка Component, драйвер MACHINE_TIME), из параметров (колонка Component у component_value/material_family), метрики <code>_sheets и <code>_cut_length_m. В UI только для чтения: переименование используемого кода ломает эти ссылки. Два компонента могут иметь один код, если их Condition взаимоисключающие (Lightbox: два BACK).
Label строка Только отображение (строка в разбивке, заголовок nesting-группы). В расчёте не участвует.
Method calculation_method, один из NESTED_SHEET, PERIMETER_LENGTH, AREA, LENGTH_INPUT, MANUAL_COST Как считается количество и стоимость (раздел 3).
Materials (selector value) available_materials: список {"material_id": "<id канонического материала>", "value": <число или null>} Допустимые материалы и их значение для selector (раздел 4).
Selector selector_parameter: ключ параметра или пусто Параметр оценки, значение которого выбирает материал (раздел 5).
Family family_parameter: ключ параметра или пусто Параметр оценки, значение которого — название материала (фильтр, раздел 5).
Default default_value: число или пусто Значение selector, если параметр selector пуст (раздел 4).
Waste × waste_factor: число 0.5…5 Множитель количества для PERIMETER_LENGTH, AREA, LENGTH_INPUT (раздел 6).
Quantity parameter (раньше “Qty param”) quantity_parameter: ключ или пусто Откуда LENGTH_INPUT берёт метры, а MANUAL_COST — сумму (раздел 6).
Category cost_category: MATERIALS, MACHINE, LABOUR, OUTSOURCING, LOGISTICS, OPTIONS Группа в разбивке. В итогах оценки MATERIALS → Material; MACHINE → колонка cut cost; остальные → other operations; MACHINE и остальные вместе = Operations.
Condition condition: JSON-объект Когда строка применяется (раздел 7). {} = всегда.
Active boolean Неактивная строка полностью пропускается расчётом.

3. Methods (METHODS в products.py) #

Method Что считает Нужно Количество
NESTED_SHEET Раскладка геометрии изделия на листах выбранного материала (одна nesting-группа на код компонента, размер листа берётся из материала). Стоимость = списанная площадь × цена за м² (цена SHEET делится на площадь листа, цена M2 берётся как есть). Материал с размерами листа и ценой SHEET или M2. Списанные м² по результату nesting (листы минус остатки, возвращённые на склад). Waste не применяется.
PERIMETER_LENGTH Суммарный периметр геометрии изделия (Lightbox: 2W + 2H на короб × количество). Материал с ценой за METER. периметр, м × Waste.
AREA Суммарная площадь геометрии изделия. Материал с ценой за M2. площадь, м² × Waste.
LENGTH_INPUT Длина, введённая в оценке (например профиль рамы). Quantity parameter (метры), материал за METER. значение параметра × Waste (пусто → 0).
MANUAL_COST Денежная сумма (например предложение поставщика; Lightbox LED = модули × цена модуля). Quantity parameter (EUR). Материал не нужен. сумма = значение параметра; пусто/0 → предупреждение “no cost entered”.

Если единица цены материала не подходит методу (например цена за метр у NESTED_SHEET), оценка показывает предупреждение.

4. Materials (selector value) и выбор материала #

Каждый тег — запись {"material_id": "...", "value": ...}. material_id — id канонического материала
(Settings → Materials). В UI тег подписан как <название> — <толщина> mm · <лист>; число после → — selector value.
Отключённые материалы пропускаются (component_entries).

Алгоритм выбора одного материала (plan_components), строго по порядку:

  1. Если задан Family и у параметра family есть значение — оставить только материалы с name = этому значению.
  2. Если задан Selector и у его параметра есть значение — взять материал с равным selector value
    (числа сравниваются численно: 3 = 3.0).
  3. Иначе, если значение selector пустое и задан Default — взять материал с selector value = Default.
  4. Иначе (значение selector пустое) — взять первый материал списка.

Если у параметра selector есть значение, но совпадения нет, материал не выбирается, и оценка предупреждает
“no material configured for the selected options”.

Важно. Несколько материалов без selector value и без Selector означают: всегда используется первый.
Сейчас в Lightbox так настроен BACK · Aluminium: Aluminium 1.50 mm и Aluminium 2.00 mm без значений —
всегда берётся 1.50 mm. Чтобы толщину можно было выбирать, задайте записям значения 1.5 и 2, создайте параметр
(например back_thickness_mm, тип component_value, Component BACK) и укажите его в Selector.

5. Selector и Family #

Selector — ключ (lower_snake_case) параметра оценки этой технологии, например face_thickness_mm.
Его текущее значение в оценке (ручной выбор → значение Auto-правила → default параметра) сравнивается
с selector value материалов. Параметр типа component_value, у которого колонка Component = этот компонент,
автоматически предлагает в оценке selector values его материалов (так появляется «Face thickness 3 / 4 / 5 mm»).

Family — ключ параметра типа material_family, значение которого — название материала,
например face_material = "PMMA opal". Он сужает список до одного семейства, поэтому
“PMMA opal 3 mm” и “PMMA clear 3 mm” могут иметь одинаковый selector value 3.

Пустой Selector/Family просто не используется. Серое слово family в пустом поле — это placeholder, не значение.
UI предлагает ключи параметров этой технологии (datalist); вводить можно только lower_snake_case.

6. Waste и Quantity parameter #

Waste × умножает измеренное количество до умножения на цену; только для PERIMETER_LENGTH, AREA,
LENGTH_INPUT. Пример: периметр 10 м, Waste 1.05 → 10.5 м × 3.40 €/м = 35.70 €. 1 = без припуска.
NESTED_SHEET его игнорирует — раскладка уже даёт реальный расход и остатки; MANUAL_COST тоже.

Quantity parameter — ключ параметра оценки. LENGTH_INPUT читает из него метры, MANUAL_COST — сумму в EUR.
Другие методы его игнорируют. Пусто: LENGTH_INPUT = 0, MANUAL_COST = 0 с предупреждением.
Примеры: frame_length_m (рама Channel Letters), led_cost (сумма поставщика; у Lightbox — модули × цена модуля,
вычисляется LED-раскладкой).

6a. Operation route и ставки #

Технология не хранит ставок. Каждый шаг маршрута ссылается на операцию из Settings → Operations
(выбирается из списка), и ставка всегда берётся из этой операции (operation.rate + operation.unit).
Нужна другая ставка — выберите или создайте операцию с этой ставкой. Поле Rate override удалено из редактора,
API его отклоняет. Старые значения остаются только в архивных версиях и в замороженных оценках.

Unit операции Rate означает Для каких шагов
HOUR (или pricing per_hour) € в час шаги по времени: заполнен Min / unit и/или Fixed min
MINUTE € в минуту (показывается как €/ч × 60) шаги по времени
METER, M2, PIECE, SHEET, TRIP, FIXED € за единицу шаги за единицу: Min / unit и Fixed min пустые

Шаг по времени (заполнен Min / unit или Fixed min, либо драйвер PER_MINUTE):
минуты = Fixed min + (Quantity source × Coefficient) × Min / unit, стоимость = минуты ÷ 60 × €/ч операции.
Пустой Min / unit считается как 1 минута на единицу Quantity source; для чисто фиксированного времени пишите 0.

Шаг за единицу (оба поля пустые): стоимость = Quantity source × Coefficient × ставка операции.

Пример Light Box, сборка 45 минут на ящик: Quantity source box_count, Min / unit 45, Fixed min 0;
45 минут на весь заказ: Min / unit 0, Fixed min 45. Ставка — у операции «Assemble 4 corners» в Settings → Operations
(Unit HOUR, Rate, например, 21).

Название драйвера для таких шагов — только подпись. Свои формулы есть у MACHINE_TIME (подача CNC из машинного профиля
материала) и у драйверов Channel Letters (BODY_ASSEMBLY, PVC_TRIM_GLUING, ASSEMBLE_ON_BASE, CLOSE_FACE),
но и они используют почасовую ставку операции. Если шаг по времени ссылается на непочасовую операцию,
расчёт и редактор показывают предупреждение (раньше получалось молча 0).

Миграция f7b9d1e3a5c4 перенесла существующие Rate override в операции без изменения стоимости: совпадающие со ставкой
операции — удалены; отличающиеся — маршрут переключён на отдельную операцию с этой ставкой
(например «LED installation · Aluminium / Powder Coated», 21 €/ч). Операции Light Box стали почасовыми
(покраска и UV — за м²).

7. Condition #

Проверяется функцией products.matches(condition, values); values — значения параметров оценки по ключам.
Тот же синтаксис используют Condition операций и Visible if параметров. UI и API проверяют синтаксис
и не дают сохранить неверное условие.

Нужно Пишите Смысл
всегда {} без условия
одно значение {"back_material": "ACP"} равно "ACP"
boolean {"frame": true} поле Frame = Yes
число {"profile_mm": 100} равно 100 (100 = 100.0)
одно из {"graphics": {"in": ["CUT_VINYL", "PRINTED_VINYL"]}} значение в списке
ни одно из {"painting_mode": {"not_in": ["NONE"]}} значение не в списке
не равно {"painting_mode": {"ne": "NONE"}} отличается ("eq" — равно)
несколько (И) {"frame": true, "frame_paint": true} все условия сразу
нет значения {"led_cost": null} значение null; отсутствующее или скрытое (Visible if) поле имеет значение null

Не поддерживается: ИЛИ между разными ключами, <, >, диапазоны, формулы. Неизвестные операторы
(например "gt") раньше молча игнорировались — теперь отклоняются с ошибкой. Ключи должны быть параметрами этой технологии.

Сравнение учитывает тип: "100" (строка) ≠ 100 (число), "true" ≠ true. Пишите значение так, как его хранит поле:
choice → строка, Yes/No → true/false, числовые поля → число.

8. Синтаксис по полям #

Поле Ожидаемый тип Правильно Неправильно
Component код FACE face, "FACE"
Label текст Face · PMMA opal (пусто)
Selector value материала число или пусто 3, 1.5 "3", 3 mm
Selector / Family ключ или пусто face_thickness_mm Face thickness, {face_thickness_mm}
Default число или пусто 3 "3", [3], {"value":3}
Waste × число 0.5–5 1.05 5%, 105
Quantity parameter ключ или пусто frame_length_m frame length
Condition / Visible if JSON-объект {"letter_depth_mm": 100} letter_depth_mm=100, {'frame': true}, [], ""
Default параметра (Estimate fields) JSON-объект с "value" {"value": 3}, {"value": "ACP"} 3
Options параметра JSON-массив объектов [{"value": "ACP", "label": "ACP"}] ACP, ALUMINIUM

Правила JSON:

  • { } — объект (пары ключ → значение), [ ] — список. Condition — всегда объект; списки только внутри in/not_in.
  • Ключи и строки — в двойных кавычках: "ACP". Одинарные 'ACP' — невалидный JSON.
  • Числа без кавычек, через точку: 100, 1.5.
  • Логические значения строчными без кавычек: true / false; пустое значение — null.
  • Пары и элементы списка разделяются запятыми, без запятой в конце: {"a": 1, "b": 2}.
  • Пустое поле Condition сохраняется как {} (Always). "" и [] отклоняются.

9. Примеры из текущих технологий #

Channel Letters · Aluminium / Powder Coated · FACE

Component Method Materials Selector Family Default Waste Quantity parameter Condition
FACE NESTED_SHEET PMMA opal 3/4/5 mm → 3, 4, 5; PMMA clear 3/5 mm → 3, 5 face_thickness_mm face_material 3 1 — {}

Эта строка означает: лицевая панель раскладывается на листах; в оценке выбирается семейство (face_material, например
“PMMA opal”) и толщина (face_thickness_mm); движок оставляет PMMA opal и берёт запись со значением выбранной толщины;
без выбора — 3 mm; применяется всегда.

Channel Letters · Aluminium / Powder Coated · RETURN

RETURN PERIMETER_LENGTH Aluminium return coil 60 mm → 60; 100 mm → 100 return_depth_mm — 60 1.05 — {}

Эта строка означает: борт покупается погонными метрами — периметр букв × 1.05; глубина буквы (return_depth_mm,
часто задаётся правилом по высоте) выбирает ленту 60 или 100 mm.

Lightbox · Light Box · profile construction · FACE

FACE NESTED_SHEET PMMA opal 3/4/5 mm → 3, 4, 5 face_thickness_mm — 3 1 — {}

Эта строка означает: одна лицевая панель на короб (Width × Height) раскладывается на листах PMMA opal;
толщина из оценки выбирает 3, 4 или 5 mm, по умолчанию 3 mm.

Lightbox · Light Box · profile construction · BACK (две строки)

Component Method Materials Selector Family Default Waste Quantity parameter Condition
BACK NESTED_SHEET ACP 3.00 mm — — — 1 — {"back_material": "ACP"}
BACK NESTED_SHEET Aluminium 1.50 mm, Aluminium 2.00 mm (без значений) — — — 1 — {"back_material": "ALUMINIUM"}

Эти строки означают: применяется ровно одна строка BACK в зависимости от поля Back material; обе используют код BACK,
поэтому nesting-группа всегда называется BACK; у алюминиевой строки нет Selector, поэтому всегда берётся первый
материал — Aluminium 1.50 mm.

Channel Letters · Aluminium / Powder Coated · FRAME

FRAME LENGTH_INPUT Aluminium frame profile 40×20 — — — 1.1 frame_length_m {"frame": true}

Эта строка означает: только если в оценке включена рама — введённая длина рамы (frame_length_m) × 1.1 покупается
как профиль рамы за метр и показывается в группе Options.

Обновлено 07.10.2026