Устройство кода
> Кратко. ТГИД — два репозитория: веб на 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 (от внешнего к внутреннему):
LocaleErrorMiddleware(api_i18n.py) — переводитdetailответов с ошибкой.CityPathMiddleware(city_context.py) — у API нескольких городов снимает префикс/city/<код>/и запоминает город запроса.MetricsMiddleware— метрики Prometheus (/metrics), если установленprometheus-client.ActivityMiddleware(activity.py) — журнал действий после ответа; параметрыtoken,key,passwordв адресе пишутся звёздочками.PiiMaskMiddleware— маскирует персональные данные в JSON-ответе, если у пользователя нет права «Персональные данные».DbRoleMiddleware— роль PostgreSQL запроса по токену (для RLS).AuthRequiredGetMiddleware— приAUTH_REQUIRED_GET=trueтребует токен на всех адресах, кроме входа,/health,/docs,/openapi.jsonи адресов, которые проверяют вход сами (/mcp,/api/arcgis/…,/api/v2/…, ключ встраивания).- 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/. Подробно — Несколько городов.
