Skip to content

Техпланы — Справочник API

Общая информация

  • GitHub: TechCon-ML-Team/techcon_techplans_search
  • Версия: 1.0.0
  • Swagger UI: https://techplans.techcon-ml.ru/docs
  • ReDoc: https://techplans.techcon-ml.ru/redoc
  • OpenAPI JSON: https://techplans.techcon-ml.ru/openapi.json

Среды

Среда URL
Production https://techplans.techcon-ml.ru
Dev-stand http://localhost:8000 (docker compose)
Локальная http://localhost:8000 (uvicorn)

Аутентификация

Большинство эндпоинтов публичны. Bearer-токен (TECHPLANS_BEARER_TOKEN) требуется для административных эндпоинтов /reports (GET, PATCH) и инфраструктурного /metrics.

Матрица защиты

Эндпоинт Метод Требуется токен
GET /health GET Нет
GET / GET Нет
POST /search POST Нет
POST /reset POST Нет
GET /detail/{uid} GET Нет
GET /export GET Нет
GET /images/{filename} GET Нет
POST /reports POST Нет (rate-limit по IP)
GET /reports GET Да (Bearer)
PATCH /reports/{id} PATCH Да (Bearer)
GET /admin/reports GET Нет (токен вводится в UI)
GET /metrics GET Да (Bearer)

Общие ограничения

  • Rate limiting: POST /search — 150мс между запросами (per session_id); POST /reports — 5 заявок / 5 мин per IP
  • Максимальный размер запроса: 1 MB
  • Таймаут: 30s

Формат ответов

Все JSON-эндпоинты используют единую обёртку:

Успех

{"ok": true, "data": { "<содержимое>" }}

Ошибка

{"ok": false, "error": {"code": "<ERROR_CODE>", "message": "<описание>"}}

Коды ошибок

Код HTTP Описание
VALIDATION_ERROR 422 Невалидные параметры запроса
RATE_LIMITED 429 Превышен лимит запросов
UNAUTHORIZED 401 Отсутствует или неверный Bearer-токен
NOT_FOUND 404 Запись/заявка не найдена
INTERNAL_ERROR 500 Внутренняя ошибка сервера

Исключения из обёртки: HTML-фрагменты (HTMX-эндпоинты), бинарные файлы (XLSX, изображения).


Эндпоинты

Machine-to-Machine (JSON)


GET /health

Проверка доступности сервиса. Используется Gatus-мониторингом.

Аутентификация: не требуется.

Параметры запроса: нет.

Тело запроса: нет.

Ответ (200):

{
  "ok": true,
  "data": {
    "status": "ok",
    "total_records": 6231
  }
}
Поле Тип Описание
ok bool Всегда true при успешном ответе
data.status string Всегда "ok"
data.total_records int Количество записей, загруженных в память

Коды ошибок: 500 — внутренняя ошибка сервиса (не возвращается при нормальной работе; при падении сервис не отвечает вовсе).

Примеры:

curl -s https://techplans.techcon-ml.ru/health
# → {"ok":true,"data":{"status":"ok","total_records":6231}}
import httpx

r = httpx.get("https://techplans.techcon-ml.ru/health")
data = r.json()
assert data["ok"] is True
print(f"Records: {data['data']['total_records']}")

Web UI / HTMX

Эти эндпоинты используются браузерным интерфейсом через HTMX. Возвращают HTML-фрагменты (partials). При интеграции из кода (curl/CLI) ответ будет HTML, не JSON.


GET /

Главная страница. Возвращает полный HTML-документ с формой поиска и начальными результатами (первые 50 записей, сортировка по адресу).

Аутентификация: не требуется.

Cookie: session_id (TTL 24ч). Если отсутствует — создаётся автоматически, устанавливается в ответе (HttpOnly; SameSite=Lax).

Ответ: 200 text/html — полная страница.


POST /search

Поиск по базе техпланов. HTMX endpoint — возвращает HTML-фрагмент с результатами и OOB-обновлением счётчика.

Аутентификация: не требуется.

Content-Type: application/x-www-form-urlencoded

Rate limit: 150мс между запросами (per session_id cookie). При превышении — 429.

Параметры формы:

Поле Тип Required Описание
region list[str] нет Регион (множественный выбор, region=Москва&region=Тверь)
material_walls list[str] нет Материал стен (множественный выбор)
roof_type list[str] нет Тип кровли (множественный выбор)
floors_min str нет Минимальная этажность (numeric)
floors_max str нет Максимальная этажность (numeric)
entrances_min str нет Минимальное количество подъездов
entrances_max str нет Максимальное количество подъездов
apartments_min str нет Минимальное количество квартир
apartments_max str нет Максимальное количество квартир
area_min str нет Минимальная площадь, м²
area_max str нет Максимальная площадь, м²
series str нет Серия дома (нечёткий поиск, порог совпадения ≥ 50%)
address str нет Адрес (нечёткий поиск, порог совпадения ≥ 50%)
sort_by str нет Поле сортировки (default: Dirty_Address). Допустимые значения: UID, Region, Dirty_Address, material_walls, roof_type, floors, entrances, apartments, area_total, series, modification
sort_desc str нет "true" для сортировки по убыванию (default: "false")
page str нет Номер страницы (default: "1", 50 записей/страница)

Ответ (200): text/html — HTML-фрагмент с результатами (HTMX OOB swap). Содержит список карточек и обновление счётчика найденных записей.

Ответ (429): text/plain"Too many requests" — запрос отклонён rate limiter'ом.

Поведение при пустом результате: возвращает HTML-фрагмент с сообщением "Ничего не найдено" (не ошибка, не пустой ответ).

Сохранение фильтров в сессию: при каждом успешном запросе к /search активные фильтры сохраняются в сессию пользователя. Последующий GET /export использует эти же фильтры для выгрузки.

Примеры:

# Поиск по адресу
curl -X POST https://techplans.techcon-ml.ru/search \
  -d "address=Ленина" -d "page=1"

# Поиск по серии + этажность
curl -X POST https://techplans.techcon-ml.ru/search \
  -d "series=П-44" -d "floors_min=9" -d "floors_max=9" -d "page=1"

# Множественный выбор региона
curl -X POST https://techplans.techcon-ml.ru/search \
  -d "region=Москва" -d "region=Тверь" -d "page=1"

POST /reset

Сброс активных фильтров сессии. Возвращает полный перерисованный HTML-фрагмент формы и результатов.

Аутентификация: не требуется.

Content-Type: application/x-www-form-urlencoded

Тело запроса: нет (пустое).

Ответ (200): text/html — HTML-фрагмент с очищенной формой и начальными результатами (50 записей, без фильтров).

Примеры:

curl -X POST https://techplans.techcon-ml.ru/reset \
  -b "session_id=<your-session-id>"

GET /detail/{uid}

Детальная карточка записи. Возвращает HTML-фрагмент с полной информацией о техплане, фотографиями (фасад, план, главное фото).

Аутентификация: не требуется.

Параметры пути:

Параметр Тип Описание
uid string Уникальный идентификатор записи (поле UID в базе)

Параметры запроса:

Параметр Тип Required Описание
tab string нет Начальная вкладка фото: Photo_Main, Photo_Facade, Photo_Plan

Ответ (200): text/html — HTML-фрагмент с карточкой записи.

Ответ (404): text/html — сообщение об ошибке "Запись не найдена".

Примеры:

curl https://techplans.techcon-ml.ru/detail/abc-1234
curl "https://techplans.techcon-ml.ru/detail/abc-1234?tab=Photo_Facade"

GET /export

Выгрузка текущих результатов поиска сессии в XLSX-файл. Применяет те же фильтры что активны в сессии на момент запроса.

Аутентификация: не требуется.

Cookie: session_id — если отсутствует, выгружаются все записи без фильтров.

Параметры запроса:

Параметр Тип Required Описание
sort_by string нет Поле сортировки (default: Dirty_Address). Те же допустимые значения что у POST /search
sort_desc string нет "true" для сортировки по убыванию (default: "false")

Ответ (200): application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

Content-Disposition: attachment; filename=techplans_export.xlsx

Файл содержит колонки: ID, Регион, Адрес, Материал стен, Тип кровли, Этажность, Подъезды, Квартиры, Площадь м², Серия, Модификация, Ссылка на оцифровку.

Примеры:

curl -s https://techplans.techcon-ml.ru/export \
  -b "session_id=<your-session-id>" \
  -o techplans_export.xlsx

GET /images/{filename}

Отдача изображений техпланов (фасады, планы). Поддерживает форматы .webp, .jpg, .jpeg, .png.

Аутентификация: не требуется.

Параметры пути:

Параметр Тип Описание
filename string Путь к файлу относительно директории изображений

Ответ (200): бинарный контент изображения с соответствующим Content-Type.

Ответ (404): text/plain"Not found" — файл не найден (пробует расширения .webp/.jpg/.jpeg/.png).


/reports — Заявки об ошибках

Фича «Кнопка сообщить об ошибке». Хранилище заявок персистентно.


POST /reports

Подать заявку об ошибке в записи. Публичный — аутентификация не требуется.

Аутентификация: не требуется.

Rate limit: не более 5 заявок с одного IP в 5 минут. При превышении — 429.

Content-Type: application/json

Тело запроса:

Поле Тип Required Описание
record_id string да UID записи из базы техпланов
action_type string да Тип проблемы: delete, edit, content_correction
description string да Описание проблемы (минимум 10 символов)
user_info string нет Контактная информация (email, имя)

Ответ (201):

{
  "ok": true,
  "data": { "id": "<uuid>" }
}

Ответ (422): невалидные данные (отсутствуют обязательные поля, неверный action_type, описание короче 10 символов).

Ответ (429): превышен лимит заявок с IP.

Примеры:

curl -s -X POST https://techplans.techcon-ml.ru/reports \
  -H "Content-Type: application/json" \
  -d '{"record_id":"ABC-123","action_type":"content_correction","description":"Неверно указана этажность — в реальности 9, не 5"}'
import httpx

r = httpx.post("https://techplans.techcon-ml.ru/reports", json={
    "record_id": "ABC-123",
    "action_type": "content_correction",
    "description": "Неверно указана этажность — в реальности 9, не 5"
})
report_id = r.json()["data"]["id"]
print(f"Заявка создана: {report_id}")

GET /reports

Список всех заявок. Только администратор.

Аутентификация: Authorization: Bearer <TECHPLANS_BEARER_TOKEN>

Параметры запроса:

Параметр Тип Описание
status string Фильтр по статусу: pending, accepted, rejected. Если не передан — все заявки

Ответ (200):

{
  "ok": true,
  "data": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "record_id": "ABC-123",
      "action_type": "content_correction",
      "description": "Неверно указана этажность",
      "user_info": "user@example.com",
      "status": "pending",
      "created_at": "2026-04-08T10:00:00+00:00",
      "resolved_at": null,
      "resolver_note": null
    }
  ]
}

Ответ (401): токен не передан или неверен.

Примеры:

curl -s https://techplans.techcon-ml.ru/reports \
  -H "Authorization: Bearer $TOKEN"
import httpx

TOKEN = "..."
r = httpx.get("https://techplans.techcon-ml.ru/reports",
              headers={"Authorization": f"Bearer {TOKEN}"})
for report in r.json()["data"]:
    print(f"{report['id']}: {report['status']}")

PATCH /reports/{id}

Принять или отклонить заявку. Только администратор.

Аутентификация: Authorization: Bearer <TECHPLANS_BEARER_TOKEN>

Параметры пути:

Параметр Тип Описание
id string (UUID) Идентификатор заявки

Тело запроса:

Поле Тип Required Описание
status string да accepted или rejected
resolver_note string нет Комментарий администратора

Ответ (200): обновлённый объект Report (схема как в GET /reports, поля resolved_at и resolver_note заполнены).

Ответ (401): токен не передан или неверен.

Ответ (404): заявка с указанным id не найдена.

Примеры:

curl -s -X PATCH "https://techplans.techcon-ml.ru/reports/$REPORT_ID" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"status":"accepted","resolver_note":"Исправлено"}'
import httpx

TOKEN = "..."
r = httpx.patch(f"https://techplans.techcon-ml.ru/reports/{report_id}",
                headers={"Authorization": f"Bearer {TOKEN}"},
                json={"status": "accepted", "resolver_note": "Исправлено"})
print(r.json()["data"]["resolved_at"])

GET /admin/reports

Веб-интерфейс администратора для просмотра и обработки заявок.

Аутентификация: страница публичная; для загрузки данных вводится Bearer-токен на самой странице или передаётся через query param ?token=<TECHPLANS_BEARER_TOKEN>.

Ответ (200): HTML-страница с таблицей заявок.


Типовой сценарий (E2E)

Сценарий: поиск → экспорт → отчёт об ошибке

1. Проверить доступность сервиса:

curl -s https://techplans.techcon-ml.ru/health | jq .
import httpx

base = "https://techplans.techcon-ml.ru"
r = httpx.get(f"{base}/health")
assert r.json()["ok"] is True

2. Выполнить поиск (получить session_id):

curl -c cookies.txt -X POST https://techplans.techcon-ml.ru/search \
  -d "address=Ленина" -d "floors_min=5" -d "page=1"

3. Экспортировать результаты:

curl -b cookies.txt -o techplans.xlsx https://techplans.techcon-ml.ru/export

4. Подать заявку об ошибке в найденной записи:

r = httpx.post(f"{base}/reports", json={
    "record_id": "ABC-123",
    "action_type": "content_correction",
    "description": "Неверно указана этажность: в документе 5, фактически 9 этажей"
})
report_id = r.json()["data"]["id"]
print(f"Заявка создана: {report_id}")

5. Администратор: просмотреть и принять заявку:

TOKEN = "..."
headers = {"Authorization": f"Bearer {TOKEN}"}

# Все pending заявки
pending = httpx.get(f"{base}/reports?status=pending", headers=headers)
for rpt in pending.json()["data"]:
    print(f"  {rpt['id']}: {rpt['description']}")

# Принять
httpx.patch(f"{base}/reports/{report_id}", headers=headers,
            json={"status": "accepted", "resolver_note": "Этажность исправлена в базе"})