Files
darkorion 0f239d5015 feat: документация из Nextcloud, график у сотрудника, автоочистка фото, мониторинг, итог мастера, фильтр по местам; доп. сервисы в docker-compose
- docker-compose.yml: профили docs (Nextcloud), office (ONLYOFFICE Document Server), ai (Ollama + скачивание модели)
- автонастройка: адреса Nextcloud, ONLYOFFICE, портала и модели подставляются из .env или по именам сервисов
  во внутренней сети Docker; значения из интерфейса важнее
- локальная модель в своей сети (имя сервиса, частный IP) подключается без ключа API
- .env.example, docs/DEPLOY.md (раздел 7а), README
2026-10-13 13:27:20 +03:00

280 lines
32 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# API для клиентов (Android)
Базовый адрес: `https://<домен>/api/v1`. Интерактивная схема: `/docs`, машинная: `/openapi.json`.
Старый путь `/api/...` (без версии) работает как алиас, но в новых клиентах используйте `/api/v1`.
## Общие правила
- Формат — JSON, UTF-8. Ошибки: HTTP-код + `{"detail": "текст"}` (для 422 валидации `detail` — список `{loc, msg}`).
- **Время** — ISO 8601 со смещением: `2026-10-07T11:25:00+03:00`. На вход принимается любое смещение или `Z`;
без смещения время считается серверным локальным.
- Коды: 401 — нет/просрочен токен, 403 — нет прав, 404 — не найдено *или не видно вам*, 409 — конфликт данных,
422 — неверные данные, 429 — слишком много попыток входа (см. `Retry-After`).
## Авторизация
| Запрос | Назначение |
|---|---|
| `POST /auth/login` `{username, password, device?}` | → `{access_token, refresh_token, expires_in}` |
| `POST /auth/refresh` `{refresh_token}` | новая пара токенов; **старый refresh становится недействительным** |
| `POST /auth/logout` `{refresh_token}` | завершить сессию |
| `POST /auth/change-password` `{old_password, new_password}` | смена пароля, остальные сессии завершаются, возвращает новую пару |
| `GET /me` | профиль, должность (`position.kind`: technician/engineer), `on_shift`, `is_admin`, `is_moderator`, `demo_mode` (сессия в демо), `crane_map` (краны — отдельная служба) |
Заголовок: `Authorization: Bearer <access_token>`. Access живёт 60 мин (`ACCESS_TTL_MIN`), refresh — 90 дней.
Рекомендуемая схема клиента: при 401 один раз вызвать `/auth/refresh` (сериализуя параллельные вызовы!) и повторить запрос;
если и refresh вернул 401 — показать экран входа. refresh хранить в `EncryptedSharedPreferences`/Keystore.
Повторное предъявление уже использованного refresh трактуется как кража и отзывает **все** сессии пользователя.
После 5 неудачных входов с одного IP или на один логин вход блокируется на 5 минут.
## Наряды
| Запрос | Назначение |
|---|---|
| `GET /orders` | список видимых нарядов. Параметры: `status=new,in_progress`, `mine`, `overdue`, `to_review`, `equipment_id`, `q` (поиск по названию наряда, оборудованию или номеру; без учёта регистра), `updated_since`, `include_items`, `limit` (≤1000), `offset` |
| `GET /orders/{id}` | наряд с пунктами (`items[]`: тип ответа, варианты, границы, `photo`, `photos[]`, `value`) |
| `POST /orders/{id}/start` | взять в работу |
| `PUT /orders/{id}/items/{item_id}` `{value}` | ответ на пункт. status: `in_progress`/`done`; choice — значение из `options`; number — число (запятая допустима); пустое значение стирает ответ |
| `POST /orders/{id}/items/{item_id}/photos` | тело — файл, `Content-Type: image/jpeg\|png\|webp`; опционально `Idempotency-Key` |
| `GET /photos/{id}` | файл (нужен Bearer) |
| `POST /orders/{id}/submit` | сдать наряд |
| `POST /orders/{id}/sync` | пакетная отправка офлайн-данных (ниже) |
| `GET/POST /orders/{id}/comments`, `DELETE /comments/{id}` | комментарии |
| `POST /orders/{id}/review` `{approve, comment}` | проверка (флаг `can_review` в наряде) |
Права вычислять не нужно: в наряде есть `can_execute` и `can_review`.
Статусы: `new → in_progress → review → completed`, `cancelled`; `is_overdue` вычисляется сервером.
## Офлайн-режим
Типичный обход идёт там, где нет связи. Схема клиента:
1. **Загрузка.** При наличии сети: `GET /orders?include_items=true&status=new,in_progress` — наряды с чек-листами
в локальную БД. Справочники (`/equipment`, `/locations`) — по необходимости. Запомнить курсор
`max(updated_at)` по всем полученным нарядам.
2. **Инкрементально.** `GET /orders?include_items=true&updated_since=<курсор>` — вернёт только изменённое
(ответы, статусы, комментарии, фото двигают `updated_at`). Курсор — значение `updated_at` из ответа сервера,
а не часы телефона.
3. **Работа без сети.** Ответы копятся локально вместе с моментом ввода (`answered_at`), фото — в очереди с
сгенерированным UUID.
4. **Отправка.** `POST /orders/{id}/sync`:
```json
{"items": [{"item_id": 12, "value": "done", "answered_at": "2026-10-07T12:00:00+03:00"},
{"item_id": 13, "value": "4,5", "answered_at": "2026-10-07T12:01:10+03:00"}],
"submit": true}
```
Ответ: `results[]` по каждому пункту (`ok` / `stale` / `error` + `detail`), `submitted`, `submit_error`,
актуальный `order`. Ответы применяются независимо: ошибка одного не блокирует остальные.
`stale` — на сервере уже есть **более свежий** ответ (другой ввод позже по времени), значение не изменено.
Пакет можно безопасно отправить повторно. Фото: `POST .../photos` с заголовком `Idempotency-Key: <uuid>` —
повтор после обрыва связи не создаёт дубликат. Рекомендуется сначала отправить фото, затем `sync` с `submit`
(для обязательных фото иначе наряд не сдастся).
## Push (Android, FCM)
1. Админ кладёт JSON-ключ сервисного аккаунта Firebase на сервер и указывает путь в «Настройки сервера».
2. Приложение при входе и в `onNewToken` вызывает `POST /push/device` `{token, platform:"android"}`;
при выходе — `DELETE /push/device` с тем же телом.
3. Сообщения содержат `notification{title, body}` и `data{url}` (`/#/order/<id>`). Парсите id из `url`
либо открывайте `GET /orders?updated_since=...`.
Уведомления: новый наряд, наряд на проверку, возврат на доработку, комментарий.
## Графики работы (смены)
`GET/POST /schedules`, `PATCH/DELETE /schedules/{id}`. Три вида (`kind`): `weekly` (дни недели), `cycle` (N через M),
`pattern` — конструктор: `pattern` = список шагов `{type: "work"|"off", days, start_time, end_time, label}`, повторяется
по кругу от `cycle_start`. Конец смены раньше начала — ночная смена, равные время — сутки (08:00–08:00).
Пример «3 дня, отсыпной, 3 ночи, отсыпной, 3 выходных» — 5 шагов, цикл 11 дней.
`POST /schedules/preview` `{schedule, from_date, days}` — календарь без сохранения (для конструктора).
График назначается **сотруднику**: `schedule_id` в `POST/PATCH /users` и в `UserOut` (пусто — всегда на смене). `on_shift`
считается по графику сотрудника. Наряд на должность видят и получают уведомление только сотрудники этой должности, которые
сейчас на смене. `schedule_id` у должности устарел и не используется (при обновлении перенесён сотрудникам).
## Отчёты (инженер/модератор/админ)
`GET /reports` — список отчётов. `GET /reports/completed-orders?from=…&to=…` — **период обязателен** (дата и время).
Фильтры: `equipment_id`, `position_id` (должность исполнителя **на момент выполнения**), `user_id`, `include_review`.
`format=json|pdf|csv` (pdf — файл; csv — UTF-8 с BOM, разделитель `;`, открывается в Excel).
`GET /reports/execution?from=…&to=…` — «Выполнение работ» по машинам. `GET /reports/people?from=…&to=…` — «По сотрудникам»:
фильтры `department_id` (с вложенными), `position_id`, `user_id`; `format=json|pdf|csv|csv_calendar`. В JSON: `days`, `totals`,
`rows: [{user_id, name, position, department, done, orders_done, tasks_done, tasks_total, tasks_open, on_time_pct, overdue, returns,
deviations, tasks_cancelled, tasks_paused, hours, calendar: {"2026-10-12": {n, h}}}]`. Трудоёмкость задачи — `labor_hours` (норма),
если задана, иначе фактическое время «в работе»; наряда — от начала до сдачи.
## Регламентное обслуживание (узлы машин, наработка)
Чтение — любому авторизованному; изменение — инженер, модератор, администратор.
| Метод | Путь | Описание |
|---|---|---|
| GET | `/equipment/{id}/service-units` | Узлы машины с расчётом: `state` (`ok`/`soon`/`due`/`nodata`), `percent`, `remaining_hours`, `remaining_days`, `due_date`, `forecast_date`, `rule` |
| POST | `/equipment/{id}/service-units` | Создать узел: `name`, `mode` (`hours`/`calendar`/`both`), `interval_hours`, `interval_value` + `interval_unit` (`day`/`week`/`month`), `warn_percent`, начальное состояние `last_service_at`, `last_service_hours` |
| PATCH | `/service-units/{id}` | Изменить правило, название, `is_active` |
| DELETE | `/service-units/{id}` | Удалить узел (пункты старых нарядов остаются) |
| POST | `/service-units/{id}/reset` | Сброс: `{at?, hours?, note}` — обслужено отдельно от наряда / при внештатной ситуации |
| GET | `/service-units/{id}/events` | История обслуживания: `initial` / `order` / `reset` |
| POST | `/equipment/{id}/meter` | Показание наработки `{hours, at?, correction?, note}`; меньше текущего — только с `correction: true` |
| GET | `/equipment/{id}/meter` | История показаний |
| GET | `/reports/equipment-state` | Отчёт «Текущее состояние»: `equipment_id`, `location_id`, `state` (`due`/`soon`/`attention`/`ok`), `include_inactive`, `format` (`json`/`pdf`/`csv`) |
Чек-лист: поля `kind` (`inspection` | `regular`) и `only_due`. Для `regular` наряд требует `equipment_id`; в наряде появляются пункты с
`is_meter: true` (число, м/ч) и `service_unit_id` (статус `done` = узел обслужен). При завершении наряда сервер обновляет наработку и сбрасывает узлы.
## Инциденты и сообщения о неисправности
Одна сущность с полем `kind`: `incident` — авария, оборудование остановлено (машина в состоянии `alarm`); `fault` — сообщение о
неисправности: замечена проблема, но оборудование работает (состояние `fault`). Замена узлов (`replaced`) у `fault` не применяется.
- `POST /api/v1/incidents` `{kind, equipment_id, title, description?, occurred_at?, units: [{unit_id, replaced}], replaced?, replacement_note?}`.
- `GET /api/v1/incidents?kind=incident|fault&status=open|closed&mine&source&cranes&q&equipment_id`.
- `POST /api/v1/incidents/{id}/close` — закрыть (автор или инженер). Тело (всё, кроме `note`, необязательно; для отказа незаполненное
подставляется: начало работ — момент отказа, ввод — сейчас, наработка — снимок при регистрации, причина — `unknown`):
`{note, repair_started_at, restored_at, parts_wait_hours, hours_at_failure, meter_at_restore, cause_unit_id | cause_unit_other,
action: repair|replace|adjust|restart, nature: sudden|gradual, cause: wear|operation|external|power|defect|secondary|unknown,
precursor: inspection|fault_report|none, replaced_units: [unit_id]}`. Проверка хронологии: отказ ≤ начало работ ≤ ввод ≤ сейчас.
`meter_at_restore` записывается показанием счётчика; `replaced_units` сбрасывают ресурс узлов.
- `PATCH /api/v1/incidents/{id}/failure` — те же поля: инженер или руководитель уточняет данные отказа (и у закрытой записи).
- При регистрации сохраняется `hours_at_open` (наработка машины); при переводе неисправности в отказ — `escalated_at`, `hours_at_failure`
и `precursor: fault_report`. В интерфейсе сайта `kind=incident` называется «Отказ».
- `POST /api/v1/incidents/{id}/escalate` `{note}` — перевести сообщение о неисправности в инцидент (оборудование остановилось).
- `GET /api/v1/equipment-health` → `{id: {state: alarm|fault|due|soon|ok, open_incidents, open_faults, due, soon}}`;
приоритет состояния: авария > неисправность > требуется ТО > скоро ТО. То же состояние — в `GET /map`.
## Анализ надёжности (инженер, руководитель; настройка `reliability`)
- `GET /api/v1/reliability?from&to&department_id&cranes&equipment_id&confidence=0.9&horizon_days=30` — показатели за период:
`fleet` (по парку) и `rows` по машинам: `hours` (наработка; `basis: meter|calendar`), `failures`, `faults`, `escalated`, `lambda`,
`lambda_upper`, `theta`, `theta_lower`, `theta_median`, `mttr`, `repair_mean`, `wait_mean`, `downtime`, `availability`,
`forecast_mu`, `forecast_p`; разбивки `causes`, `natures`, `actions`, `precursors`, `cause_units`, `months`.
Оценки по малоотказному опыту: λ_верх = χ²(Pα; 2n+2) / 2T (при n = 0 — −ln(1−Pα)/T).
- `GET /api/v1/reliability/drift?days=180&horizon=90` — числовые пункты чек-листов с нормой: тренд, `days_to_bound`, `out_of_range`, `nu_per_30d`.
- `GET /api/v1/reliability/export?kind=failures|service&from&to` — CSV (UTF-8 с BOM, `;`): отказы и неисправности / история обслуживания узлов.
- `GET /api/v1/reliability/dictionaries` — справочники причин, характера, действий, признаков. В `GET /me` — флаг `reliability`.
## Инциденты извне (внешние системы и публичная форма)
Внешняя система (диспетчерская, SCADA, сервис заявок) заводит инцидент по **ключу доступа**. Ключ выпускает администратор:
«Администрирование → Настройки сервера → Ключи доступа к API» (ключ показывается один раз; в БД хранится только хэш; отзыв — там же).
- `GET /api/v1/external/equipment` — справочник машин: `[{id, name, inventory_no, location}]`.
- `POST /api/v1/external/incidents` — завести инцидент. Заголовок `X-API-Key: cl_…`. Тело:
```json
{"inventory_no": "НС-402", "title": "Перегрев двигателя", "description": "t обмотки 135 °C",
"occurred_at": "2026-10-09T11:20:00+03:00", "reporter_name": "SCADA цех 1", "reporter_contact": "+7 …",
"external_id": "ALARM-1234", "kind": "incident"}
```
`kind`: `incident` (по умолчанию) или `fault` — сообщение о неисправности. Машина — `equipment_id` (id портала) или `inventory_no`. Повторный запрос с тем же `external_id` от того же ключа вернёт уже
созданный инцидент (без дубликата). Ответ — инцидент (`source: "api"`). Ошибки: 401 — нет/неверный/отозванный ключ,
404 — машина не найдена, 422 — неверные данные.
Публичная форма **`/report`** (без входа) использует `GET /api/v1/public/report-options` (места и машины) и
`POST /api/v1/public/incidents` (`location_id`, `equipment_id`, `description`, `reporter_name`, `reporter_contact` — обязательны,
`kind` — `fault` «работает, но есть неисправность» или `incident` «остановлено, авария»;
машина должна стоять в выбранном месте или вложенном в него). Не более 5 отправок с адреса за 10 минут. Форму выключает
администратор (настройка `public_incidents`).
## Задачи и поручения (бывшие «суточные задания»)
`GET /api/v1/tasks` — фильтры `day`, `from`, `to`, `assignee_id`, `equipment_id`, `status`, `mine` (я исполнитель), `created_by_me`
(поставил я), `active`. `POST/PATCH /api/v1/tasks` — поле `labor_hours` (трудоёмкость-норма, ч, необязательно; 0–1000).
`POST /api/v1/tasks/{id}/transfer` `{"assignee_id": 7, "comment": "причина"}` — исполнитель, поставивший задание или инженер;
незавершённое задание у нового исполнителя становится «назначен»; в истории — событие `kind: "transfer"`.
## Оборудование: отделы и краны
У оборудования поля `department_id` (за каким отделом закреплено) и `is_crane` (кран). Сотрудник с отделом видит оборудование своего
отдела и вложенных, а также без отдела; администратор, модератор и сотрудник без отдела — всё. Это касается `GET /equipment`,
`GET /incidents`, `GET /equipment-health`, `GET /map`, отчёта о состоянии узлов и уведомлений об инцидентах.
При удалении отдела его оборудование переходит к вышестоящему отделу.
Краны — отдельная служба: настройка `crane_map` (`PUT /api/v1/settings {"crane_map": true}`, в `GET /me` — `crane_map`).
Тогда `GET /map` (`kind=plant`) показывает оборудование без кранов, `GET /map?kind=crane` — только краны (при выключенной настройке — 404);
картинка карты кранов: `GET/PUT /map/image?kind=crane` (если своей нет — отдаётся план завода; в ответе `/map` — `own_image`).
`GET /incidents?cranes=true|false` — только краны / всё, кроме кранов. Также у инцидентов фильтры `mine`, `source=staff|public|api`, `q`.
## История машины
`GET /api/v1/equipment/{id}/history?types=incident,service,meter,order,task&from=2026-10-01&to=2026-10-09&q=текст` —
хронология (новые сверху): `{total, counts, rows: [{at, type, type_ru, title, text, user_name, order_id?, task_id?, incident_id?}]}`.
## Отделы
`GET/POST /api/v1/departments`, `GET/PATCH/DELETE /api/v1/departments/{id}` — `{name, parent_id, head_user_id, notes}`, в ответе `members`
(число активных сотрудников). Править — модератор/админ. У пользователя поле `department_id` (в ответе ещё `department: {id, name}`).
При удалении отдела его сотрудники остаются без отдела, вложенные отделы поднимаются на уровень выше.
## Резервные копии (администратор)
- `GET /api/v1/backups` — список копий и настройки автокопирования (`auto_hours`, `keep`, `media`, `next_auto`).
- `POST /api/v1/backups` `{"include_media": false, "note": "перед обновлением"}` — создать копию.
- `GET /api/v1/backups/{name}` — скачать zip; `DELETE /api/v1/backups/{name}` — удалить.
- `POST /api/v1/backups/upload` (тело — zip) — загрузить копию, например с другого сервера.
- `POST /api/v1/backups/{name}/restore` `{"confirm": true}` — заменить ВСЕ данные продуктовой базы содержимым копии
(текущее состояние сначала сохраняется копией `…_before-restore.zip`; после восстановления все входят заново).
Состав zip: `manifest.json`, `db/<таблица>.json` (переносимый формат — PostgreSQL ↔ SQLite), `config/environment.json`
(настройки сервера и несекретные переменные окружения; секреты не сохраняются — только их названия), `media/…` — фото и картинки,
если выбрано. Автокопии: настройки `backup_auto_hours` (0 — выключено), `backup_keep`, `backup_media` в `PUT /api/v1/settings`.
Копии лежат в `DATA_DIR/backups` (в Docker — том `appdata`). В демо-режиме копирование и восстановление недоступны.
## Демонстрационный режим (личный)
Демо включается для конкретного пользователя: его запросы идут в отдельную тестовую базу, остальные работают с рабочей.
Тип базы определяется токеном: после включения/выключения клиент получает новую пару токенов (или обновляет её через `/auth/refresh`)
и должен очистить локальные данные, если `GET /me` → `demo_mode` изменился.
- `GET /demo/status` (без входа) → `{active}` — есть ли демо-данные (для подсказки на экране входа).
- `GET /demo/me` → `{in_demo, can_leave}`; `POST /demo/leave` — выйти из демо (возвращает токены рабочей базы).
- Администратор: `GET /demo` — кто в демо и тестовые учётные записи; `POST /demo/enable {"confirm": true}` — войти самому (токены);
`POST /demo/users/{id} {"on": true|false}` — включить/выключить демо другому сотруднику; `POST /demo/reset` — пересоздать
тестовые данные; `POST /demo/destroy` — удалить их (все возвращаются к рабочим данным); `POST /demo/impersonate {"user_id"}` —
войти под тестовым пользователем (только из демо-сессии).
## Документы
`/guide.pdf` — руководство пользователя, `/presentation.pdf` и `/presentation.pptx` — презентация, `/app.apk` — приложение для Android,
`/report` — публичная форма заявки (все без входа).
## Обратная связь и администраторы портала
- `POST /api/v1/feedback` `{kind: idea|bug|other, text, page?, client: web|m|android}` — любой сотрудник (не больше 20 в час).
- `GET /api/v1/feedback?status=open|new|taken|done|rejected`, `PATCH /api/v1/feedback/{id}` `{status?, answer?}`, `DELETE /api/v1/feedback/{id}` —
только **администраторы портала**.
- Администратор портала: `is_admin` и (`portal_admin` или логин `ADMIN_USERNAME`, обычно admin). В `GET /me` — `portal_admin`, `is_root`.
Флаг `portal_admin` у пользователя меняет только admin; admin нельзя лишить прав администратора или отключить.
- Только администраторам портала доступны `PUT /settings` (кроме отметок мастера `setup_done`), полный `GET /settings`, `/api-keys`,
`/assistant/config` и `/assistant/test`. Остальные администраторы получают в `GET /settings` сокращённый ответ (`limited: true`).
## Документация оборудования (Nextcloud)
Портал читает Nextcloud по WebDAV от служебной учётной записи (пароль приложения, только чтение) и сам отдаёт файлы;
Office переводится в PDF через ONLYOFFICE Document Server (ConvertService).
- `GET/PUT /api/v1/docs/config` — **администратор портала**: `nc_url, nc_user, nc_password, nc_root, onlyoffice_url, onlyoffice_jwt,
portal_internal_url, docs_ai`. Пароль и секрет JWT в ответе не возвращаются (`nc_password_set`, `onlyoffice_jwt_set`) и не попадают
в резервные копии. `POST /api/v1/docs/test` — проверка Nextcloud и ONLYOFFICE.
- `GET /api/v1/docs/browse?path=` — администратор или модератор: вложенные папки (с `linked` — к каким машинам привязаны) и число файлов.
- `PUT /api/v1/equipment/{id}/docs` `{path}` — администратор или модератор: привязать папку (пустой `path` — отвязать). В `EquipmentOut` — `docs_path`.
- `GET /api/v1/equipment/{id}/docs?sub=` — кто видит машину: `{folder, sub, items: [{name, path, is_dir, kind: dir|pdf|image|text|office|file, size, mtime}]}`.
- `GET /api/v1/equipment/{id}/docs/file?path=...&view=1` — файл; `view=1` — для просмотра: PDF, картинки, текст — inline,
Office — PDF через ONLYOFFICE (кэшируется). Без `view` — скачивание оригинала. Пути `..` отклоняются (422).
- `GET /api/v1/docs/raw/{token}` — без входа, только для ONLYOFFICE: подписанная ссылка на исходник, живёт 10 минут.
- В `GET /me` — `docs: true`, если Nextcloud подключён.
Опция AI-помощника **«Работа с документацией»** (`docs_ai`): раз в сутки портал строит индекс (имя и начало текста файлов,
PDF — через pypdf, docx/xlsx/pptx/odt — из XML; файлы больше 60 МБ — только по имени), `POST /api/v1/docs/reindex` — вручную
(администратор портала). `GET /api/v1/docs/search?q=` — поиск по индексу среди машин, которые видит пользователь (только при
включённой опции). Помощник получает инструменты `search_docs` и `equipment_docs`. Фрагменты документов уходят провайдеру ИИ —
для закрытой документации нужна локальная модель.
## Фото: «хранить более года» и автоочистка
- `photo_keep` у пункта чек-листа (`ChecklistItemIn`), копируется в пункт наряда (`OrderItemOut.photo_keep`); у задания —
`photo_keep` в `POST/PATCH /tasks` и `TaskOut`. Такие фото сохраняются в каталог `keep/` и очисткой не удаляются.
- Настройка `photo_keep_days` (`PUT /settings`, администратор портала): раз в сутки удалять фото нарядов и заданий старше N дней,
0 — выключено (по умолчанию). `GET /photo-cleanup` — сколько удалится сейчас (ничего не удаляет), `POST /photo-cleanup` — очистить сразу.
В `GET /settings` — `photo_keep_stats` (сколько фото в `keep/`).
## Мониторинг сервера
`GET /api/v1/server/monitor` (администратор портала) → `{cpu: %, cpu_count, load: [1, 5, 15 мин] | null,
memory: {used, total, scope: host|container}, disks: [{labels, path, used, total, free}], now}`.
## AI-помощник
Включает администратор: «Администрирование → Настройки сервера → AI-помощник» (`GET/PUT /api/v1/assistant/config`:
`enabled, provider (openai | anthropic | gigachat), base_url, model, api_key, scope`; ключ в ответах не возвращается и не попадает
в резервные копии). Проверка: `POST /api/v1/assistant/test`.
- `GET /api/v1/assistant/status` → `{enabled}` — показывать ли кнопку помощника.
- `POST /api/v1/assistant/chat` `{"messages": [{"role": "user", "content": "Как зафиксировать аварию?"}]}` → `{reply, offtopic, tools}`.
Помощник отвечает только о работе в портале (иначе — вежливый отказ, `offtopic: true`), знает роль пользователя и читает данные
только через этот же API от имени пользователя (его задания, наряды, инструмент, оборудование, инциденты) — ничего не меняет.
Ограничения: вопрос до 1000 символов, история — последние 10 сообщений, не более 30 вопросов в час на пользователя.
OpenAI-совместимый провайдер подходит для OpenAI, YandexGPT (`https://llm.api.cloud.yandex.net/v1`), DeepSeek, OpenRouter
и локальных моделей (Ollama: `http://localhost:11434/v1`, ключ не нужен).
## Версия Android-приложения
`GET /api/v1/app/version` (без входа) → `{available, version, size, url}` — версия берётся из имени файла `checklists-<версия>.apk`.
Приложение проверяет её при запуске и предлагает скачать обновление, если на сервере версия новее.