openapi: 3.0.3

info:
  title: BIM Backend v2 API
  version: "2.0.0"
  description: |
    REST API сервиса **bim-backend-v2** (`platform/bim-backend-v2`) — работа с
    BIM-моделями, их элементами, свойствами, моделями статусов и метаданными.

    Сервис написан на Go. HTTP-сервер собирается в
    `internal/app/http/httpserver.go` (роутер `gorilla/mux` поверх
    `pkg/gotools/rest`). Роутинг состоит из трёх групп:

    - публичный API v1 — префикс `/api/v1` (`internal/controller/http/v1`);
    - публичный API v2 — префикс `/api/v2`, работа с BIM v3
      (`internal/controller/http/v2`);
    - внутренний API v1 — префикс `/internal/v1`
      (`internal/controller/http/v1/bim_internal` и часть роутов `bim`),
      предназначен для вызовов внутри кластера (через ingress не публикуется,
      доступ ограничивается Istio `AuthorizationPolicy` по namespace).

    Health-check доступен по `GET /ping` (без префикса и без аутентификации),
    профилировщик `net/http/pprof` — на отдельном порту `:8081`.

    ### Аутентификация
    Публичные эндпоинты (`/api/v1/*`, `/api/v2/*`) требуют JWT. Токен
    передаётся заголовком `Authorization: Bearer <jwt>` либо query-параметром
    `auth_jwt` (`pkg/gotools/auth.JWTToCtx`). Затем `pkg/auth`
    (`JWTUserExtractorFromCtx`) извлекает `user_id`:

    1. если передан заголовок `Identity` — id берётся из Zitadel-токена
       (claim `urn:zitadel:iam:user:metadata`, поле `user_id`, base64url);
    2. иначе — из числового claim `user_id` основного JWT.

    Подпись токена приложением **не проверяется** (проверка выполняется на
    уровне Istio по claim `token_type=access`). При отсутствии/непарсинге
    токена возвращается `401` с пустым телом.

    Изменение моделей статусов дополнительно требует прав администратора
    (проверка через Django, см. `ENDPOINTS.md`); при их отсутствии — `403`.

    Внутренние эндпоинты (`/internal/v1/*`) аутентификации на уровне
    приложения не требуют — доступ ограничен сетевым слоем (Istio, namespace).

    ### Идентификаторы
    Все path-параметры (`bim_id`, `project_id`, `company_id`, `sarex_id` и т.п.)
    парсятся как `uint64`; неверный формат → `400`.

    ### Формат ошибок
    Ошибки из обработчиков возвращаются как `{ "error": "<сообщение>" }`
    (`pkg/gotools/httperror`). Исключения: `401` от middleware (пустое тело),
    а также `404`/`405` от роутера (текст `404 route not found` /
    `405 method not allowed`).

servers:
  - url: https://api.sarex.io/bimv2
    description: production (через api-gateway, префикс api_host_prefix)
  - url: https://stage-api.sarex.io/bimv2
    description: stage
  - url: http://localhost:5555
    description: локальный запуск

tags:
  - name: bim-v1
    description: BIM, элементы, свойства, статусы (API v1)
  - name: metadata
    description: Метаданные BIM (API v1)
  - name: company-status-model
    description: Модели статусов компании (API v1)
  - name: internal-v1
    description: Внутренние эндпоинты (только внутри кластера)
  - name: bim-v2
    description: BIM v3 (API v2)
  - name: service
    description: Служебные эндпоинты

security:
  - bearerAuth: []

paths:
  /ping:
    get:
      tags: [service]
      summary: Health-check
      security: []
      responses:
        "200":
          description: Сервис работоспособен

  /api/v1/projects/{project_id}/bims:
    get:
      tags: [bim-v1]
      summary: Список BIM проекта
      parameters:
        - $ref: '#/components/parameters/ProjectId'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  bims:
                    type: array
                    items: { $ref: '#/components/schemas/Bim' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims:
    get:
      tags: [bim-v1]
      summary: Список BIM по набору id
      parameters:
        - name: bim_id
          in: query
          required: true
          description: Список id BIM через запятую (напр. `1,2,3`)
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/Bim' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}:
    get:
      tags: [bim-v1]
      summary: BIM по id
      parameters:
        - $ref: '#/components/parameters/BimId'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Bim' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/sarexid/{sarex_id}:
    get:
      tags: [bim-v1]
      summary: Элементы BIM по sarex_id (из пути)
      parameters:
        - $ref: '#/components/parameters/BimId'
        - name: sarex_id
          in: path
          required: true
          description: Список sarex_id через запятую
          schema: { type: string }
        - $ref: '#/components/parameters/WithHierarchy'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BIMElementsResult' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/sarexid:
    post:
      tags: [bim-v1]
      summary: Элементы BIM по sarex_id (из тела)
      parameters:
        - $ref: '#/components/parameters/BimId'
        - $ref: '#/components/parameters/WithHierarchy'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GetBimElementsRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BIMElementsResult' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/changes:
    get:
      tags: [bim-v1]
      summary: История изменений статусов элементов BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
        - { name: offset, in: query, required: false, schema: { type: integer, format: int64, default: 0 } }
        - { name: limit, in: query, required: false, schema: { type: integer, format: int64, default: 20 } }
        - { name: status_type, in: query, required: false, description: 'Типы статусов через запятую', schema: { type: string } }
        - { name: sarex_ids, in: query, required: false, description: 'sarex_id через запятую', schema: { type: string } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PaginatedChangesResponse' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }
    post:
      tags: [bim-v1]
      summary: Установить статус элементам BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
        - $ref: '#/components/parameters/WithHierarchy'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SetStatusBodyRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/SetStatusResponse' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/elements:
    get:
      tags: [bim-v1]
      summary: Дерево элементов BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
        - { name: depth, in: query, required: false, schema: { type: integer, format: int64, default: 5 } }
        - { name: root, in: query, required: false, schema: { type: integer, format: int64 } }
        - name: statuses
          in: query
          required: false
          description: 'Повторяемый параметр вида `type:value`'
          schema: { type: array, items: { type: string } }
        - { name: format, in: query, required: false, schema: { type: string, enum: [full, tiny], default: full } }
      responses:
        "200":
          description: 'При `format=full` элементы полные, при `format=tiny` — усечённые'
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items:
                      oneOf:
                        - { $ref: '#/components/schemas/BIMElement' }
                        - { $ref: '#/components/schemas/BIMElementTiny' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }
    post:
      tags: [bim-v1]
      summary: Отфильтрованные элементы BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GetFilteredElementsRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BIMElementsResult' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/statuses_color:
    get:
      tags: [bim-v1]
      summary: Цвета статусов BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
      responses:
        "200":
          description: 'Ключ — имя модели статусов'
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: array
                  items: { $ref: '#/components/schemas/StatusColorRequest' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/status_models:
    get:
      tags: [bim-v1]
      summary: Модели статусов BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/StatusCategory' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/status_model:
    post:
      tags: [bim-v1]
      summary: Создать модель статусов BIM (требует прав администратора)
      parameters:
        - $ref: '#/components/parameters/BimId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/StatusCategory' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OkResponse' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/delete_status_model:
    delete:
      tags: [bim-v1]
      summary: Удалить модель статусов BIM (требует прав администратора)
      parameters:
        - $ref: '#/components/parameters/BimId'
        - name: status_type
          in: query
          required: true
          description: 'Имя модели статусов. Значение по умолчанию (`building`) удалить нельзя'
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OkResponse' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/filter_fields:
    get:
      tags: [bim-v1]
      summary: Поля для фильтрации элементов BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
      responses:
        "200":
          description: 'Категория → свойство → дескриптор фильтра (multiselector/slider/checkbox)'
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: object
                  additionalProperties:
                    oneOf:
                      - { $ref: '#/components/schemas/StringsType' }
                      - { $ref: '#/components/schemas/MinMaxType' }
                      - { $ref: '#/components/schemas/BooleansType' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/properties:
    get:
      tags: [bim-v1]
      summary: Свойства всех элементов BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
      responses:
        "200":
          description: 'sarex_id → категория → свойство → значение. Пустой объект, если таблица свойств отсутствует'
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: object
                  additionalProperties:
                    type: object
                    additionalProperties: true
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/elements/{sarex_id}/properties:
    get:
      tags: [bim-v1]
      summary: Свойства одного элемента BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
        - { name: sarex_id, in: path, required: true, schema: { type: integer, format: int64 } }
      responses:
        "200":
          description: 'категория → свойство → значение'
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  type: object
                  additionalProperties: true
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/statuses:
    get:
      tags: [bim-v1]
      summary: Статусы элементов BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
        - name: statuses
          in: query
          required: false
          description: 'Повторяемый параметр вида `type:value`'
          schema: { type: array, items: { type: string } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items: { $ref: '#/components/schemas/BIMElementStatuses' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/filter_by_statuses:
    get:
      tags: [bim-v1]
      summary: sarex_id элементов, сгруппированные по статусам
      parameters:
        - $ref: '#/components/parameters/BimId'
        - name: statuses
          in: query
          required: false
          description: 'Повторяемый параметр вида `type:value`'
          schema: { type: array, items: { type: string } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/FilterElementsByStatusResponse' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/csv_properties:
    post:
      tags: [bim-v1]
      summary: CSV-отчёт по свойствам элементов BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GetCSVPropertiesReportRequest' }
      responses:
        "200":
          description: 'CSV-файл (разделитель `;`). Заголовки Content-Disposition: attachment; filename=<uuid>.csv'
          content:
            text/csv:
              schema: { type: string, format: binary }
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/metadata:
    get:
      tags: [metadata]
      summary: Метаданные BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/GetMetadataResponse' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/bims/{bim_id}/metadata/transform:
    patch:
      tags: [metadata]
      summary: Обновить матрицу трансформации BIM
      parameters:
        - $ref: '#/components/parameters/BimId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/SetMetadataTransformRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OkResponse' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v1/companies/{company_id}/status_model:
    post:
      tags: [company-status-model]
      summary: Создать модель статусов компании (требует прав администратора)
      parameters:
        - $ref: '#/components/parameters/CompanyId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/StatusCategory' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  result: { $ref: '#/components/schemas/CompanyStatusModel' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "500": { $ref: '#/components/responses/InternalError' }
    get:
      tags: [company-status-model]
      summary: Модель статусов компании
      parameters:
        - $ref: '#/components/parameters/CompanyId'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  result: { $ref: '#/components/schemas/CompanyStatusModel' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }
    delete:
      tags: [company-status-model]
      summary: Удалить модель статусов компании (требует прав администратора)
      parameters:
        - $ref: '#/components/parameters/CompanyId'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/OkResponse' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "403": { $ref: '#/components/responses/Forbidden' }
        "500": { $ref: '#/components/responses/InternalError' }

  /internal/v1/bims/{bim_id}/sarexid:
    post:
      tags: [internal-v1]
      summary: Элементы BIM по sarex_id (внутренний, без аутентификации приложения)
      security: []
      parameters:
        - $ref: '#/components/parameters/BimId'
        - $ref: '#/components/parameters/WithHierarchy'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GetBimElementsRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BIMElementsResult' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /internal/v1/projects/{project_id}/bims:
    post:
      tags: [internal-v1]
      summary: Создать BIM в проекте (внутренний)
      security: []
      parameters:
        - $ref: '#/components/parameters/ProjectId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateBimRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Bim' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /internal/v1/bims/{bim_id}/filter_elements_by_status:
    post:
      tags: [internal-v1]
      summary: Отфильтровать sarex_id по статусам (внутренний)
      security: []
      parameters:
        - $ref: '#/components/parameters/BimId'
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/GetFilteredSarexIDsByStatusRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  sarex_ids:
                    type: array
                    items: { type: integer, format: int64 }
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /internal/v1/bims/guid_sarex_id:
    get:
      tags: [internal-v1]
      summary: Сопоставление GUID → sarex_id (внутренний)
      security: []
      parameters:
        - name: bim_ids
          in: query
          required: true
          description: 'Список id BIM через запятую'
          schema: { type: string }
      responses:
        "200":
          description: 'Ключ — GUID элемента'
          content:
            application/json:
              schema:
                type: object
                additionalProperties: { $ref: '#/components/schemas/BimIDSarexID' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v2/bims:
    get:
      tags: [bim-v2]
      summary: Список BIM v3
      parameters:
        - { name: company_id, in: query, required: false, schema: { type: integer, format: int64 } }
        - { name: bundle_id, in: query, required: false, schema: { type: string, format: uuid } }
        - { name: document_id, in: query, required: false, schema: { type: integer, format: int64 } }
        - { name: bim_type, in: query, required: false, schema: { type: integer, format: int64 } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/BIMV3' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v2/bims/{bim_id}:
    get:
      tags: [bim-v2]
      summary: BIM v3 по id
      parameters:
        - $ref: '#/components/parameters/BimId'
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/BIMV3' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "404": { $ref: '#/components/responses/NotFound' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v2/bims/{bim_id}/elements:
    post:
      tags: [bim-v2]
      summary: Список элементов BIM v3 (с фильтрами по атрибутам)
      parameters:
        - $ref: '#/components/parameters/BimId'
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: '#/components/schemas/ListBIMElementsRequest' }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/BIMV3Element' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

  /api/v2/bims/{bim_id}/elements/{sarex_id}:
    get:
      tags: [bim-v2]
      summary: Элемент(ы) BIM v3 по sarex_id
      parameters:
        - $ref: '#/components/parameters/BimId'
        - { name: sarex_id, in: path, required: true, schema: { type: integer, format: int64 } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: array
                items: { $ref: '#/components/schemas/BIMV3Element' }
        "400": { $ref: '#/components/responses/BadRequest' }
        "500": { $ref: '#/components/responses/InternalError' }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        `Authorization: Bearer <jwt>` (или query-параметр `auth_jwt`). Дополнительно
        может передаваться заголовок `Identity` для Zitadel-токена.

  parameters:
    BimId:
      name: bim_id
      in: path
      required: true
      schema: { type: integer, format: int64 }
    ProjectId:
      name: project_id
      in: path
      required: true
      schema: { type: integer, format: int64 }
    CompanyId:
      name: company_id
      in: path
      required: true
      schema: { type: integer, format: int64 }
    WithHierarchy:
      name: with_hierarchy
      in: query
      required: false
      schema: { type: boolean, default: false }

  responses:
    BadRequest:
      description: Некорректный запрос
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: Не найдено
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Forbidden:
      description: Недостаточно прав (нужны права администратора)
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    InternalError:
      description: Внутренняя ошибка сервера
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }

  schemas:
    Error:
      type: object
      properties:
        error: { type: string }
      required: [error]

    OkResponse:
      type: object
      properties:
        ok: { type: boolean, example: true }

    Bim:
      type: object
      properties:
        id: { type: integer, format: int64 }
        project_id: { type: integer, format: int64 }
        document_id: { type: integer, format: int64 }
        created_at: { type: string, format: date-time }
        status: { type: string, description: 'BimStatusType, напр. Pending' }
        transform: { type: array, items: { type: number, format: double } }
        status_model:
          type: array
          items: { $ref: '#/components/schemas/StatusCategory' }
        properties_names: { type: array, items: { type: string } }
        category_properties:
          type: object
          additionalProperties:
            type: object
            additionalProperties: { type: integer, format: int64 }
        data_types: { type: array, items: { type: string } }
        categories_names: { type: array, items: { type: string } }
        company_id: { type: integer, format: int64, nullable: true }

    StatusCategory:
      type: object
      required: [name, verbose_name, initial_status, statuses]
      properties:
        name: { type: string }
        verbose_name: { type: string }
        initial_status: { type: string }
        statuses:
          type: array
          minItems: 1
          items: { $ref: '#/components/schemas/Statuses' }

    Statuses:
      type: object
      required: [verbose_name, color, permissions, name, allowed_transitions]
      properties:
        verbose_name: { type: string }
        color: { type: integer, description: '0..16777215 (RGB)' }
        permissions: { type: array, items: { type: string } }
        name: { type: string }
        allowed_transitions: { type: array, items: { type: string } }

    StatusColorRequest:
      type: object
      properties:
        name: { type: string }
        color: { type: integer }

    BIMElement:
      type: object
      description: 'Кастомная сериализация; hierarchy разворачивается в массив uint64'
      properties:
        sarex_id: { type: integer, format: int64 }
        name: { type: string }
        hierarchy: { type: array, items: { type: integer, format: int64 } }
        updated_at: { type: string, format: date-time, nullable: true }
        bim_id: { type: integer, format: int64 }
        statuses:
          type: object
          additionalProperties: { type: string }
        is_leaf: { type: boolean, nullable: true }
        extras_from_converter:
          type: object
          additionalProperties: true
          nullable: true
        bboxMax: { type: array, items: { type: number, format: double } }
        bboxMin: { type: array, items: { type: number, format: double } }
        color: { type: integer, nullable: true }

    BIMElementTiny:
      type: object
      properties:
        sarex_id: { type: integer, format: int64 }
        parent_id: { type: integer, format: int64, nullable: true }

    BIMElementStatuses:
      type: object
      properties:
        sarex_id: { type: integer, format: int64 }
        statuses:
          type: object
          additionalProperties: { type: string }

    BIMElementsResult:
      type: object
      properties:
        results:
          type: array
          items: { $ref: '#/components/schemas/BIMElement' }

    StringsType:
      type: object
      properties:
        values: { type: array, items: { type: string, nullable: true } }
        type: { type: string, enum: [multiselector, slider, checkbox] }

    MinMaxType:
      type: object
      properties:
        min: { type: number, format: double, nullable: true }
        max: { type: number, format: double, nullable: true }
        has_null: { type: boolean }
        type: { type: string, enum: [multiselector, slider, checkbox] }

    BooleansType:
      type: object
      properties:
        values: { type: array, items: { type: boolean, nullable: true } }
        type: { type: string, enum: [multiselector, slider, checkbox] }

    FilterFieldRequestStruct:
      type: object
      required: [category_name, property_name]
      properties:
        category_name: { type: string }
        property_name: { type: string }
        values: { type: array, items: {} }
        min: { type: number, format: double, nullable: true }
        max: { type: number, format: double, nullable: true }

    StatusFiltersRequestStruct:
      type: object
      properties:
        group: { type: string }
        values: { type: array, items: { type: string } }

    FiltersResponse:
      type: object
      properties:
        properties_filters:
          type: array
          items: { $ref: '#/components/schemas/FilterFieldRequestStruct' }
        status_filters:
          type: array
          items: { $ref: '#/components/schemas/StatusFiltersRequestStruct' }

    GetBimElementsRequest:
      type: object
      required: [sarex_ids]
      properties:
        sarex_ids:
          type: array
          minItems: 1
          items: { type: integer, format: int64 }

    GetFilteredElementsRequest:
      type: object
      required: [filters]
      properties:
        filters: { $ref: '#/components/schemas/FiltersResponse' }

    SetStatusBodyRequest:
      type: object
      required: [sarex_ids, new_status_type, new_status_value]
      properties:
        sarex_ids:
          type: array
          items: { type: integer, format: int64 }
        new_status_type: { type: string }
        new_status_value: { type: string }

    SetStatusResponse:
      type: object
      properties:
        count: { type: integer, format: int64 }

    PaginatedChangesResponse:
      type: object
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/ChangedBimElementDTO' }
        total: { type: integer, format: int64 }
        count: { type: integer, format: int64 }
        offset: { type: integer, format: int64 }
        limit: { type: integer, format: int64 }

    ChangedBimElementDTO:
      type: object
      properties:
        bim_id: { type: integer, format: int64 }
        sarex_id: { type: integer, format: int64 }
        created_at: { type: string, format: date-time }
        number: { type: integer, format: int64 }
        author: { type: integer, format: int64 }
        new_status:
          type: object
          additionalProperties: { type: string }
        old_statuses:
          type: object
          additionalProperties: { type: string }

    FilterElementsByStatusResponse:
      type: object
      properties:
        results:
          type: object
          additionalProperties:
            type: object
            additionalProperties: { $ref: '#/components/schemas/ElementsResponse' }

    ElementsResponse:
      type: object
      properties:
        sarex_ids:
          type: array
          items: { type: integer, format: int64 }

    GetCSVPropertiesReportRequest:
      type: object
      required: [filters]
      properties:
        filters:
          type: object
          properties:
            properties_filters:
              type: array
              items: { $ref: '#/components/schemas/FilterFieldRequestStruct' }
            statuses_filters:
              type: object
              additionalProperties:
                type: array
                items: { type: string }
        element_ids:
          type: array
          items: { type: integer, format: int64 }
        reported_category_property:
          type: object
          additionalProperties:
            type: array
            items: { type: string }
        reported_statuses:
          type: array
          items: { type: string }

    GetMetadataResponse:
      type: object
      properties:
        elements:
          type: object
          additionalProperties: { $ref: '#/components/schemas/MetaElement' }
        bim: { type: integer, format: int64 }
        created_at: { type: string, format: date-time }
        global_transformation_matrix:
          type: array
          items: { type: number, format: double }

    MetaElement:
      type: object
      properties:
        name: { type: string }
        hierarchy: { type: array, items: { type: integer, format: int64 } }
        bboxMax: { type: array, items: { type: number, format: double } }
        bboxMin: { type: array, items: { type: number, format: double } }
        children: { type: array, items: { type: integer, format: int64 } }
        color: { type: integer, nullable: true }

    SetMetadataTransformRequest:
      type: object
      required: [transform]
      properties:
        transform:
          type: array
          minItems: 16
          maxItems: 16
          items: { type: number, format: double }

    CompanyStatusModel:
      type: object
      properties:
        id: { type: integer, format: int64 }
        updated_at: { type: string, format: date-time, nullable: true }
        status_model: { $ref: '#/components/schemas/StatusCategory' }

    CreateBimRequest:
      type: object
      required: [document_id]
      properties:
        document_id: { type: integer, format: int64 }

    GetFilteredSarexIDsByStatusRequest:
      type: object
      properties:
        sarex_ids:
          type: array
          items: { type: integer, format: int64 }
        status_filters:
          type: array
          items: { $ref: '#/components/schemas/StatusFiltersRequestStruct' }

    BimIDSarexID:
      type: object
      properties:
        bim_id: { type: integer, format: int64 }
        document_id: { type: integer, format: int64 }
        sarex_id: { type: integer, format: int64 }
        path: { type: string }

    BIMV3:
      type: object
      properties:
        id: { type: integer, format: int64 }
        created_at: { type: string, format: date-time }
        company_id: { type: integer, format: int64 }
        bundle_id: { type: string, format: uuid }
        document_id: { type: integer, format: int64 }
        bim_type: { type: integer, description: '0 — BIM, 1 — Comparison' }
        transform: { type: array, items: { type: number, format: double } }
        reference_bundle_id: { type: string, format: uuid, nullable: true }
        compared_to_bundle_id: { type: string, format: uuid, nullable: true }

    BIMV3Element:
      type: object
      description: 'Кастомная сериализация; hierarchy разворачивается в массив uint64'
      properties:
        id: { type: integer, format: int64 }
        bim_id: { type: integer, format: int64 }
        sarex_id: { type: integer, format: int64 }
        name: { type: string }
        hierarchy: { type: array, items: { type: integer, format: int64 } }
        is_leaf: { type: boolean }
        bboxMin: { type: array, items: { type: number, format: double } }
        bboxMax: { type: array, items: { type: number, format: double } }
        attributes:
          type: object
          additionalProperties: true

    ListBIMElementsRequest:
      type: object
      properties:
        attributes_filters:
          type: array
          items: { $ref: '#/components/schemas/ListBIMElementsAttributesFilter' }

    ListBIMElementsAttributesFilter:
      type: object
      required: [id, values]
      properties:
        id: { type: integer, format: int64 }
        values:
          type: array
          minItems: 1
          items: {}
