Для разработчиков

О!кно как блок для Островка

Всё, что вы видите в О!кне, работает через HTTP API с контрактом OpenAPI. Наш сайт только один из клиентов API: те же блоки Островок может встроить в свой продукт по одному.

  1. 1. ДанныеOpenStreetMap, рельеф Copernicus GLO-30, погода и ответы отельеров. У каждого значения источник, дата и срок годности.
  2. 2. Агенты и инструментыИнструменты считают вид, путь до моря и шум. Агенты собирают характеристики и спрашивают отельера. Языковая модель факты не считает.
  3. 3. APIHTTP и JSON по контракту OpenAPI. Клиенты: наш сайт, кабинет отельера и продукт Островка.

Гид для интегратора с теми же примерами лежит в репозитории: docs/api.md.

Быстрый старт за пять запросов

Адрес сайта: $OKNO_API, по умолчанию http://localhost:8080. Запросы без cookie открыты с любого клиента. Полный справочник методов: Swagger UI по ссылке слева.

  1. 1. Проверить, что сервис жив и готов

    Без входа. Готовность значит: база доступна и миграции применены. Статус показывает, подключён ли ИИ: llm on или off.

    Запрос
    export OKNO_API=http://localhost:8080
    curl -s "$OKNO_API/api/v1/health"
    curl -s "$OKNO_API/api/v1/ready"
    curl -s "$OKNO_API/api/v1/status"
  2. 2. Найти варианты по фразе гостя

    Без входа и без cookie: такой вызов можно делать с сервера партнёра. В ответе потребности, варианты «отель × сторона × этаж», группы «не подтверждено» и «не подошли».

    Запрос
    curl -s -X POST "$OKNO_API/api/v1/search" \
      -H "Content-Type: application/json" \
      -d '{"query": "тихий номер с видом на закат недалеко от моря", "dates": {"from": "2026-07-10", "to": "2026-07-14"}}'
  3. 3. Взять факты для карточки и «Как мы это узнали»

    Значение, источник, метод, дата, срок годности и уверенность. Профиль горизонта и путь солнца для стороны и этажа.

    Запрос
    curl -s "$OKNO_API/api/v1/hotels/$HOTEL_ID"
    curl -s "$OKNO_API/api/v1/hotels/$HOTEL_ID/characteristics/CH-SUNSET"
    curl -s "$OKNO_API/api/v1/hotels/$HOTEL_ID/view?side=ЮЗ&floor=5"
  4. 4. Войти по роли и читать кабинет

    В демо-режиме вход одной кнопкой: роль guest, hotelier или admin. Сервер ставит cookie сессии, её значение в заголовке Set-Cookie.

    Запрос
    curl -s -D - -X POST "$OKNO_API/api/v1/auth/demo-login" \
      -H "Content-Type: application/json" -d '{"role": "hotelier"}'
    # Set-Cookie: sid=...
    curl -s "$OKNO_API/api/v1/hotelier/questions" -b "sid=$OKNO_SID"
  5. 5. Изменить что-то от имени сессии

    Изменяющий запрос с cookie принимается только с Origin сайта: так защищён кабинет. Без cookie Origin не нужен.

    Запрос
    curl -s -X POST "$OKNO_API/api/v1/hotelier/questions/$QUESTION_ID/answers" \
      -b "sid=$OKNO_SID" -H "Origin: $OKNO_API" \
      -H "Content-Type: application/json" \
      -d '{"text": "Номера 301-310 смотрят на запад, море видно с 3-го этажа", "from_audio": false}'

Блоки API

Шесть блоков, которые можно брать по одному. $OKNO_SID это значение cookie сессии после входа по роли (POST /api/v1/auth/demo-login). Примеры ответов из контракта.

Гость, без входа

Фраза гостя превращается в условия поиска и варианты «отель × сторона × этаж» с причинами и уверенностью. Блок встаёт рядом с обычными фильтрами Островка.

  • POST/api/v1/searchРазбор запроса и выдача
  • GET/api/v1/search/suggestionsУмные чипы главного экрана
Запрос
curl -s -X POST "$OKNO_API/api/v1/search" \
  -H "Content-Type: application/json" \
  -d '{"dates":{"from":"2026-05-03","to":"2026-05-07"},"needs_override":null,"query":"Закат над морем из окна и тихо ночью","use_memory":false}'
Ответ, сокращён
{
  "data_mode": "real",
  "dates": {
    "from": "2026-05-03",
    "to": "2026-05-07"
  },
  "explanations_pending": true,
  "needs": [
    {
      "characteristic_id": "CH-SUNSET",
      "condition": {
        "op": "sea_on_dates",
        "value": null
      },
      "id": "n1",
      "importance": "must",
      "…": "ещё 2"
    }
  ],
  "…": "ещё 6"
}

Факты для карточки отеля

Гость, без входа

Готовые отметки для карточки: значение, источник, дата, срок годности и уверенность. Например, «Закат над морем с 5-го этажа, расчёт».

  • GET/api/v1/hotels/{hotel_id}Карточка отеля
  • GET/api/v1/hotels/{hotel_id}/characteristics/{ch_id}Как мы это узнали: провенанс значения
Запрос
curl -s -X GET "$OKNO_API/api/v1/hotels/$HOTEL_ID"
Ответ, сокращён
{
  "building": {
    "height_confidence": 0.75,
    "height_m": 28,
    "height_source": "osm_levels",
    "levels": 9,
    "…": "ещё 1"
  },
  "data_mode": "real",
  "facts": [
    {
      "characteristic_id": "CH-SEA-VIEW",
      "confidence": 0.86,
      "confidence_label": "high",
      "display": "Море видно с 2-го этажа, сектор 62°",
      "…": "ещё 8"
    }
  ],
  "hotel": {
    "address": "Курортный проспект, 18",
    "district": "center",
    "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
    "kind": "hotel",
    "…": "ещё 6"
  },
  "…": "ещё 3"
}

Вид из окна

Гость, без входа

Профиль горизонта и путь солнца для стороны и этажа. Подходит для блока «Вид из окна» в карточке номера.

  • GET/api/v1/hotels/{hotel_id}/viewВид из окна
Запрос
curl -s -X GET "$OKNO_API/api/v1/hotels/$HOTEL_ID/view?side=%D0%A1&floor=5"
Ответ, сокращён
{
  "azimuth_from": 145,
  "azimuth_to": 305,
  "confidence": 0.82,
  "confidence_label": "high",
  "data_mode": "real",
  "dates": {
    "from": "2026-05-03",
    "to": "2026-05-07"
  },
  "floor": 5,
  "floors_count": 9,
  "floors_summary": [
    {
      "floor": 1,
      "sea_sector_deg": 86,
      "sunset_over": "buildings"
    }
  ],
  "horizon_deg": [
    0.32
  ],
  "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
  "mountain_mask": [
    false
  ],
  "…": "ещё 7"
}

Диалог с отельером

Отельер, по сессии

Агент спрашивает отельера о том, чего нет в открытых данных, и разбирает ответ по полям. Встраивается в кабинет партнёра как раздел «Вопросы».

  • GET/api/v1/hotelier/questionsЛента вопросов агента
  • POST/api/v1/hotelier/questions/{question_id}/answersОтветить на вопрос
  • POST/api/v1/hotelier/answers/{answer_id}/confirmПодтвердить черновик ответа
Запрос
curl -s -X GET "$OKNO_API/api/v1/hotelier/questions" \
  -b "sid=$OKNO_SID"
Ответ, сокращён
{
  "data_mode": "real",
  "items": [
    {
      "characteristic_ids": [
        "CH-ROOM-SIDES"
      ],
      "created_at": "2026-10-23T07:00:00Z",
      "evidence": [],
      "fields_schema": {
        "properties": {
          "rooms": {
            "type": "string"
          },
          "side": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
      "hotel_name": "Гостиница «Бриз»",
      "id": "41c93f91-182f-546d-ae1d-ea750b9ab5d9",
      "priority": 6.4,
      "reason": "missing",
      "state": "open",
      "text": "Какие номера и этажи выходят на юго-западную сторону, к морю?"
    }
  ],
  "page": 1,
  "page_size": 10,
  "total": 12
}

Новая характеристика текстом

Администратор, по сессии

Админ описывает характеристику словами, агент собирает черновик спецификации. Потом пробный прогон на пяти отелях и включение - без программиста.

  • POST/api/v1/admin/characteristicsЧерновик спецификации из текста
  • POST/api/v1/admin/characteristics/{ch_id}/versions/{version}/dry-runПробный прогон на 5 отелях
  • POST/api/v1/admin/characteristics/{ch_id}/versions/{version}/activateВключить версию
Запрос
curl -s -X POST "$OKNO_API/api/v1/admin/characteristics" \
  -b "sid=$OKNO_SID" \
  -H "Origin: $OKNO_API" \
  -H "Content-Type: application/json" \
  -d '{"description":"Есть ли рядом пекарня, которая открыта с 7 утра. Пешком не больше 10 минут."}'
Ответ, сокращён
{
  "data_mode": "real",
  "events_path": "/api/v1/admin/runs/94256fd5-964b-5167-8bb0-e644a7769395/events",
  "kind": "recompute",
  "run_id": "94256fd5-964b-5167-8bb0-e644a7769395",
  "state": "queued"
}

Хранилище фактов

Гость - провенанс значения; отельер и админ - факты и конфликты

Любое значение - наблюдение с источником, методом, уверенностью, датой и сроком годности. Противоречия между источниками становятся конфликтами для разбора.

  • GET/api/v1/hotels/{hotel_id}/characteristics/{ch_id}Как мы это узнали: провенанс значения
  • GET/api/v1/hotelier/hotels/{hotel_id}/factsЧто система знает
  • GET/api/v1/admin/conflictsОчередь конфликтов
Запрос
curl -s -X GET "$OKNO_API/api/v1/hotels/$HOTEL_ID/characteristics/CH-SUNSET"
Ответ, сокращён
{
  "characteristic_id": "CH-SEA-VIEW",
  "conflicts": [
    {
      "id": "531809e2-a4c0-503c-9c1e-c6898ed7c409",
      "observation_ids": [
        "c2283979-9a71-5935-8a3a-7d9fda8a89d9"
      ],
      "resolution": null,
      "scope_key": "floor:З:5",
      "state": "asked_hotelier"
    }
  ],
  "data_mode": "real",
  "hotel_id": "e30ee3ba-c3ae-5cf1-acb1-5d70c7b6f0a4",
  "hotelier_confirmations": [],
  "method_note": "Профиль горизонта из рельефа Copernicus GLO-30 и зданий OSM в радиусе 1,5 км. Море засчи…",
  "…": "ещё 5"
}

Агенты как блоки

Семь агентов в одном сервисе FastAPI с фоновой очередью. Факты считают только инструменты из реестра; языковая модель понимает текст, планирует и формулирует, а без ключа её заменяют правила и шаблоны.

Агенты продукта: что делает, метод API, вход, выход, нужна ли языковая модель
АгентЧто делаетМетод APIВходВыходЯзыковая модель
Разбор запросаAG-SRCHФраза гостя превращается в потребности по каталогу характеристикPOST /api/v1/searchquery, datesneeds, unknown_needs, resultsС ключом модель, без ключа правила и синонимы
ОбъясненияAG-EXPLПочему вариант подходит и в чём не дотягивает, по фактам, которые собрал кодGET /api/v1/search/{search_id}/explanations (SSE)search_idтекст объяснения по вариантуС ключом модель, без ключа шаблоны
Сбор характеристикAG-ORCHСчитает значения инструментами по спецификации: вид, путь, шум, погодаPOST /api/v1/admin/runs (collect, recompute), пересборка отеля, песочницаспецификация × отельнаблюдения с источником, методом и уверенностьюНет
Вопросы отельеруAG-HTLСпрашивает то, чего нет в данных, и раскладывает свободный ответ по полямPOST /api/v1/admin/runs (ask), GET /api/v1/hotelier/questions, POST …/answersспецификация, ответ своими словамивопрос, черновик по полям на подтверждениеС ключом модель, без ключа шаблон вопроса и правила разбора
Новая характеристикаAG-PLANИз описания текстом собирает спецификацию только из инструментов реестраPOST /api/v1/admin/characteristics, …/dry-run, …/activateописание словамиYAML-спецификация, пробный прогон на пяти отеляхНужна; без ключа только готовые образцы
Симулятор отельераAG-SIMОтвечает за отельера в демо по скрытой «правде» и характеру отеляPOST /api/v1/admin/simulator/questions/{question_id}/answerвопрос агентаответ своими словамиНужна; без ключа заготовленные ответы
АктуализацияПланировщикСледит за сроком годности значений и ставит перепроверку устаревшихworker; POST /api/v1/admin/runs (recompute, stale_only)сроки годности из спецификацийзапуск пересчётаНет

Правила API

Формат
JSON в UTF-8, даты ISO 8601, время в UTC. Префикс /api/v1. Поле data_mode в каждом ответе: real значит расчёт по данным.
Ошибки
application/problem+json (RFC 9457): type, title, status, detail, instance, errors[] по полям. Чужой объект отвечает 404, как и несуществующий.
Доступ
x-access у каждой операции: public без входа, guest, hotelier или admin по cookie сессии. Владение проверяется на сервере, в базе включена построчная защита.
Списки
page с 1, page_size 10, 25, 50 или 100, sort из белого списка. Ответ: items, page, page_size, total.
Повторы
Бронь, подтверждения и разрешение конфликтов принимают заголовок Idempotency-Key: повторный запрос с тем же ключом не создаёт дубль.
Лимиты
Тело до 64 КБ. Частота за минуту на IP и на сессию: вход 30, чтение 600, запись 120, вызовы с моделью 20, запуски агентов 10. Сверх лимита 429 с Retry-After.
Поток событий
Объяснения выдачи и журнал запуска агента приходят как text/event-stream; события описаны в контракте расширением x-sse-events.

Как встроить

  1. Сопоставить отели. Наши отели это объекты OpenStreetMap. Нужен адаптер, который свяжет их с id Островка по координатам и названию. Его пока нет.
  2. Вызвать поиск. Фразу гостя и даты отправить в POST /api/v1/search, варианты показать рядом с обычной выдачей.
  3. Показать отметки в карточке. Факты с источником и уверенностью берутся из GET /api/v1/hotels/{hotel_id}, «Как мы это узнали» из провенанса характеристики.
  4. Добавить раздел «Вопросы» в кабинет партнёра: методы отельера.

Как добавить город

Конвейер данных собирает любой город с побережьем или без. Город задан в одном месте на каждой стороне:

  1. Зона в API: services/api/app/zone.py. Название, часовой пояс, рамки отелей и карты, подложка. Переопределяется переменными OKNO_ZONE_*.
  2. Зона на сайте: apps/web/src/lib/zone.ts. Название, часовой пояс, рамка и начальный вид карты.
  3. Откуда брать карту и рельеф: регион Geofabrik и тайлы Copernicus GLO-30 в scripts/data/fetch.py.
  4. Районы: data/specs/districts.geojson.

Дальше тот же конвейер: импорт, стороны зданий, рельеф, маршруты и сбор характеристик агентами. Спецификации, поиск и интерфейс не меняются.

Контракт OpenAPI

Полное описание методов, схем и примеров. По нему можно сгенерировать клиент на любом языке; наш сайт использует клиент, сгенерированный Orval.

  • В репозитории: contracts/openapi.json
  • Зона: Большой Сочи, 843 отеля в снимке
О!кноСочи