Руководство администратора
Импорт проекта из 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-запросом.