Техпланы — Справочник 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=Москва®ion=Тверь) |
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": "Этажность исправлена в базе"})