Руководство администратора

Импорт проекта из JSON-бандла

Миграция проекта целиком (Space, спецификации, требования, трассировки, системная инженерия) из одного файла bundle.json

Предварительные требования

Кто может импортировать

• Нужна авторизация под зарегистрированной учётной записью. • Создание нового пространства доступно любому авторизованному пользователю. Вызвавший становится owner'ом пространства и автоматически попадает в команду "General" по умолчанию. • Обновление существующего пространства требует роли owner или admin в этом пространстве (сопоставляется по slug). Member и viewer импортировать в него не могут.

Что потребуется

• Один файл bundle.json размером не более 64 МБ. Большие проекты разбивайте на несколько bundle'ов (обычно по спецификации на каждый). • Файл — валидный UTF-8 JSON по bundle-схеме (см. "Справочник полей"). • У каждой сущности, которую планируется перезагружать — устойчивый externalId. Конвертер идемпотентен по externalId: один и тот же вход всегда даёт одно и то же состояние БД. • Никаких дополнительных ключей API не нужно: импорт выполняется от вашего имени, по тому же cookie-сеансу, с которым вы работаете в интерфейсе.

Как попасть на страницу импорта

Откройте /import в интерфейсе (например https://ваш-rms-navigator/import) или перейдите по ссылке "Импорт" в основной навигации. На этой странице доступны и CSV/ReqIF-импорт, и импорт JSON-бандла — переключитесь на вкладку "Bundle".

Обзор

Когда применять

Используйте импорт через bundle, когда есть готовый проект (в Word, Excel, DOORS или другом инструменте) и нужно перенести его в СУТР Навигатор одним шагом без ручного ввода. Импорт идемпотентный: можно повторно запускать тот же bundle для обновления данных — сопоставление идёт по externalId, а не по id в БД. Это позволяет итеративно дорабатывать конвертер и импортировать заново. Типичный сценарий: 1. Написать (или переиспользовать) конвертер, который извлекает исходные данные и формирует bundle.json. 2. Проверить и посмотреть предпросмотр на странице /import. 3. Запустить импорт. При необходимости повторить. 4. Добавить участников и продолжить работу в UI.

Верхнеуровневая структура

Bundle — это один JSON-объект со следующими полями: • version (обязательно) — должно быть "1.0" • metadata — произвольные метаданные, сервер игнорирует (полезно для происхождения данных) • space (обязательно) — целевое пространство • systemProject (необязательно) — для данных системной инженерии • systemFunctions — функции из FHA • safetyAssessments — FHA/PSSA/SSA/CCA с замечаниями • specifications (обязательно, минимум одна) — каждая содержит собственный requirements[] • traceLinks — трассировочные связи между требованиями, в том числе кросс-спецификационные У каждой сущности есть строковый externalId (буквы Unicode + цифры + ._:\-/). Повторный запуск того же bundle обновляет по externalId — дубликаты не создаются.

Справочник полей

Space

• externalId — постоянный ключ для импорта • name — отображаемое имя • slug — kebab-case, глобально уникальный, входит в URL • description, icon — необязательные Если space с таким slug уже существует, он обновляется; иначе создаётся новый, а вызывающий становится его owner.

SystemProject, SystemFunction, SafetyAssessment

SystemProject: externalId, name, code, systemDalLevel (A…E), status (CONCEPT|DEVELOPMENT|VERIFICATION|CERTIFICATION|COMPLETED). SystemFunction: externalId, code, name, опциональные fdal, failureCondition, failureSeverity (CATASTROPHIC|HAZARDOUS|MAJOR|MINOR|NO_EFFECT), parentExternalId для иерархии, sortOrder. SafetyAssessment: externalId, type (FHA|PSSA|SSA|CCA), status, version, findings[]. Каждый SafetyFinding содержит code, description, severity, category (FAILURE_MODE|COMMON_CAUSE|INDEPENDENCE|COVERAGE_GAP|DESIGN_ERROR|OTHER), опциональный mitigationStrategy и linkedRequirementExternalIds[] для привязки замечаний к требованиям.

Specification

• externalId, name, code • type — SyRS, HLR, LLR, IRS, SRS, ICD и др. • domain — SOFTWARE | HARDWARE | SYSTEM • status, version • codeTemplate — шаблон вида "FSCU_REQ_SYS_{GROUP}_{NNN:03}". Плейсхолдеры: {GROUP} — литеральное значение groupCode, {NNN} или {NNN:N} — автосчётчик с нулями до N знаков. Счётчик независимый для каждой группы. • attributeSchema[] — описание кастомных полей карточки. Каждое поле: key, label, type (text|number|boolean|enum|multi-enum|date), options[] для enum, required. • requirements[] — см. ниже.

Requirement

• externalId — обязательно • code — обязателен для новых требований, при обновлении можно не указывать. Если у спецификации задан codeTemplate, можно оставить пустым — сервер сам сгенерирует код (автосчётчик по groupCode). • groupCode — значение {GROUP} для шаблона • content — TipTap JSON-документ. Минимальный вид: { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "..." }] }] } • rationale — TipTap JSON (обоснование производного требования) • status — DRAFT | IN_REVIEW | APPROVED | REJECTED | OBSOLETE • priority — MUST | SHOULD | COULD | WONT • isRequirement — false для заголовков/примечаний (трассировка не нужна) • dalLevel — A…E • isSafety, isDerived — булевы • verificationMethod — ANALYSIS | REVIEW | TEST | DEMONSTRATION • attributes — произвольный объект по attributeSchema • parentExternalId — иерархия внутри той же спецификации • sortOrder — целое

TraceLink

• fromExternalId, toExternalId — ссылки на требования в любой спецификации бандла • type — SATISFIES | DERIVES_FROM | VERIFIED_BY | IMPLEMENTS | REFINES | CONFLICTS | DEPENDS_ON | TRACES_TO | ALLOCATED_TO | MITIGATES • description — необязательное Дубликаты (одинаковые from/to/type) пропускаются. Дубликаты в исходных данных часто бывают, когда матрица трассировки приводит связи в обоих направлениях — это нормальный случай и отражается в counts.skipped.

Минимальный пример

Минимальный рабочий bundle

{
  "version": "1.0",
  "space": {
    "externalId": "acme",
    "name": "Acme Controller",
    "slug": "acme"
  },
  "specifications": [
    {
      "externalId": "acme-sys",
      "name": "Системные требования",
      "code": "SYS",
      "type": "SyRS",
      "domain": "SYSTEM",
      "codeTemplate": "ACME_SYS_{GROUP}_{NNN:03}",
      "requirements": [
        {
          "externalId": "ACME_SYS_INPUT_001",
          "code": "ACME_SYS_INPUT_001",
          "groupCode": "INPUT",
          "content": {
            "type": "doc",
            "content": [
              {
                "type": "paragraph",
                "content": [
                  { "type": "text", "text": "Контроллер должен обнаруживать входной сигнал не позднее чем за 10 мс." }
                ]
              }
            ]
          },
          "dalLevel": "C",
          "isSafety": true
        }
      ]
    }
  ]
}

Как применить

Сохраните сниппет выше в файл bundle.json на своём компьютере. Откройте в СУТР Навигатор страницу /import, загрузите файл, сверьтесь с предпросмотром и нажмите "Импортировать". В ответе будут показаны сущности, которые создались, обновились и были пропущены.

От исходных документов к bundle.json

"У меня папка Word/Excel — с чего начать?"

Волшебной кнопки "выбрать папку и импортировать" нет. Эндпоинт импорта принимает структурированный JSON, а ваши исходные файлы — .docx, .xlsx, .xml, PDF-выгрузка, дамп DOORS — для него неструктурированы. Поэтому работа такая: извлечь нужные данные из файлов, уложить их в структуру bundle-схемы и отправить результат. Это одноразовый скриптовый таск. Вы пишете небольшой конвертер (один раз, ~100-400 строк), доводите его до нужного результата и затем перезапускаете при каждом изменении источников. Типичный бюджет: от полудня до пары дней — в зависимости от беспорядка в источнике. Эталонный конвертер scripts/import-fscu/ — это полностью рабочий пример, берите его как стартовый шаблон. Далее по разделам — решения, которые вам предстоит принять.

Шаг 1. Выберите язык и парсер

Zod-типы bundle-схемы написаны на TypeScript, поэтому TS даёт сквозную типизацию. Эталонный FSCU-R использует Node.js + tsx и эти библиотеки: • pandoc (внешний CLI) — конвертирует .docx в HTML. Вызывайте через child_process.spawn. Сохраняет таблицы, списки, sub/sup и математические формулы (как $…$ в LaTeX). Mammoth, в отличие от pandoc, тихо теряет OMML-формулы. • cheerio — jQuery-подобный обходчик HTML для Node. Позволяет итерировать <table>, классифицировать их по тексту первой ячейки и извлекать размеченные поля. • Ничего тяжёлого больше — никаких фреймворков. Другие форматы — другие инструменты: xlsx / exceljs для Excel, fast-xml-parser для ReqIF, papaparse для CSV, pdf-parse для PDF. Общая схема одинаковая. Python тоже подходит — используйте python-docx + json, сверяясь с bundle.schema.ts как источником правды.

Шаг 2. Определите, где в источнике какая сущность

Любой bundle сводится к пяти основным сущностям: Space (проект), Specification (документ требований), Requirement (отдельное требование), TraceLink (связь родитель-потомок) и опционально SystemFunction / SafetyAssessment для данных системной инженерии. Пройдитесь по одному исходному документу и ответьте на каждый вопрос: • Где начинается и заканчивается *карточка* требования? В FSCU каждое требование — отдельная <table>, первая ячейка которой содержит идентификатор; значит, детектор — "у таблицы в верхней-левой ячейке текст, похожий на ID". В других документах это может быть абзац с определённым стилем и следующий за ним маркированный список; или строка в большой таблице Excel. • Какой externalId устойчивый? Обычно — существующий код в источнике (REQ-042, FSCU_SYS_INIT_001). Если кода нет — придумайте его по устойчивому признаку, но НИКОГДА не по индексу строки: иначе повторный импорт создаст дубликаты. • Какие поля в какой атрибут ложатся? Сопоставьте столбцы/метки источника с полями Requirement (content, rationale, status, priority, isSafety, isDerived, dalLevel, verificationMethod, attributes.*). Всё, что не попадает во встроенные поля, кладите в attributes[] и декларируйте в attributeSchema[] у Specification. • Где трассировки? В отдельной матрице-приложении, в столбце "родитель" у каждого требования или и там и там — собирайте в traceLinks[] с from/to/type.

Шаг 3. Напишите конвертер

Скопируйте scripts/import-fscu/ в scripts/import-<ваш-проект>/, переименуйте пакет и выкиньте FSCU-специфичные парсеры (parseSysCard, parseHlrCard, parseFhaFunctions, русские метки). Оставляйте как есть: • docxToHtml() — вызов pandoc с нужными флагами. • TipTap-хелперы: $htmlToTiptap(), inlineRun(), parseTableAsTiptap(), collapseWs() — они превращают HTML-поддерево в структуру { "type": "doc", "content": [...] }, которую ожидает Requirement.content. Переиспользуйте без изменений. • classifyTables() — обход всех <table> в порядке документа с диспетчером по форме. Оставьте каркас, замените правила диспетчеризации. • detectMatrix() и extractCodes() — обобщённый детектор двухколоночной матрицы трассируемости; пригоден для любого источника с такой матрицей. На место FSCU-парсеров напишите свои. Соберите bundle в конце main() и сохраните через JSON.stringify.

Шаг 4. Запустите локально, поитерируйте, сверьтесь с предпросмотром и импортируйте

Из каталога конвертера:

Локальный запуск конвертера

cd scripts/import-<ваш-проект>
pnpm install              # один раз
./node_modules/.bin/tsx convert.ts \
  --input  ~/Documents/<папка-с-источниками> \
  --output ~/Documents/<папка-с-источниками>/out/bundle.json

# Быстрая проверка содержимого
python3 -c "
import json
b = json.load(open('.../out/bundle.json'))
print('specs:', [s['code'] for s in b['specifications']])
print('req counts:', [(s['code'], len(s['requirements'])) for s in b['specifications']])
print('trace links:', len(b.get('traceLinks', [])))
"

Цикл итерации

Конвертер — чистая функция: один и тот же вход даёт один и тот же bundle.json. Поэтому здоровый цикл такой: 1. Запустите конвертер, откройте bundle в редакторе, посмотрите несколько требований и трассировочные связи. 2. Откройте сгенерированный bundle на странице /import в режиме "Предпросмотр". Сервер валидирует Zod-схемой целиком и показывает счётчики по разделам; если обязательное поле отсутствует, Zod подскажет точный путь, где сломалось. 3. Поправьте конвертер и начните сначала. Прогон конвертера — 1-5 секунд на типичный проект, итерировать дёшево. 4. Когда счётчики в "Предпросмотре" соответствуют ожиданиям — нажимайте "Импортировать". Повторный импорт идемпотентен и безопасен. НЕ правьте bundle.json руками после генерации. Файл — продукт сборки, каждый прогон конвертера его перезаписывает; ручная правка слетит при следующей переконверсии источников.

Типичные ошибки

• externalId по индексу строки — убивает идемпотентность. Используйте устойчивый код из источника. • Свободный текст в Requirement.content как plain string — поле принимает TipTap-JSON, не markdown. Минимум — оберните в { "type": "doc", "content": [{ "type": "paragraph", "content": [{ "type": "text", "text": "..." }] }] }. • Забытый attributeSchema — кастомные поля в attributes отобразятся в UI только если декларированы в attributeSchema[] у Specification. • Слишком жадный детектор матриц — общий detectMatrix() может принять таблицу данных с двумя столбцами-кодами за матрицу трассировки. Ужесточайте регулярку для кода (например, требуйте суффикс _\d{2,}) или привязывайтесь к уникальному заголовку столбца. • Bundle > 64 МБ — обычно из-за вложенных картинок/файлов, которые base64'ом попали в content. Храните вложения отдельно: заливайте в object-storage и ссылайтесь по URL из текста.

Процесс импорта

Сначала предпросмотр, потом импорт

На странице /import есть два действия: 1. Предпросмотр — сервер прогоняет транзакцию и откатывает её. В ответе видно, что именно будет создано, обновлено, пропущено, и все предупреждения. 2. Импортировать — та же транзакция, но с фиксацией. Оба действия вызывают POST /api/v1/import/bundle с телом {"bundle": {...}, "previewOnly": true|false}. Повторный запуск безопасен: сущности upsert-ятся по externalId, дубликаты трассировок отфильтровываются. Если в исходных данных или конвертере найдена ошибка — правьте и запускайте заново.

Источник истины

Zod-схема, по которой сервер проверяет входящий bundle, лежит в apps/api/src/modules/bundle-import/bundle.schema.ts. Это единый источник истины — UI, сервис и любые конвертеры используют именно её. Лимит размера: 64 МБ на запрос. Для больших проектов разбивайте bundle по спецификациям и делайте несколько импортов подряд.

Эталонный конвертер FSCU-R

Реальный пример конверсии Word-документов в bundle — scripts/import-fscu/ в репозитории. Скрипт разбирает системные требования, ТВУ к ПО, приложение с матрицей трассируемости и таблицу FHA в валидный bundle.json через pandoc (точная конвертация .docx в HTML с сохранением формул и таблиц) и cheerio (обход HTML). Используйте его как шаблон при написании конвертера под свой формат. Подход тот же для Excel (XLSX), ReqIF-выгрузок из DOORS или CSV: извлечь в память, собрать структуру bundle, проверить и отправить POST-запросом.