Устройство кода

> Кратко. ТГИД — два репозитория: веб на Nuxt 3 (Vuetify, Pinia, MapLibre, Cesium) и API на FastAPI с базой города в PostgreSQL/PostGIS, очередью Celery и расчётным движком heatnet. Ниже — где что лежит, как проходит запрос и в каком виде API возвращает ошибки.

Два репозитория

Репозиторий Что в нём Запуск
веб (web-itwin) одностраничное приложение карты, полевое приложение, публичные страницы npm run dev, сборка — npm run build, служба — node .output/server/index.mjs
API (itwin-api) REST, MCP, OGC и FeatureServer, фоновые задачи, расчёт uvicorn main:app, воркер — celery -A worker.celery_app worker -P solo

Десктопный ТГИД и веб работают с одной базой города: таблицы сети (nodes, linesobj, heatpipesections, потребители, оборудование) и результаты расчёта (us_out, ut_out, pt_out …) общие. Свои таблицы веба называются tgid_*; их создаёт и доводит сам API при запуске (database/city_schema.py).

Веб

Где Что
pages/ index.vue — карта (всё приложение), field.vue — полевое приложение, embed.vue — встраиваемая карта, outages.vue — публичная карта отключений, s/ — короткие ссылки
components/ окна и панели; MapViewer.vue — карта и ленивое открытие окон (openLazyDialog)
stores/ Pinia: mapStore (карта MapLibre, запросы тайлов), layerStore, fragmentStore, authStore (вход и права), uiStore (requestToolDialog — открыть окно по ключу), cesiumStore (3D), liveStore (живая карта)
services/api/ клиент API по разделам: http.ts (ядро: адрес, повторы, разбор ошибок), auth.ts, journals.ts, calc.ts, webhooks.ts, embed.ts, telemetry.ts и др.; services/fastApiService.ts только собирает разделы
composables/ общая логика окон: журналы, рисование на карте, горячие клавиши, useMutationsEnabled
utils/ чистые функции: форматирование, стили слоёв, протокол встраивания (embedProtocol.ts), язык (i18n.ts)
locales/ словари ru.ts, kk.ts, en.ts
server/ серверная часть Nuxt: каталог слоёв GeoServer (server/api/geoserver-layers.ts), заголовки фреймов (server/middleware/frameAncestors.ts)
public/ статика, в том числе SDK встраивания tgid-embed.js

Карта — MapLibre GL (maplibre-gl 5), 3D — Cesium. Слои сети приходят векторными тайлами из martin или GeoServer, здания — из архивов PMTiles через API (см. Векторные тайлы). Подложка — MapTiler или своя.

Адрес API веб берёт из NUXT_PUBLIC_MAP_API_BASE_URL. Каждый запрос несёт Authorization: Bearer <токен входа> и Accept-Language с языком интерфейса — так ошибки приходят уже на нужном языке.

API

Где Что
main.py сборка приложения: слои middleware, обработчики ошибок, подключение роутеров, фоновые задачи при запуске
routers/ маршруты по разделам (63 файла): auth_routes.py, journals.py, calc.py, ogc.py, arcgis.py, api_v2.py, mcp.py, webhooks.py, embed.py, static_tiles.py …
database/ запросы и предметная логика (asyncpg); пул соединений — database/connect.py, у API нескольких городов — пул на город
auth.py, permissions.py вход (JWT), роли и права, зависимости get_current_user, require_roles, require_permission, require_mutations_enabled
worker.py задачи Celery: расчёт (run_sety_calculation), нормативные потери, выгрузки файлов, запекание тайлов (bake_static_tiles), износ
heatnet/, native/ расчётный движок heatnet (Python) и его ядро на Rust; старый движок — sety/
ai/ помощник ИИ и разборы (модель Claude)
tgid_mcp/ реестр инструментов MCP (tools.py) и протокол JSON-RPC (protocol.py); тот же реестр — у помощника ИИ и OGC Processes
api_i18n.py, api_messages.py перевод текстов ошибок на kk и en
sql/migrations/ изменения схемы базы города, их применяет city_schema
docs/ описания подсистем: mcp.md, ogc-api.md, arcgis-featureserver.md, webhooks-api-v2.md, embed-maps.md, martin.md, static-tiles.md, multi-city.md

Как проходит запрос

Слои middleware (от внешнего к внутреннему):

  1. LocaleErrorMiddleware (api_i18n.py) — переводит detail ответов с ошибкой.
  2. CityPathMiddleware (city_context.py) — у API нескольких городов снимает префикс /city/<код>/ и запоминает город запроса.
  3. MetricsMiddleware — метрики Prometheus (/metrics), если установлен prometheus-client.
  4. ActivityMiddleware (activity.py) — журнал действий после ответа; параметры token, key, password в адресе пишутся звёздочками.
  5. PiiMaskMiddleware — маскирует персональные данные в JSON-ответе, если у пользователя нет права «Персональные данные».
  6. DbRoleMiddleware — роль PostgreSQL запроса по токену (для RLS).
  7. AuthRequiredGetMiddleware — при AUTH_REQUIRED_GET=true требует токен на всех адресах, кроме входа, /health, /docs, /openapi.json и адресов, которые проверяют вход сами (/mcp, /api/arcgis/…, /api/v2/…, ключ встраивания).
  8. CORS (CORS_ALLOWED_ORIGINS).

Дальше — зависимости маршрута: вход и права (require_permission("journals"), require_roles("admin")), флаг записи MUTATIONS_ENABLED. Маршрут берёт соединение из пула города (acquire_conn), вызывает функцию из database/ и отдаёт JSON. Долгое (расчёт, выгрузка, запекание) уходит в Celery: ответ — task_id, ход — GET /api/v1/task/{task_id}.

Важно: многие адреса чтения (журналы, карточки /node, /line, реестры) своей проверки входа не имеют и закрыты только шлюзом AUTH_REQUIRED_GET. На сервере с доступом извне он должен быть включён.

Ошибки

Ошибка — HTTP-код и JSON:

Вид Пример
текст {"detail": "Дефект 999999999 не найден"}
объект с кодом (правки сети и карточек, импорт) {"detail": {"code": "version_conflict", "message": "…", "conflicts": […]}}
ошибки полей (422, pydantic) {"detail": [{"type": "less_than_equal", "loc": ["query", "limit"], "msg": "…", "ctx": {"le": 500}}]}

Коды: 401 — нет токена или он не принят, 403 — нет права (и отказ PostgreSQL по правам), 404, 409 — конфликт версий, 422 — неверный запрос, 503 — функция выключена флагом (MUTATIONS_ENABLED, MCP_ENABLED, ИИ). Свои форматы у стандартов: OGC Processes — RFC 7807 (application/problem+json), FeatureServer — HTTP 200 с {"error": {"code", "message"}}, MCP — ошибки JSON-RPC.

Язык ошибок

Язык берётся из cookie tgid_locale, иначе из Accept-Language (ru, kk, en). Если язык не передан, текст уходит как в коде: сообщения API — по-русски, ошибки полей pydantic — по-английски. Перевод делается на выходе: каталог api_messages.py хранит шаблоны исходных текстов с {имя} на месте подставленных значений; ошибки полей переводятся по коду ошибки pydantic. Ответ с переводом получает заголовок Content-Language. Текста нет в каталоге — он уходит как есть. Пример на копии Алматы:

curl -H "Accept-Language: kk" "$API/api/v2/defects/999999999"
# 404 {"detail":"999999999 ақауы табылмады"}
curl -H "Accept-Language: ru" "$API/api/v2/work-orders?limit=501"
# 422 {"detail":[{"type":"less_than_equal","loc":["query","limit"],"msg":"Значение должно быть не больше 500",…}]}

Успешные ответы слой не трогает. Ход и итог фоновых задач переводит адрес статуса задачи, протокол расчёта — адреса протоколов (каталог protocol_messages.py). Новый текст ошибки — добавить в api_messages.py перевод (тест tests/test_api_i18n.py проверяет, что тексты каталога есть в коде и подстановки {имя} совпадают).

Несколько городов

Один процесс API может обслуживать все города (TGID_CITY_DBS=код=база,…): город — по префиксу /city/<код>/ в пути, у каждого города свой пул, кеши и очередь Celery tgid.<город>. Без TGID_CITY_DBS API работает с одной базой (DB_NAME), и префикс не нужен — на таком стенде /city/almaty/… отвечает 404. За nginx адрес API города — /tgid/<город>/map-api/. Подробно — Несколько городов.

Хотите увидеть эту функцию на своих данных?Покажем ТГИД на демонстрации и ответим на вопросы инженеров.
Запросить демо