{
  "components": {
    "schemas": {
      "AcceptObservationIn": {
        "additionalProperties": false,
        "description": "Принять одно из наблюдений.",
        "examples": [
          {
            "action": "accept",
            "observation_id": "c2283979-9a71-5935-8a3a-7d9fda8a89d9"
          }
        ],
        "properties": {
          "action": {
            "const": "accept",
            "title": "Action",
            "type": "string"
          },
          "observation_id": {
            "format": "uuid",
            "title": "Observation Id",
            "type": "string"
          }
        },
        "required": [
          "action",
          "observation_id"
        ],
        "title": "AcceptObservationIn",
        "type": "object"
      },
      "ActorKind": {
        "description": "Кто действовал: человек, агент или система (сегменты A9).",
        "enum": [
          "human",
          "agent",
          "system"
        ],
        "examples": [
          "human"
        ],
        "title": "ActorKind",
        "type": "string"
      },
      "AdminHotel": {
        "additionalProperties": false,
        "examples": [
          {
            "conflicts": 0,
            "coverage": 0.71,
            "district": "center",
            "id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
            "is_showcase": true,
            "location": {
              "lat": 43.5935,
              "lon": 39.7105
            },
            "name": "Апарт-отель «Платан»",
            "osm_ref": "way/245602350",
            "stale": 3,
            "updated_at": "2026-10-20T09:00:00Z"
          }
        ],
        "properties": {
          "conflicts": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Conflicts",
            "type": "integer"
          },
          "coverage": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Coverage",
            "type": "number"
          },
          "district": {
            "$ref": "#/components/schemas/District"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "is_showcase": {
            "description": "Один из 30 витринных отелей",
            "title": "Is Showcase",
            "type": "boolean"
          },
          "location": {
            "$ref": "#/components/schemas/GeoPoint"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          },
          "osm_ref": {
            "maxLength": 40,
            "minLength": 6,
            "pattern": "^(node|way|relation)/[0-9]{1,15}$",
            "title": "Osm Ref",
            "type": "string"
          },
          "stale": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Stale",
            "type": "integer"
          },
          "updated_at": {
            "format": "date-time",
            "title": "Updated At",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "osm_ref",
          "district",
          "location",
          "coverage",
          "conflicts",
          "stale",
          "is_showcase",
          "updated_at"
        ],
        "title": "AdminHotel",
        "type": "object"
      },
      "AdminHotelPage": {
        "additionalProperties": false,
        "description": "Таблица отелей зоны.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "conflicts": 0,
                "coverage": 0.71,
                "district": "center",
                "id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                "is_showcase": true,
                "location": {
                  "lat": 43.5935,
                  "lon": 39.7105
                },
                "name": "Апарт-отель «Платан»",
                "osm_ref": "way/245602350",
                "stale": 3,
                "updated_at": "2026-10-20T09:00:00Z"
              },
              {
                "conflicts": 0,
                "coverage": 0.79,
                "district": "imeretinka",
                "id": "49493573-598b-544a-b886-baa32164a3af",
                "is_showcase": true,
                "location": {
                  "lat": 43.4012,
                  "lon": 39.9625
                },
                "name": "Апартаменты «Голубая бухта»",
                "osm_ref": "way/512901409",
                "stale": 3,
                "updated_at": "2026-10-12T09:00:00Z"
              },
              {
                "conflicts": 0,
                "coverage": 0.63,
                "district": "center",
                "id": "880360de-824a-5c27-9dea-5b000a6261c1",
                "is_showcase": false,
                "location": {
                  "lat": 43.5889,
                  "lon": 39.7104
                },
                "name": "Апартаменты «Морской 13»",
                "osm_ref": "node/900000013",
                "stale": 1,
                "updated_at": "2026-10-10T09:00:00Z"
              },
              {
                "conflicts": 0,
                "coverage": 0.31,
                "district": "khosta",
                "id": "784d46d0-bbd5-5dfa-bf4e-7f0b025ac129",
                "is_showcase": false,
                "location": {
                  "lat": 43.5164,
                  "lon": 39.8623
                },
                "name": "Апартаменты «Морской 17»",
                "osm_ref": "node/900000017",
                "stale": 1,
                "updated_at": "2026-10-06T09:00:00Z"
              },
              {
                "conflicts": 0,
                "coverage": 0.71,
                "district": "adler",
                "id": "4da9a280-9ad3-5577-bd6b-e70a3bb432de",
                "is_showcase": false,
                "location": {
                  "lat": 43.4277,
                  "lon": 39.9255
                },
                "name": "Апартаменты «Морской 21»",
                "osm_ref": "node/900000021",
                "stale": 1,
                "updated_at": "2026-10-22T09:00:00Z"
              },
              {
                "conflicts": 0,
                "coverage": 0.39,
                "district": "center",
                "id": "b9be2743-31a9-51f2-a089-ca96f0c29311",
                "is_showcase": false,
                "location": {
                  "lat": 43.5868,
                  "lon": 39.7122
                },
                "name": "Апартаменты «Морской 25»",
                "osm_ref": "node/900000025",
                "stale": 1,
                "updated_at": "2026-10-18T09:00:00Z"
              },
              {
                "conflicts": 1,
                "coverage": 0.79,
                "district": "khosta",
                "id": "0e976d98-fc38-59f8-afa5-1c75f0baf5ca",
                "is_showcase": false,
                "location": {
                  "lat": 43.5178,
                  "lon": 39.8641
                },
                "name": "Апартаменты «Морской 29»",
                "osm_ref": "node/900000029",
                "stale": 1,
                "updated_at": "2026-10-14T09:00:00Z"
              },
              {
                "conflicts": 0,
                "coverage": 0.47,
                "district": "adler",
                "id": "f31df614-9698-5c00-94a3-cbb72fdc54a3",
                "is_showcase": false,
                "location": {
                  "lat": 43.4291,
                  "lon": 39.921
                },
                "name": "Апартаменты «Морской 33»",
                "osm_ref": "node/900000033",
                "stale": 1,
                "updated_at": "2026-10-10T09:00:00Z"
              },
              {
                "conflicts": 0,
                "coverage": 0.87,
                "district": "center",
                "id": "2d6ac1a9-dd4b-5d0e-8dc0-dc3a9e4ecc88",
                "is_showcase": false,
                "location": {
                  "lat": 43.5882,
                  "lon": 39.714
                },
                "name": "Апартаменты «Морской 37»",
                "osm_ref": "node/900000037",
                "stale": 1,
                "updated_at": "2026-10-06T09:00:00Z"
              },
              {
                "conflicts": 0,
                "coverage": 0.55,
                "district": "khosta",
                "id": "7328762b-f289-5de3-8df1-ad3daa838366",
                "is_showcase": false,
                "location": {
                  "lat": 43.5157,
                  "lon": 39.8596
                },
                "name": "Апартаменты «Морской 41»",
                "osm_ref": "node/900000041",
                "stale": 1,
                "updated_at": "2026-10-22T09:00:00Z"
              }
            ],
            "page": 1,
            "page_size": 10,
            "total": 60
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/AdminHotel"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "AdminHotelPage",
        "type": "object"
      },
      "AdminHotelSegment": {
        "description": "Сегменты A2: с конфликтами, устарели, без данных.",
        "enum": [
          "with_conflicts",
          "stale",
          "no_data"
        ],
        "examples": [
          "with_conflicts"
        ],
        "title": "AdminHotelSegment",
        "type": "string"
      },
      "AffectedViewpoint": {
        "additionalProperties": false,
        "description": "Точка обзора соседа: было и стало.",
        "examples": [
          {
            "after": {
              "sea_sector_deg": 21.5,
              "sunset_over": "buildings"
            },
            "before": {
              "sea_sector_deg": 62.0,
              "sunset_over": "sea"
            },
            "changed": true,
            "floor": 1,
            "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "hotel_name": "Гостиница «Бриз»",
            "side": "ЮЗ"
          }
        ],
        "properties": {
          "after": {
            "$ref": "#/components/schemas/ViewpointState"
          },
          "before": {
            "$ref": "#/components/schemas/ViewpointState"
          },
          "changed": {
            "title": "Changed",
            "type": "boolean"
          },
          "floor": {
            "description": "Этаж, 1-40",
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floor",
            "type": "integer"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "hotel_name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Hotel Name",
            "type": "string"
          },
          "side": {
            "$ref": "#/components/schemas/Rumb"
          }
        },
        "required": [
          "hotel_id",
          "hotel_name",
          "side",
          "floor",
          "before",
          "after",
          "changed"
        ],
        "title": "AffectedViewpoint",
        "type": "object"
      },
      "AgentId": {
        "description": "Агенты продукта.",
        "enum": [
          "AG-ORCH",
          "AG-PLAN",
          "AG-TRUTH",
          "AG-HTL",
          "AG-SIM",
          "AG-SRCH",
          "AG-EXPL",
          "AG-VOICE"
        ],
        "examples": [
          "AG-ORCH"
        ],
        "title": "AgentId",
        "type": "string"
      },
      "AnswerConfirmIn": {
        "additionalProperties": false,
        "description": "Подтверждение черновика с правками полей.",
        "examples": [
          {
            "parsed": {
              "from_floor": 3,
              "rooms": "301-310",
              "side": "З"
            }
          }
        ],
        "properties": {
          "parsed": {
            "additionalProperties": {
              "anyOf": [
                {
                  "maxLength": 500,
                  "type": "string"
                },
                {
                  "maximum": 1000000000.0,
                  "minimum": -1000000000.0,
                  "type": "integer"
                },
                {
                  "maximum": 1000000000.0,
                  "minimum": -1000000000.0,
                  "type": "number"
                },
                {
                  "type": "boolean"
                },
                {
                  "type": "null"
                },
                {
                  "items": {
                    "anyOf": [
                      {
                        "maxLength": 500,
                        "type": "string"
                      },
                      {
                        "maximum": 1000000000.0,
                        "minimum": -1000000000.0,
                        "type": "integer"
                      },
                      {
                        "maximum": 1000000000.0,
                        "minimum": -1000000000.0,
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "maxItems": 20,
                  "type": "array"
                }
              ]
            },
            "description": "Поля ответа после правки, плоский объект",
            "maxProperties": 30,
            "minProperties": 1,
            "propertyNames": {
              "maxLength": 41,
              "not": {
                "enum": [
                  "confidence",
                  "method",
                  "observed_at",
                  "source",
                  "status",
                  "trust",
                  "valid_until"
                ]
              },
              "pattern": "^[a-z][a-z0-9_]{0,40}$",
              "type": "string"
            },
            "title": "Parsed",
            "type": "object"
          }
        },
        "required": [
          "parsed"
        ],
        "title": "AnswerConfirmIn",
        "type": "object"
      },
      "AnswerConfirmed": {
        "additionalProperties": false,
        "examples": [
          {
            "answer_id": "5c34db89-564b-5888-b1c7-22b9e65cbe40",
            "conflict_ids": [],
            "data_mode": "real",
            "observation_ids": [
              "34f832bc-78ea-5bfd-a44e-02b26785424d",
              "b598de3b-9a1f-56ca-9a84-f23d1ed3e523",
              "d6c70b5a-9f56-502b-90d3-d7f35faa006d"
            ],
            "state": "confirmed"
          }
        ],
        "properties": {
          "answer_id": {
            "format": "uuid",
            "title": "Answer Id",
            "type": "string"
          },
          "conflict_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 10,
            "title": "Conflict Ids",
            "type": "array"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "observation_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 50,
            "title": "Observation Ids",
            "type": "array"
          },
          "state": {
            "$ref": "#/components/schemas/AnswerState"
          }
        },
        "required": [
          "data_mode",
          "answer_id",
          "state",
          "observation_ids",
          "conflict_ids"
        ],
        "title": "AnswerConfirmed",
        "type": "object"
      },
      "AnswerDraft": {
        "additionalProperties": false,
        "description": "Черновик разбора «Вот что я понял» (AnswerDraft).",
        "examples": [
          {
            "clarifications": [],
            "data_mode": "real",
            "field_confidence": {
              "from_floor": 0.55,
              "rooms": 0.92,
              "side": 0.81
            },
            "id": "b7e86a46-fac6-51f8-9670-c0fb71a9c9da",
            "parsed": {
              "from_floor": 3,
              "rooms": "301-310",
              "side": "З"
            },
            "question_id": "41c93f91-182f-546d-ae1d-ea750b9ab5d9",
            "state": "draft",
            "summary": [
              "Номера 301-310 → сторона З",
              "Море видно с 3-го этажа"
            ]
          }
        ],
        "properties": {
          "clarifications": {
            "description": "Уточняющие вопросы",
            "items": {
              "maxLength": 200,
              "minLength": 1,
              "type": "string"
            },
            "maxItems": 5,
            "title": "Clarifications",
            "type": "array"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "field_confidence": {
            "additionalProperties": {
              "description": "Уверенность от 0 до 1",
              "maximum": 1.0,
              "minimum": 0.0,
              "type": "number"
            },
            "maxProperties": 30,
            "title": "Field Confidence",
            "type": "object"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "parsed": {
            "additionalProperties": true,
            "maxProperties": 30,
            "title": "Parsed",
            "type": "object"
          },
          "question_id": {
            "format": "uuid",
            "title": "Question Id",
            "type": "string"
          },
          "state": {
            "$ref": "#/components/schemas/AnswerState"
          },
          "summary": {
            "description": "Строки «Вот что я понял»",
            "items": {
              "maxLength": 200,
              "minLength": 1,
              "type": "string"
            },
            "maxItems": 20,
            "title": "Summary",
            "type": "array"
          }
        },
        "required": [
          "data_mode",
          "id",
          "question_id",
          "parsed",
          "field_confidence",
          "clarifications",
          "summary",
          "state"
        ],
        "title": "AnswerDraft",
        "type": "object"
      },
      "AnswerIn": {
        "additionalProperties": false,
        "description": "Ответ текстом. Диктовка приходит текстом с from_audio=true.",
        "examples": [
          {
            "from_audio": false,
            "text": "Номера 301-310 смотрят на запад, море видно с 3-го этажа"
          }
        ],
        "properties": {
          "from_audio": {
            "default": false,
            "title": "From Audio",
            "type": "boolean"
          },
          "text": {
            "maxLength": 4000,
            "minLength": 1,
            "title": "Text",
            "type": "string"
          }
        },
        "required": [
          "text"
        ],
        "title": "AnswerIn",
        "type": "object"
      },
      "AnswerState": {
        "enum": [
          "draft",
          "confirmed",
          "rejected"
        ],
        "examples": [
          "draft"
        ],
        "title": "AnswerState",
        "type": "string"
      },
      "AskHotelierIn": {
        "additionalProperties": false,
        "description": "Отправить вопрос отельеру.",
        "examples": [
          {
            "action": "ask_hotelier"
          }
        ],
        "properties": {
          "action": {
            "const": "ask_hotelier",
            "title": "Action",
            "type": "string"
          }
        },
        "required": [
          "action"
        ],
        "title": "AskHotelierIn",
        "type": "object"
      },
      "AttentionItem": {
        "additionalProperties": false,
        "examples": [
          {
            "action": "Разобрать",
            "kind": "conflict",
            "object_id": "531809e2-a4c0-503c-9c1e-c6898ed7c409",
            "object_type": "conflict",
            "title": "«Кипарис»: отельер и расчёт расходятся по виду на море"
          }
        ],
        "properties": {
          "action": {
            "anyOf": [
              {
                "maxLength": 80,
                "minLength": 1,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Action"
          },
          "kind": {
            "$ref": "#/components/schemas/AttentionKind"
          },
          "object_id": {
            "anyOf": [
              {
                "maxLength": 64,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Object Id"
          },
          "object_type": {
            "maxLength": 40,
            "minLength": 1,
            "title": "Object Type",
            "type": "string"
          },
          "title": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Title",
            "type": "string"
          }
        },
        "required": [
          "kind",
          "title",
          "object_type",
          "object_id",
          "action"
        ],
        "title": "AttentionItem",
        "type": "object"
      },
      "AttentionKind": {
        "enum": [
          "conflict",
          "source_failure",
          "stale",
          "question"
        ],
        "examples": [
          "conflict"
        ],
        "title": "AttentionKind",
        "type": "string"
      },
      "Attribution": {
        "additionalProperties": false,
        "description": "Подписи источников и лицензии для интерфейса и экрана G9.",
        "examples": [
          {
            "data_mode": "real",
            "map_attribution": [
              "© участники OpenStreetMap",
              "Protomaps",
              "Рельеф: Copernicus GLO-30"
            ],
            "not_used": [
              {
                "name": "Mapillary",
                "reason": "Нужен ключ, компания-владелец признана в РФ экстремистской"
              },
              {
                "name": "FABDEM",
                "reason": "Лицензия запрещает коммерческое использование"
              },
              {
                "name": "OpenCellID",
                "reason": "Нет надёжных открытых данных о связи: CH-CONNECTIVITY не делаем"
              }
            ],
            "sources": [
              {
                "attribution": "© участники OpenStreetMap",
                "data_date": "2026-10-22",
                "id": "SRC-01",
                "license": "ODbL",
                "license_url": "https://opendatacommons.org/licenses/odbl/",
                "name": "OpenStreetMap",
                "url": "https://www.openstreetmap.org/copyright",
                "what_we_use": "Отели, здания, дороги, пляжи, заведения"
              },
              {
                "attribution": "produced using Copernicus WorldDEM-30 © DLR e.V. 2010-2014 and © Airbus Defence and Space GmbH 2014-2018 provided under COPERNICUS by the European Union and ESA; all rights reserved",
                "data_date": null,
                "id": "SRC-02",
                "license": "Copernicus DEM",
                "license_url": null,
                "name": "Copernicus GLO-30",
                "url": "https://spacedata.copernicus.eu/",
                "what_we_use": "Рельеф для вида, заката и гор"
              },
              {
                "attribution": "Protomaps, © участники OpenStreetMap",
                "data_date": "2026-10-23",
                "id": "SRC-03",
                "license": "BSD и ODbL",
                "license_url": null,
                "name": "Protomaps",
                "url": "https://protomaps.com/",
                "what_we_use": "Подложка карты"
              },
              {
                "attribution": "Open-Meteo, CC BY 4.0",
                "data_date": null,
                "id": "SRC-06",
                "license": "CC BY 4.0",
                "license_url": "https://creativecommons.org/licenses/by/4.0/",
                "name": "Open-Meteo Historical (ERA5)",
                "url": "https://open-meteo.com/",
                "what_we_use": "Климат по месяцам"
              },
              {
                "attribution": "Open-Meteo, CC BY 4.0",
                "data_date": null,
                "id": "SRC-07",
                "license": "CC BY 4.0",
                "license_url": "https://creativecommons.org/licenses/by/4.0/",
                "name": "Open-Meteo Marine",
                "url": "https://open-meteo.com/",
                "what_we_use": "Температура моря"
              },
              {
                "attribution": "Автор и лицензия указаны у каждого фото",
                "data_date": null,
                "id": "SRC-09",
                "license": "Своя у каждого файла",
                "license_url": null,
                "name": "Wikimedia Commons",
                "url": "https://commons.wikimedia.org/",
                "what_we_use": "Фото отелей и окрестностей"
              }
            ]
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "map_attribution": {
            "description": "Подписи на карте",
            "items": {
              "maxLength": 80,
              "minLength": 1,
              "type": "string"
            },
            "maxItems": 6,
            "title": "Map Attribution",
            "type": "array"
          },
          "not_used": {
            "items": {
              "$ref": "#/components/schemas/NotUsedSource"
            },
            "maxItems": 10,
            "title": "Not Used",
            "type": "array"
          },
          "sources": {
            "items": {
              "$ref": "#/components/schemas/AttributionSource"
            },
            "maxItems": 20,
            "title": "Sources",
            "type": "array"
          }
        },
        "required": [
          "data_mode",
          "sources",
          "not_used",
          "map_attribution"
        ],
        "title": "Attribution",
        "type": "object"
      },
      "AttributionSource": {
        "additionalProperties": false,
        "examples": [
          {
            "attribution": "© участники OpenStreetMap",
            "data_date": "2026-10-22",
            "id": "SRC-01",
            "license": "ODbL",
            "license_url": "https://opendatacommons.org/licenses/odbl/",
            "name": "OpenStreetMap",
            "url": "https://www.openstreetmap.org/copyright",
            "what_we_use": "Отели, здания, дороги, пляжи, заведения"
          }
        ],
        "properties": {
          "attribution": {
            "description": "Обязательная подпись",
            "maxLength": 300,
            "minLength": 1,
            "title": "Attribution",
            "type": "string"
          },
          "data_date": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Data Date"
          },
          "id": {
            "description": "ID источника",
            "maxLength": 6,
            "minLength": 6,
            "pattern": "^SRC-[0-9]{2}$",
            "title": "Id",
            "type": "string"
          },
          "license": {
            "maxLength": 80,
            "minLength": 1,
            "title": "License",
            "type": "string"
          },
          "license_url": {
            "anyOf": [
              {
                "maxLength": 500,
                "minLength": 1,
                "pattern": "^(https://|/)",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "License Url"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          },
          "url": {
            "anyOf": [
              {
                "maxLength": 500,
                "minLength": 1,
                "pattern": "^(https://|/)",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Url"
          },
          "what_we_use": {
            "maxLength": 200,
            "minLength": 1,
            "title": "What We Use",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "what_we_use",
          "license",
          "license_url",
          "attribution",
          "data_date",
          "url"
        ],
        "title": "AttributionSource",
        "type": "object"
      },
      "AudioFormat": {
        "additionalProperties": false,
        "description": "Формат звука моста. Частота уточняется на спайке Yandex (В-VOI-02).",
        "examples": [
          {
            "channels": 1,
            "encoding": "pcm16",
            "frame_ms": 20,
            "sample_rate_hz": 16000
          }
        ],
        "properties": {
          "channels": {
            "const": 1,
            "title": "Channels",
            "type": "integer"
          },
          "encoding": {
            "const": "pcm16",
            "title": "Encoding",
            "type": "string"
          },
          "frame_ms": {
            "maximum": 40.0,
            "minimum": 20.0,
            "title": "Frame Ms",
            "type": "integer"
          },
          "sample_rate_hz": {
            "maximum": 48000.0,
            "minimum": 8000.0,
            "title": "Sample Rate Hz",
            "type": "integer"
          }
        },
        "required": [
          "encoding",
          "sample_rate_hz",
          "channels",
          "frame_ms"
        ],
        "title": "AudioFormat",
        "type": "object"
      },
      "AuditDecision": {
        "enum": [
          "allowed",
          "denied",
          "done",
          "failed"
        ],
        "examples": [
          "allowed"
        ],
        "title": "AuditDecision",
        "type": "string"
      },
      "AuditEntry": {
        "additionalProperties": false,
        "description": "Запись аудита без персональных данных.",
        "examples": [
          {
            "action": "conflict.resolve",
            "actor": "Админ · демо 3",
            "actor_kind": "human",
            "actor_role": "admin",
            "decision": "done",
            "id": "41e7e90a-5eb6-5134-a7ae-85eb9dba79a2",
            "object_id": "6a5f937a-9a56-57a1-85af-98d2eb0a763a",
            "object_type": "conflict",
            "ts": "2026-10-23T09:00:00Z"
          }
        ],
        "properties": {
          "action": {
            "maxLength": 60,
            "minLength": 1,
            "title": "Action",
            "type": "string"
          },
          "actor": {
            "description": "Роль и номер демо-пользователя или ID агента",
            "maxLength": 80,
            "minLength": 1,
            "title": "Actor",
            "type": "string"
          },
          "actor_kind": {
            "$ref": "#/components/schemas/ActorKind"
          },
          "actor_role": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Role"
              },
              {
                "type": "null"
              }
            ]
          },
          "decision": {
            "$ref": "#/components/schemas/AuditDecision"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "object_id": {
            "anyOf": [
              {
                "maxLength": 64,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Object Id"
          },
          "object_type": {
            "maxLength": 40,
            "minLength": 1,
            "title": "Object Type",
            "type": "string"
          },
          "ts": {
            "format": "date-time",
            "title": "Ts",
            "type": "string"
          }
        },
        "required": [
          "id",
          "ts",
          "actor_kind",
          "actor_role",
          "actor",
          "action",
          "object_type",
          "object_id",
          "decision"
        ],
        "title": "AuditEntry",
        "type": "object"
      },
      "AuditPage": {
        "additionalProperties": false,
        "description": "Журнал аудита.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "action": "conflict.resolve",
                "actor": "Админ · демо 3",
                "actor_kind": "human",
                "actor_role": "admin",
                "decision": "done",
                "id": "41e7e90a-5eb6-5134-a7ae-85eb9dba79a2",
                "object_id": "6a5f937a-9a56-57a1-85af-98d2eb0a763a",
                "object_type": "conflict",
                "ts": "2026-10-23T09:00:00Z"
              },
              {
                "action": "fact.confirm",
                "actor": "Отельер · демо 7",
                "actor_kind": "human",
                "actor_role": "hotelier",
                "decision": "done",
                "id": "1e7886d1-a415-5e5f-a88c-95e8205a8a25",
                "object_id": "2596a126-3a15-5a67-ab42-86b4ee3bf11f",
                "object_type": "fact",
                "ts": "2026-10-23T08:53:00Z"
              },
              {
                "action": "conflict.auto_resolve",
                "actor": "AG-TRUTH",
                "actor_kind": "agent",
                "actor_role": null,
                "decision": "done",
                "id": "5501d325-c18f-5415-a7b5-b20ae520a1c4",
                "object_id": "d9081389-36f3-5b9e-843d-300de733663b",
                "object_type": "conflict",
                "ts": "2026-10-23T08:46:00Z"
              },
              {
                "action": "hotel.read",
                "actor": "Отельер · демо 9",
                "actor_kind": "human",
                "actor_role": "hotelier",
                "decision": "denied",
                "id": "fa66788d-cf43-5242-99ab-5af362837614",
                "object_id": "549c43c9-1616-5a86-8700-8a5f24f129a3",
                "object_type": "hotel",
                "ts": "2026-10-23T08:39:00Z"
              },
              {
                "action": "observation.write",
                "actor": "AG-ORCH",
                "actor_kind": "agent",
                "actor_role": null,
                "decision": "done",
                "id": "c1133e81-69c3-5578-aeec-e8c8ee41b0d3",
                "object_id": "a73032c5-ead7-5db4-8b00-fcf93bd486a0",
                "object_type": "observation",
                "ts": "2026-10-23T08:32:00Z"
              },
              {
                "action": "run.create",
                "actor": "Планировщик сроков",
                "actor_kind": "system",
                "actor_role": null,
                "decision": "done",
                "id": "a9d61d3c-cf57-520f-802c-bd875c695b14",
                "object_id": "2c5f49f2-10f9-5aac-b507-5db820788bcd",
                "object_type": "run",
                "ts": "2026-10-23T08:25:00Z"
              },
              {
                "action": "characteristic.activate",
                "actor": "Админ · демо 3",
                "actor_kind": "human",
                "actor_role": "admin",
                "decision": "done",
                "id": "4eeae17a-0ab6-5930-90c0-d2e3f95dd13f",
                "object_id": "c8803ca6-5ee7-5985-852d-5f087b12c850",
                "object_type": "characteristic",
                "ts": "2026-10-23T08:18:00Z"
              },
              {
                "action": "conflict.resolve",
                "actor": "Админ · демо 3",
                "actor_kind": "human",
                "actor_role": "admin",
                "decision": "done",
                "id": "7cb4bb38-e294-5535-9c37-110950c8ac77",
                "object_id": "e3786afa-df6f-5264-839f-ebd9eadfec93",
                "object_type": "conflict",
                "ts": "2026-10-23T08:11:00Z"
              },
              {
                "action": "fact.confirm",
                "actor": "Отельер · демо 7",
                "actor_kind": "human",
                "actor_role": "hotelier",
                "decision": "done",
                "id": "1ea9fe50-cdef-57ce-b156-2a464f81889e",
                "object_id": "f57c426f-36c4-5412-8371-747921098ce9",
                "object_type": "fact",
                "ts": "2026-10-23T08:04:00Z"
              },
              {
                "action": "conflict.auto_resolve",
                "actor": "AG-TRUTH",
                "actor_kind": "agent",
                "actor_role": null,
                "decision": "done",
                "id": "1badbb49-98dd-5720-907f-072526993360",
                "object_id": "ed4b65ca-1650-5534-a900-32930dfd1fa9",
                "object_type": "conflict",
                "ts": "2026-10-23T07:57:00Z"
              }
            ],
            "page": 1,
            "page_size": 10,
            "total": 35
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/AuditEntry"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "AuditPage",
        "type": "object"
      },
      "Booking": {
        "additionalProperties": false,
        "examples": [
          {
            "created_at": "2026-10-23T09:00:00Z",
            "data_mode": "real",
            "dates": {
              "from": "2026-05-03",
              "to": "2026-05-07"
            },
            "demo": true,
            "floor": 5,
            "guaranteed": [
              {
                "by": "system",
                "confirmed_at": null,
                "text": "Закат над морем со стороны ЮЗ: расчёт по рельефу и зданиям"
              },
              {
                "by": "hotelier",
                "confirmed_at": "2026-10-20",
                "text": "Вид на море с 5-го этажа: подтвердил отельер"
              }
            ],
            "guests": 2,
            "hotel": {
              "address": "Курортный проспект, 18",
              "district": "center",
              "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
              "kind": "hotel",
              "location": {
                "lat": 43.579,
                "lon": 39.723
              },
              "name": "Гостиница «Бриз»",
              "photo": {
                "author": "Участник Викисклада",
                "license": "CC BY-SA 4.0",
                "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
                "url": "/media/photos/briz.jpg"
              },
              "stars": 4,
              "walk_back_min": 11,
              "walk_to_sea_min": 7
            },
            "id": "0042c4d5-498f-5f4a-85a3-06369742efa9",
            "not_guaranteed": [
              "Тишина ночью: отельер не подтвердил"
            ],
            "number": "OK-2026-803221",
            "side": "ЮЗ",
            "wishes_to_hotel": [
              "Номер на сторону ЮЗ",
              "Этаж не ниже 5-го"
            ]
          }
        ],
        "properties": {
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "dates": {
            "$ref": "#/components/schemas/DateRange"
          },
          "demo": {
            "const": true,
            "description": "Демонстрация: оплаты и цен нет",
            "title": "Demo",
            "type": "boolean"
          },
          "floor": {
            "anyOf": [
              {
                "description": "Этаж, 1-40",
                "maximum": 40.0,
                "minimum": 1.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Floor"
          },
          "guaranteed": {
            "items": {
              "$ref": "#/components/schemas/Guarantee"
            },
            "maxItems": 10,
            "title": "Guaranteed",
            "type": "array"
          },
          "guests": {
            "maximum": 10.0,
            "minimum": 1.0,
            "title": "Guests",
            "type": "integer"
          },
          "hotel": {
            "$ref": "#/components/schemas/HotelSummary"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "not_guaranteed": {
            "items": {
              "maxLength": 200,
              "minLength": 1,
              "type": "string"
            },
            "maxItems": 10,
            "title": "Not Guaranteed",
            "type": "array"
          },
          "number": {
            "maxLength": 14,
            "minLength": 14,
            "pattern": "^OK-[0-9]{4}-[0-9]{6}$",
            "title": "Number",
            "type": "string"
          },
          "side": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rumb"
              },
              {
                "type": "null"
              }
            ]
          },
          "wishes_to_hotel": {
            "description": "Пожелания гостя отелю",
            "items": {
              "maxLength": 200,
              "minLength": 1,
              "type": "string"
            },
            "maxItems": 5,
            "title": "Wishes To Hotel",
            "type": "array"
          }
        },
        "required": [
          "data_mode",
          "id",
          "number",
          "hotel",
          "side",
          "floor",
          "dates",
          "guests",
          "guaranteed",
          "not_guaranteed",
          "wishes_to_hotel",
          "demo",
          "created_at"
        ],
        "title": "Booking",
        "type": "object"
      },
      "BookingRequest": {
        "additionalProperties": false,
        "examples": [
          {
            "dates": {
              "from": "2026-05-03",
              "to": "2026-05-07"
            },
            "floor": 5,
            "guests": 2,
            "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "side": "ЮЗ"
          }
        ],
        "properties": {
          "dates": {
            "$ref": "#/components/schemas/DateRange"
          },
          "floor": {
            "anyOf": [
              {
                "description": "Этаж, 1-40",
                "maximum": 40.0,
                "minimum": 1.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Floor"
          },
          "guests": {
            "default": 2,
            "maximum": 10.0,
            "minimum": 1.0,
            "title": "Guests",
            "type": "integer"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "side": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rumb"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "hotel_id",
          "dates"
        ],
        "title": "BookingRequest",
        "type": "object"
      },
      "Building": {
        "additionalProperties": false,
        "description": "Здание отеля и откуда его высота.",
        "examples": [
          {
            "height_confidence": 0.75,
            "height_m": 28.0,
            "height_source": "osm_levels",
            "levels": 9,
            "osm_ref": "way/245601234"
          }
        ],
        "properties": {
          "height_confidence": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Height Confidence",
            "type": "number"
          },
          "height_m": {
            "anyOf": [
              {
                "maximum": 400.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Height M"
          },
          "height_source": {
            "$ref": "#/components/schemas/HeightSource"
          },
          "levels": {
            "anyOf": [
              {
                "maximum": 100.0,
                "minimum": 1.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Levels"
          },
          "osm_ref": {
            "anyOf": [
              {
                "maxLength": 40,
                "minLength": 6,
                "pattern": "^(node|way|relation)/[0-9]{1,15}$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Osm Ref"
          }
        },
        "required": [
          "osm_ref",
          "height_m",
          "levels",
          "height_source",
          "height_confidence"
        ],
        "title": "Building",
        "type": "object"
      },
      "CharacteristicAccuracy": {
        "additionalProperties": false,
        "examples": [
          {
            "characteristic_id": "CH-SEA-VIEW",
            "f1": 0.86,
            "precision": 0.88,
            "recall": 0.84,
            "support": 112
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "f1": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "F1",
            "type": "number"
          },
          "precision": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Precision",
            "type": "number"
          },
          "recall": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Recall",
            "type": "number"
          },
          "support": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Support",
            "type": "integer"
          }
        },
        "required": [
          "characteristic_id",
          "precision",
          "recall",
          "f1",
          "support"
        ],
        "title": "CharacteristicAccuracy",
        "type": "object"
      },
      "CharacteristicCoverage": {
        "additionalProperties": false,
        "examples": [
          {
            "characteristic_id": "CH-SEA-VIEW",
            "coverage": 0.93,
            "target": 0.8
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "coverage": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Coverage",
            "type": "number"
          },
          "target": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Target",
            "type": "number"
          }
        },
        "required": [
          "characteristic_id",
          "coverage",
          "target"
        ],
        "title": "CharacteristicCoverage",
        "type": "object"
      },
      "CharacteristicDraftIn": {
        "additionalProperties": false,
        "description": "Описание новой характеристики текстом для AG-PLAN.",
        "examples": [
          {
            "description": "Есть ли рядом пекарня, которая открыта с 7 утра. Пешком не больше 10 минут."
          }
        ],
        "properties": {
          "description": {
            "maxLength": 2000,
            "minLength": 10,
            "title": "Description",
            "type": "string"
          }
        },
        "required": [
          "description"
        ],
        "title": "CharacteristicDraftIn",
        "type": "object"
      },
      "CharacteristicPage": {
        "additionalProperties": false,
        "description": "Реестр характеристик и версий.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "coverage": 0.0,
                "dates_dependent": false,
                "id": "CH-BAKERY",
                "origin": "stand",
                "priority": "should",
                "scope_kind": "hotel",
                "status": "draft",
                "title": "Пекарня рядом с 7 утра",
                "updated_at": "2026-10-23T07:00:00Z",
                "version": 1
              },
              {
                "coverage": 0.87,
                "dates_dependent": false,
                "id": "CH-CAR",
                "origin": "builtin",
                "priority": "should",
                "scope_kind": "hotel",
                "status": "active",
                "title": "На машине",
                "updated_at": "2026-10-14T09:00:00Z",
                "version": 2
              },
              {
                "coverage": 0.63,
                "dates_dependent": true,
                "id": "CH-CLIMATE",
                "origin": "builtin",
                "priority": "must",
                "scope_kind": "district",
                "status": "active",
                "title": "Климат и море",
                "updated_at": "2026-10-18T09:00:00Z",
                "version": 2
              },
              {
                "coverage": 0.93,
                "dates_dependent": false,
                "id": "CH-FAMILY",
                "origin": "builtin",
                "priority": "should",
                "scope_kind": "hotel",
                "status": "active",
                "title": "Семьям с детьми",
                "updated_at": "2026-10-15T09:00:00Z",
                "version": 1
              },
              {
                "coverage": 0.51,
                "dates_dependent": false,
                "id": "CH-MOUNTAIN-VIEW",
                "origin": "builtin",
                "priority": "should",
                "scope_kind": "floor",
                "status": "active",
                "title": "Вид на горы",
                "updated_at": "2026-10-16T09:00:00Z",
                "version": 2
              },
              {
                "coverage": 0.69,
                "dates_dependent": false,
                "id": "CH-NIGHT",
                "origin": "builtin",
                "priority": "must",
                "scope_kind": "hotel",
                "status": "active",
                "title": "Ночью рядом",
                "updated_at": "2026-10-19T09:00:00Z",
                "version": 1
              },
              {
                "coverage": 0.81,
                "dates_dependent": false,
                "id": "CH-PETS",
                "origin": "builtin",
                "priority": "should",
                "scope_kind": "hotel",
                "status": "active",
                "title": "С питомцем",
                "updated_at": "2026-10-13T09:00:00Z",
                "version": 1
              },
              {
                "coverage": 0.81,
                "dates_dependent": false,
                "id": "CH-QUIET",
                "origin": "builtin",
                "priority": "must",
                "scope_kind": "side",
                "status": "active",
                "title": "Тишина ночью",
                "updated_at": "2026-10-21T09:00:00Z",
                "version": 1
              },
              {
                "coverage": 0.57,
                "dates_dependent": false,
                "id": "CH-ROOM-SIDES",
                "origin": "builtin",
                "priority": "must",
                "scope_kind": "room",
                "status": "active",
                "title": "Номера по сторонам",
                "updated_at": "2026-10-17T09:00:00Z",
                "version": 1
              },
              {
                "coverage": 0.93,
                "dates_dependent": false,
                "id": "CH-SEA-VIEW",
                "origin": "builtin",
                "priority": "must",
                "scope_kind": "floor",
                "status": "active",
                "title": "Вид на море",
                "updated_at": "2026-10-23T09:00:00Z",
                "version": 1
              }
            ],
            "page": 1,
            "page_size": 10,
            "total": 13
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/CharacteristicRow"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "CharacteristicPage",
        "type": "object"
      },
      "CharacteristicRow": {
        "additionalProperties": false,
        "examples": [
          {
            "coverage": 0.0,
            "dates_dependent": false,
            "id": "CH-BAKERY",
            "origin": "stand",
            "priority": "should",
            "scope_kind": "hotel",
            "status": "draft",
            "title": "Пекарня рядом с 7 утра",
            "updated_at": "2026-10-23T07:00:00Z",
            "version": 1
          }
        ],
        "properties": {
          "coverage": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Coverage",
            "type": "number"
          },
          "dates_dependent": {
            "title": "Dates Dependent",
            "type": "boolean"
          },
          "id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Id",
            "type": "string"
          },
          "origin": {
            "$ref": "#/components/schemas/SpecOrigin"
          },
          "priority": {
            "$ref": "#/components/schemas/Priority"
          },
          "scope_kind": {
            "$ref": "#/components/schemas/ScopeKind"
          },
          "status": {
            "$ref": "#/components/schemas/SpecStatus"
          },
          "title": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Title",
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "title": "Updated At",
            "type": "string"
          },
          "version": {
            "maximum": 999.0,
            "minimum": 1.0,
            "title": "Version",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "version",
          "title",
          "scope_kind",
          "dates_dependent",
          "priority",
          "status",
          "origin",
          "coverage",
          "updated_at"
        ],
        "title": "CharacteristicRow",
        "type": "object"
      },
      "ChipSource": {
        "enum": [
          "preset",
          "popular",
          "memory"
        ],
        "examples": [
          "preset"
        ],
        "title": "ChipSource",
        "type": "string"
      },
      "Claim": {
        "additionalProperties": false,
        "description": "Утверждение объяснения со ссылками на наблюдения.",
        "examples": [
          {
            "observation_ids": [
              "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
            ],
            "text": "Закат над морем с 3-го этажа, сторона ЮЗ"
          }
        ],
        "properties": {
          "observation_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 10,
            "minItems": 1,
            "title": "Observation Ids",
            "type": "array"
          },
          "text": {
            "maxLength": 300,
            "minLength": 1,
            "title": "Text",
            "type": "string"
          }
        },
        "required": [
          "text",
          "observation_ids"
        ],
        "title": "Claim",
        "type": "object"
      },
      "CompareCell": {
        "additionalProperties": false,
        "examples": [
          {
            "confidence": 0.86,
            "confidence_label": "high",
            "display": "Закат над морем с 5-го этажа, сторона ЮЗ",
            "observation_ids": [
              "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
            ],
            "status": "yes"
          }
        ],
        "properties": {
          "confidence": {
            "anyOf": [
              {
                "description": "Уверенность от 0 до 1",
                "maximum": 1.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Confidence"
          },
          "confidence_label": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ConfidenceLabel"
              },
              {
                "type": "null"
              }
            ]
          },
          "display": {
            "maxLength": 120,
            "minLength": 1,
            "title": "Display",
            "type": "string"
          },
          "observation_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 10,
            "title": "Observation Ids",
            "type": "array"
          },
          "status": {
            "$ref": "#/components/schemas/MatchStatus"
          }
        },
        "required": [
          "status",
          "display",
          "confidence",
          "confidence_label",
          "observation_ids"
        ],
        "title": "CompareCell",
        "type": "object"
      },
      "CompareColumn": {
        "additionalProperties": false,
        "examples": [
          {
            "floor": 5,
            "hotel": {
              "address": "Курортный проспект, 18",
              "district": "center",
              "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
              "kind": "hotel",
              "location": {
                "lat": 43.579,
                "lon": 39.723
              },
              "name": "Гостиница «Бриз»",
              "photo": {
                "author": "Участник Викисклада",
                "license": "CC BY-SA 4.0",
                "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
                "url": "/media/photos/briz.jpg"
              },
              "stars": 4,
              "walk_back_min": 11,
              "walk_to_sea_min": 7
            },
            "side": "ЮЗ",
            "variant_index": 0
          }
        ],
        "properties": {
          "floor": {
            "anyOf": [
              {
                "description": "Этаж, 1-40",
                "maximum": 40.0,
                "minimum": 1.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Floor"
          },
          "hotel": {
            "$ref": "#/components/schemas/HotelSummary"
          },
          "side": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rumb"
              },
              {
                "type": "null"
              }
            ]
          },
          "variant_index": {
            "maximum": 3.0,
            "minimum": 0.0,
            "title": "Variant Index",
            "type": "integer"
          }
        },
        "required": [
          "variant_index",
          "hotel",
          "side",
          "floor"
        ],
        "title": "CompareColumn",
        "type": "object"
      },
      "CompareRequest": {
        "additionalProperties": false,
        "examples": [
          {
            "dates": {
              "from": "2026-05-03",
              "to": "2026-05-07"
            },
            "needs": [
              {
                "characteristic_id": "CH-SUNSET",
                "condition": {
                  "op": "sea_on_dates",
                  "value": null
                },
                "importance": "must",
                "source": "chip"
              }
            ],
            "variants": [
              {
                "floor": 5,
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "side": "ЮЗ"
              },
              {
                "floor": null,
                "hotel_id": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                "side": null
              }
            ]
          }
        ],
        "properties": {
          "dates": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateRange"
              },
              {
                "type": "null"
              }
            ]
          },
          "needs": {
            "items": {
              "$ref": "#/components/schemas/NeedIn"
            },
            "maxItems": 12,
            "title": "Needs",
            "type": "array"
          },
          "variants": {
            "items": {
              "$ref": "#/components/schemas/CompareVariantIn"
            },
            "maxItems": 4,
            "minItems": 2,
            "title": "Variants",
            "type": "array"
          }
        },
        "required": [
          "variants"
        ],
        "title": "CompareRequest",
        "type": "object"
      },
      "CompareResponse": {
        "additionalProperties": false,
        "examples": [
          {
            "data_mode": "real",
            "rows": [
              {
                "best_variant_index": 0,
                "cells": [
                  {
                    "confidence": 0.86,
                    "confidence_label": "high",
                    "display": "Закат над морем с 5-го этажа, сторона ЮЗ",
                    "observation_ids": [
                      "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
                    ],
                    "status": "yes"
                  },
                  {
                    "confidence": 0.78,
                    "confidence_label": "medium",
                    "display": "Закат над морем с 5-го этажа, сторона ЮЗ",
                    "observation_ids": [
                      "16377e7c-2277-5be8-8872-5988c294d3c6"
                    ],
                    "status": "yes"
                  }
                ],
                "characteristic_id": "CH-SUNSET",
                "differs": false,
                "label": "Закат из окна",
                "need_index": 0
              },
              {
                "best_variant_index": 0,
                "cells": [
                  {
                    "confidence": 0.86,
                    "confidence_label": "high",
                    "display": "Море видно с 5-го этажа",
                    "observation_ids": [
                      "0db6f0b6-b376-5738-b877-d9a9ca9ae79c"
                    ],
                    "status": "yes"
                  },
                  {
                    "confidence": 0.78,
                    "confidence_label": "medium",
                    "display": "Море видно с 5-го этажа",
                    "observation_ids": [
                      "be6d5881-856b-558c-b532-c172841cd0d0"
                    ],
                    "status": "partial"
                  }
                ],
                "characteristic_id": "CH-SEA-VIEW",
                "differs": true,
                "label": "Вид на море",
                "need_index": null
              },
              {
                "best_variant_index": 1,
                "cells": [
                  {
                    "confidence": 0.86,
                    "confidence_label": "high",
                    "display": "Ночью шумно со стороны трассы",
                    "observation_ids": [
                      "39a55f35-0329-5c71-b687-483a2ed6c8a0"
                    ],
                    "status": "partial"
                  },
                  {
                    "confidence": 0.78,
                    "confidence_label": "medium",
                    "display": "Ночью тихо: трасса за домом",
                    "observation_ids": [
                      "d5f8ccd4-5f4e-55a8-a820-761512f3a86d"
                    ],
                    "status": "yes"
                  }
                ],
                "characteristic_id": "CH-QUIET",
                "differs": true,
                "label": "Тишина ночью",
                "need_index": null
              },
              {
                "best_variant_index": 0,
                "cells": [
                  {
                    "confidence": 0.86,
                    "confidence_label": "high",
                    "display": "7 мин к морю, обратно 11",
                    "observation_ids": [
                      "de0db691-db80-59df-badb-64035219ad81"
                    ],
                    "status": "yes"
                  },
                  {
                    "confidence": 0.78,
                    "confidence_label": "medium",
                    "display": "6 мин к морю, обратно 9",
                    "observation_ids": [
                      "35820f60-cf8d-5ec3-832d-7e3844d0e9f2"
                    ],
                    "status": "yes"
                  }
                ],
                "characteristic_id": "CH-WALK-SEA",
                "differs": false,
                "label": "Пешком до моря",
                "need_index": null
              }
            ],
            "summary": "Лучше всего подходит Гостиница «Бриз»: закат над морем и тихо ночью.",
            "variants": [
              {
                "floor": 5,
                "hotel": {
                  "address": "Курортный проспект, 18",
                  "district": "center",
                  "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                  "kind": "hotel",
                  "location": {
                    "lat": 43.579,
                    "lon": 39.723
                  },
                  "name": "Гостиница «Бриз»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
                    "url": "/media/photos/briz.jpg"
                  },
                  "stars": 4,
                  "walk_back_min": 11,
                  "walk_to_sea_min": 7
                },
                "side": "ЮЗ",
                "variant_index": 0
              },
              {
                "floor": null,
                "hotel": {
                  "address": "ул. Таврическая, 2",
                  "district": "kudepsta",
                  "id": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                  "kind": "hotel",
                  "location": {
                    "lat": 43.4985,
                    "lon": 39.892
                  },
                  "name": "Отель «Кудепста Плаза»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_kudepsta_plaza.jpg",
                    "url": "/media/photos/kudepsta-plaza.jpg"
                  },
                  "stars": 4,
                  "walk_back_min": 9,
                  "walk_to_sea_min": 6
                },
                "side": null,
                "variant_index": 1
              }
            ]
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "rows": {
            "items": {
              "$ref": "#/components/schemas/CompareRow"
            },
            "maxItems": 40,
            "title": "Rows",
            "type": "array"
          },
          "summary": {
            "description": "Вывод одной фразой",
            "maxLength": 300,
            "minLength": 1,
            "title": "Summary",
            "type": "string"
          },
          "variants": {
            "items": {
              "$ref": "#/components/schemas/CompareColumn"
            },
            "maxItems": 4,
            "minItems": 2,
            "title": "Variants",
            "type": "array"
          }
        },
        "required": [
          "data_mode",
          "variants",
          "rows",
          "summary"
        ],
        "title": "CompareResponse",
        "type": "object"
      },
      "CompareRow": {
        "additionalProperties": false,
        "examples": [
          {
            "best_variant_index": 0,
            "cells": [
              {
                "confidence": 0.86,
                "confidence_label": "high",
                "display": "Закат над морем с 5-го этажа, сторона ЮЗ",
                "observation_ids": [
                  "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
                ],
                "status": "yes"
              },
              {
                "confidence": 0.78,
                "confidence_label": "medium",
                "display": "Закат над морем с 5-го этажа, сторона ЮЗ",
                "observation_ids": [
                  "16377e7c-2277-5be8-8872-5988c294d3c6"
                ],
                "status": "yes"
              }
            ],
            "characteristic_id": "CH-SUNSET",
            "differs": false,
            "label": "Закат из окна",
            "need_index": 0
          }
        ],
        "properties": {
          "best_variant_index": {
            "anyOf": [
              {
                "maximum": 3.0,
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Лучший в строке, если он есть",
            "title": "Best Variant Index"
          },
          "cells": {
            "items": {
              "$ref": "#/components/schemas/CompareCell"
            },
            "maxItems": 4,
            "minItems": 2,
            "title": "Cells",
            "type": "array"
          },
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "differs": {
            "description": "Значения различаются: для «Только различия»",
            "title": "Differs",
            "type": "boolean"
          },
          "label": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Label",
            "type": "string"
          },
          "need_index": {
            "anyOf": [
              {
                "maximum": 11.0,
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Need Index"
          }
        },
        "required": [
          "characteristic_id",
          "label",
          "need_index",
          "cells",
          "best_variant_index",
          "differs"
        ],
        "title": "CompareRow",
        "type": "object"
      },
      "CompareVariantIn": {
        "additionalProperties": false,
        "description": "Вариант для сравнения: отель или отель со стороной и этажом.",
        "examples": [
          {
            "floor": 5,
            "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "side": "ЮЗ"
          }
        ],
        "properties": {
          "floor": {
            "anyOf": [
              {
                "description": "Этаж, 1-40",
                "maximum": 40.0,
                "minimum": 1.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Floor"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "side": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rumb"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "hotel_id"
        ],
        "title": "CompareVariantIn",
        "type": "object"
      },
      "Condition": {
        "additionalProperties": false,
        "description": "Условие потребности: имя условия спецификации и его параметр. Без параметра value - null.",
        "examples": [
          {
            "op": "sea_on_dates",
            "value": null
          }
        ],
        "properties": {
          "op": {
            "description": "Имя условия из search.conditions спецификации, например max_minutes или sea_on_dates",
            "maxLength": 32,
            "minLength": 1,
            "pattern": "^[a-z][a-z0-9_]{0,31}$",
            "title": "Op",
            "type": "string"
          },
          "value": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "maximum": 1000000.0,
                "minimum": -1000000.0,
                "type": "integer"
              },
              {
                "maximum": 1000000.0,
                "minimum": -1000000.0,
                "type": "number"
              },
              {
                "maxLength": 80,
                "type": "string"
              },
              {
                "items": {
                  "maxLength": 80,
                  "type": "string"
                },
                "maxItems": 20,
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "description": "Параметр условия (param) или null, если его нет",
            "title": "Value"
          }
        },
        "required": [
          "op",
          "value"
        ],
        "title": "Condition",
        "type": "object"
      },
      "ConfidenceBreakdown": {
        "additionalProperties": false,
        "description": "confidence = trust × quality × freshness.",
        "examples": [
          {
            "freshness": 1.0,
            "quality": 0.95,
            "trust": 0.85
          }
        ],
        "properties": {
          "freshness": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Freshness",
            "type": "number"
          },
          "quality": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Quality",
            "type": "number"
          },
          "trust": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Trust",
            "type": "number"
          }
        },
        "required": [
          "trust",
          "quality",
          "freshness"
        ],
        "title": "ConfidenceBreakdown",
        "type": "object"
      },
      "ConfidenceLabel": {
        "description": "Словесная шкала: высокая от 0,8, средняя от 0,5, низкая ниже 0,5.",
        "enum": [
          "high",
          "medium",
          "low"
        ],
        "examples": [
          "high"
        ],
        "title": "ConfidenceLabel",
        "type": "string"
      },
      "Conflict": {
        "additionalProperties": false,
        "examples": [
          {
            "characteristic_id": "CH-SEA-VIEW",
            "created_at": "2026-10-23T09:00:00Z",
            "hotel_id": "e30ee3ba-c3ae-5cf1-acb1-5d70c7b6f0a4",
            "hotel_name": "Отель «Кипарис»",
            "id": "e3a0d001-e2b0-5c89-94da-6942335cd9e9",
            "observations": [
              {
                "confidence": 0.72,
                "display": "Расчёт: с 3-го этажа море закрывает соседний дом",
                "observation_id": "c2283979-9a71-5935-8a3a-7d9fda8a89d9",
                "observed_at": "2026-10-18T09:00:00Z",
                "source_type": "computed"
              },
              {
                "confidence": 0.62,
                "display": "Отельер: море видно с 2-го этажа",
                "observation_id": "79aa5334-56ee-5547-8b8e-60540ace5bb5",
                "observed_at": "2026-10-22T09:00:00Z",
                "source_type": "hotelier"
              }
            ],
            "scope": {
              "district": null,
              "floor": 3,
              "key": "floor:З:3",
              "kind": "floor",
              "room": null,
              "side": "З"
            },
            "state": "open",
            "suggestion": {
              "action": "ask_hotelier",
              "observation_id": null,
              "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
            },
            "title": "Вид на море"
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "hotel_name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Hotel Name",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "observations": {
            "items": {
              "$ref": "#/components/schemas/ConflictObservation"
            },
            "maxItems": 4,
            "minItems": 2,
            "title": "Observations",
            "type": "array"
          },
          "scope": {
            "$ref": "#/components/schemas/Scope"
          },
          "state": {
            "$ref": "#/components/schemas/ConflictState"
          },
          "suggestion": {
            "$ref": "#/components/schemas/ConflictSuggestion"
          },
          "title": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Title",
            "type": "string"
          }
        },
        "required": [
          "id",
          "hotel_id",
          "hotel_name",
          "characteristic_id",
          "title",
          "scope",
          "state",
          "observations",
          "suggestion",
          "created_at"
        ],
        "title": "Conflict",
        "type": "object"
      },
      "ConflictAction": {
        "description": "Решения A4: принять значение, спросить отельера, отметить как неизвестное.",
        "enum": [
          "accept",
          "ask_hotelier",
          "mark_unknown"
        ],
        "examples": [
          "accept"
        ],
        "title": "ConflictAction",
        "type": "string"
      },
      "ConflictInfo": {
        "additionalProperties": false,
        "examples": [
          {
            "id": "531809e2-a4c0-503c-9c1e-c6898ed7c409",
            "observation_ids": [
              "c2283979-9a71-5935-8a3a-7d9fda8a89d9",
              "79aa5334-56ee-5547-8b8e-60540ace5bb5"
            ],
            "resolution": null,
            "scope_key": "floor:З:5",
            "state": "asked_hotelier"
          }
        ],
        "properties": {
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "observation_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 4,
            "minItems": 2,
            "title": "Observation Ids",
            "type": "array"
          },
          "resolution": {
            "anyOf": [
              {
                "maxLength": 300,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Resolution"
          },
          "scope_key": {
            "description": "Ключ уровня значения: hotel, side:ЮЗ, floor:ЮЗ:5, room:401 или room:<uuid>, district:adler; с датами - плюс |2026-06-01..2026-06-14",
            "maxLength": 80,
            "minLength": 5,
            "pattern": "^(hotel|side:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ)|floor:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ):([1-9]|[1-3][0-9]|40)|room:([0-9A-Za-z-]{1,20}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|district:(center|matsesta|khosta|kudepsta|adler|imeretinka))(\\|[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\\.\\.[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]))?$",
            "title": "Scope Key",
            "type": "string"
          },
          "state": {
            "$ref": "#/components/schemas/ConflictState"
          }
        },
        "required": [
          "id",
          "scope_key",
          "state",
          "observation_ids",
          "resolution"
        ],
        "title": "ConflictInfo",
        "type": "object"
      },
      "ConflictObservation": {
        "additionalProperties": false,
        "examples": [
          {
            "confidence": 0.72,
            "display": "Расчёт: с 3-го этажа море закрывает соседний дом",
            "observation_id": "c2283979-9a71-5935-8a3a-7d9fda8a89d9",
            "observed_at": "2026-10-18T09:00:00Z",
            "source_type": "computed"
          }
        ],
        "properties": {
          "confidence": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Confidence",
            "type": "number"
          },
          "display": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Display",
            "type": "string"
          },
          "observation_id": {
            "format": "uuid",
            "title": "Observation Id",
            "type": "string"
          },
          "observed_at": {
            "format": "date-time",
            "title": "Observed At",
            "type": "string"
          },
          "source_type": {
            "$ref": "#/components/schemas/SourceType"
          }
        },
        "required": [
          "observation_id",
          "source_type",
          "display",
          "confidence",
          "observed_at"
        ],
        "title": "ConflictObservation",
        "type": "object"
      },
      "ConflictPage": {
        "additionalProperties": false,
        "description": "Очередь конфликтов.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "characteristic_id": "CH-SEA-VIEW",
                "created_at": "2026-10-23T09:00:00Z",
                "hotel_id": "e30ee3ba-c3ae-5cf1-acb1-5d70c7b6f0a4",
                "hotel_name": "Отель «Кипарис»",
                "id": "e3a0d001-e2b0-5c89-94da-6942335cd9e9",
                "observations": [
                  {
                    "confidence": 0.72,
                    "display": "Расчёт: с 3-го этажа море закрывает соседний дом",
                    "observation_id": "c2283979-9a71-5935-8a3a-7d9fda8a89d9",
                    "observed_at": "2026-10-18T09:00:00Z",
                    "source_type": "computed"
                  },
                  {
                    "confidence": 0.62,
                    "display": "Отельер: море видно с 2-го этажа",
                    "observation_id": "79aa5334-56ee-5547-8b8e-60540ace5bb5",
                    "observed_at": "2026-10-22T09:00:00Z",
                    "source_type": "hotelier"
                  }
                ],
                "scope": {
                  "district": null,
                  "floor": 3,
                  "key": "floor:З:3",
                  "kind": "floor",
                  "room": null,
                  "side": "З"
                },
                "state": "open",
                "suggestion": {
                  "action": "ask_hotelier",
                  "observation_id": null,
                  "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
                },
                "title": "Вид на море"
              },
              {
                "characteristic_id": "CH-SUNSET",
                "created_at": "2026-10-22T09:00:00Z",
                "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                "hotel_name": "Отель «Мацеста Хиллс»",
                "id": "4f84056f-b1bb-5b7e-89d2-587cdafb632a",
                "observations": [
                  {
                    "confidence": 0.72,
                    "display": "Расчёт: с 4-го этажа море закрывает соседний дом",
                    "observation_id": "8d352e7d-852b-563f-82ef-1a819ea5884b",
                    "observed_at": "2026-10-18T09:00:00Z",
                    "source_type": "computed"
                  },
                  {
                    "confidence": 0.62,
                    "display": "Отельер: море видно с 3-го этажа",
                    "observation_id": "9920bff4-372f-5f12-88af-095bcb4204f0",
                    "observed_at": "2026-10-22T09:00:00Z",
                    "source_type": "hotelier"
                  }
                ],
                "scope": {
                  "district": null,
                  "floor": 4,
                  "key": "floor:Ю:4",
                  "kind": "floor",
                  "room": null,
                  "side": "Ю"
                },
                "state": "asked_hotelier",
                "suggestion": {
                  "action": "ask_hotelier",
                  "observation_id": null,
                  "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
                },
                "title": "Закат из окна"
              },
              {
                "characteristic_id": "CH-QUIET",
                "created_at": "2026-10-21T09:00:00Z",
                "hotel_id": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                "hotel_name": "Отель «Кудепста Плаза»",
                "id": "5806bdec-4b2e-52b7-93d4-5a9365a3c2c4",
                "observations": [
                  {
                    "confidence": 0.72,
                    "display": "Расчёт: с 5-го этажа море закрывает соседний дом",
                    "observation_id": "d5f8ccd4-5f4e-55a8-a820-761512f3a86d",
                    "observed_at": "2026-10-18T09:00:00Z",
                    "source_type": "computed"
                  },
                  {
                    "confidence": 0.62,
                    "display": "Отельер: море видно с 4-го этажа",
                    "observation_id": "256746b1-e115-595d-ae5a-673981274e03",
                    "observed_at": "2026-10-22T09:00:00Z",
                    "source_type": "hotelier"
                  }
                ],
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:ЮЗ:5",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "state": "auto_resolved",
                "suggestion": {
                  "action": "ask_hotelier",
                  "observation_id": null,
                  "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
                },
                "title": "Тишина ночью"
              },
              {
                "characteristic_id": "CH-SEA-VIEW",
                "created_at": "2026-10-20T09:00:00Z",
                "hotel_id": "b31b83c7-4c47-5ff9-bb5e-38efed4e7d81",
                "hotel_name": "Отель «Имерети Бэй»",
                "id": "a5910ecf-7c9d-52f9-b27d-22ab2ae38fa7",
                "observations": [
                  {
                    "confidence": 0.72,
                    "display": "Расчёт: с 3-го этажа море закрывает соседний дом",
                    "observation_id": "79f7c0f2-5485-5b01-8d55-2546892af09b",
                    "observed_at": "2026-10-18T09:00:00Z",
                    "source_type": "computed"
                  },
                  {
                    "confidence": 0.62,
                    "display": "Отельер: море видно с 2-го этажа",
                    "observation_id": "3020335e-4c79-5490-9a22-3308d431cd6c",
                    "observed_at": "2026-10-22T09:00:00Z",
                    "source_type": "hotelier"
                  }
                ],
                "scope": {
                  "district": null,
                  "floor": 3,
                  "key": "floor:ЮЗ:3",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "state": "open",
                "suggestion": {
                  "action": "ask_hotelier",
                  "observation_id": null,
                  "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
                },
                "title": "Вид на море"
              },
              {
                "characteristic_id": "CH-SUNSET",
                "created_at": "2026-10-19T09:00:00Z",
                "hotel_id": "e30ee3ba-c3ae-5cf1-acb1-5d70c7b6f0a4",
                "hotel_name": "Отель «Кипарис»",
                "id": "94f2b234-628d-5ebb-86ce-9d895423ae51",
                "observations": [
                  {
                    "confidence": 0.72,
                    "display": "Расчёт: с 4-го этажа море закрывает соседний дом",
                    "observation_id": "d0c94b98-6726-5dd4-be71-259d3b0c9ae7",
                    "observed_at": "2026-10-18T09:00:00Z",
                    "source_type": "computed"
                  },
                  {
                    "confidence": 0.62,
                    "display": "Отельер: море видно с 3-го этажа",
                    "observation_id": "b7759c5b-a7b4-5451-9a33-4cefe8b6d6f8",
                    "observed_at": "2026-10-22T09:00:00Z",
                    "source_type": "hotelier"
                  }
                ],
                "scope": {
                  "district": null,
                  "floor": 4,
                  "key": "floor:З:4",
                  "kind": "floor",
                  "room": null,
                  "side": "З"
                },
                "state": "asked_hotelier",
                "suggestion": {
                  "action": "ask_hotelier",
                  "observation_id": null,
                  "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
                },
                "title": "Закат из окна"
              },
              {
                "characteristic_id": "CH-QUIET",
                "created_at": "2026-10-18T09:00:00Z",
                "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                "hotel_name": "Отель «Мацеста Хиллс»",
                "id": "fda1cd76-7072-54c3-b68f-37be56b4ab9b",
                "observations": [
                  {
                    "confidence": 0.72,
                    "display": "Расчёт: с 5-го этажа море закрывает соседний дом",
                    "observation_id": "12440c6d-35bb-54d7-abee-965d352572ee",
                    "observed_at": "2026-10-18T09:00:00Z",
                    "source_type": "computed"
                  },
                  {
                    "confidence": 0.62,
                    "display": "Отельер: море видно с 4-го этажа",
                    "observation_id": "f349a6d1-0a3a-5d67-b4e9-b47c01e7d8c5",
                    "observed_at": "2026-10-22T09:00:00Z",
                    "source_type": "hotelier"
                  }
                ],
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:Ю:5",
                  "kind": "floor",
                  "room": null,
                  "side": "Ю"
                },
                "state": "auto_resolved",
                "suggestion": {
                  "action": "ask_hotelier",
                  "observation_id": null,
                  "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
                },
                "title": "Тишина ночью"
              },
              {
                "characteristic_id": "CH-SEA-VIEW",
                "created_at": "2026-10-17T09:00:00Z",
                "hotel_id": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                "hotel_name": "Отель «Кудепста Плаза»",
                "id": "33e7651d-88d3-5dd3-8f60-a3dba38232f8",
                "observations": [
                  {
                    "confidence": 0.72,
                    "display": "Расчёт: с 3-го этажа море закрывает соседний дом",
                    "observation_id": "be6d5881-856b-558c-b532-c172841cd0d0",
                    "observed_at": "2026-10-18T09:00:00Z",
                    "source_type": "computed"
                  },
                  {
                    "confidence": 0.62,
                    "display": "Отельер: море видно с 2-го этажа",
                    "observation_id": "81497d1e-f5ba-5c03-bb5e-18dadb055e4b",
                    "observed_at": "2026-10-22T09:00:00Z",
                    "source_type": "hotelier"
                  }
                ],
                "scope": {
                  "district": null,
                  "floor": 3,
                  "key": "floor:ЮЗ:3",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "state": "open",
                "suggestion": {
                  "action": "ask_hotelier",
                  "observation_id": null,
                  "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
                },
                "title": "Вид на море"
              },
              {
                "characteristic_id": "CH-SUNSET",
                "created_at": "2026-10-16T09:00:00Z",
                "hotel_id": "b31b83c7-4c47-5ff9-bb5e-38efed4e7d81",
                "hotel_name": "Отель «Имерети Бэй»",
                "id": "6e1f6bca-fa8f-5948-813c-ef042852ac7a",
                "observations": [
                  {
                    "confidence": 0.72,
                    "display": "Расчёт: с 4-го этажа море закрывает соседний дом",
                    "observation_id": "8b2aa13e-3348-5cd7-8490-c2b7433e5926",
                    "observed_at": "2026-10-18T09:00:00Z",
                    "source_type": "computed"
                  },
                  {
                    "confidence": 0.62,
                    "display": "Отельер: море видно с 3-го этажа",
                    "observation_id": "4b50e9d2-7c98-51fb-980b-733efa31775d",
                    "observed_at": "2026-10-22T09:00:00Z",
                    "source_type": "hotelier"
                  }
                ],
                "scope": {
                  "district": null,
                  "floor": 4,
                  "key": "floor:ЮЗ:4",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "state": "asked_hotelier",
                "suggestion": {
                  "action": "ask_hotelier",
                  "observation_id": null,
                  "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
                },
                "title": "Закат из окна"
              },
              {
                "characteristic_id": "CH-QUIET",
                "created_at": "2026-10-15T09:00:00Z",
                "hotel_id": "e30ee3ba-c3ae-5cf1-acb1-5d70c7b6f0a4",
                "hotel_name": "Отель «Кипарис»",
                "id": "754b769d-810c-5be2-8cbe-d9422ff1cad9",
                "observations": [
                  {
                    "confidence": 0.72,
                    "display": "Расчёт: с 5-го этажа море закрывает соседний дом",
                    "observation_id": "48d4a75f-c440-539a-b223-060718234c28",
                    "observed_at": "2026-10-18T09:00:00Z",
                    "source_type": "computed"
                  },
                  {
                    "confidence": 0.62,
                    "display": "Отельер: море видно с 4-го этажа",
                    "observation_id": "beade413-bd4a-5a07-beca-3bb9be01ad2f",
                    "observed_at": "2026-10-22T09:00:00Z",
                    "source_type": "hotelier"
                  }
                ],
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:З:5",
                  "kind": "floor",
                  "room": null,
                  "side": "З"
                },
                "state": "auto_resolved",
                "suggestion": {
                  "action": "ask_hotelier",
                  "observation_id": null,
                  "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
                },
                "title": "Тишина ночью"
              }
            ],
            "page": 1,
            "page_size": 10,
            "total": 9
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/Conflict"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "ConflictPage",
        "type": "object"
      },
      "ConflictResolved": {
        "additionalProperties": false,
        "examples": [
          {
            "data_mode": "real",
            "id": "531809e2-a4c0-503c-9c1e-c6898ed7c409",
            "question_id": null,
            "resolution": "Принято наблюдение c2283979-9a71-5935-8a3a-7d9fda8a89d9",
            "state": "resolved"
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "question_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Question Id"
          },
          "resolution": {
            "maxLength": 300,
            "minLength": 1,
            "title": "Resolution",
            "type": "string"
          },
          "state": {
            "$ref": "#/components/schemas/ConflictState"
          }
        },
        "required": [
          "data_mode",
          "id",
          "state",
          "resolution",
          "question_id"
        ],
        "title": "ConflictResolved",
        "type": "object"
      },
      "ConflictState": {
        "enum": [
          "open",
          "auto_resolved",
          "asked_hotelier",
          "resolved"
        ],
        "examples": [
          "open"
        ],
        "title": "ConflictState",
        "type": "string"
      },
      "ConflictSuggestion": {
        "additionalProperties": false,
        "description": "Предложение AG-TRUTH.",
        "examples": [
          {
            "action": "ask_hotelier",
            "observation_id": null,
            "text": "Разница уверенностей 0,1: спросить отельера и приложить панораму горизонта"
          }
        ],
        "properties": {
          "action": {
            "$ref": "#/components/schemas/ConflictAction"
          },
          "observation_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Observation Id"
          },
          "text": {
            "maxLength": 300,
            "minLength": 1,
            "title": "Text",
            "type": "string"
          }
        },
        "required": [
          "action",
          "observation_id",
          "text"
        ],
        "title": "ConflictSuggestion",
        "type": "object"
      },
      "Consent": {
        "additionalProperties": false,
        "examples": [
          {
            "data_mode": "real",
            "granted_at": "2026-10-23T09:00:00Z",
            "kind": "voice",
            "revoked_at": null,
            "text_version": "v1"
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "granted_at": {
            "format": "date-time",
            "title": "Granted At",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/ConsentKind"
          },
          "revoked_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Revoked At"
          },
          "text_version": {
            "maxLength": 12,
            "minLength": 2,
            "title": "Text Version",
            "type": "string"
          }
        },
        "required": [
          "data_mode",
          "kind",
          "text_version",
          "granted_at",
          "revoked_at"
        ],
        "title": "Consent",
        "type": "object"
      },
      "ConsentIn": {
        "additionalProperties": false,
        "description": "Согласие на память или голос. text_version - версия показанного текста согласия.",
        "examples": [
          {
            "kind": "voice",
            "text_version": "v1"
          }
        ],
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/ConsentKind"
          },
          "text_version": {
            "maxLength": 12,
            "minLength": 2,
            "pattern": "^v[0-9]{1,3}(\\.[0-9]{1,3})?$",
            "title": "Text Version",
            "type": "string"
          }
        },
        "required": [
          "kind",
          "text_version"
        ],
        "title": "ConsentIn",
        "type": "object"
      },
      "ConsentKind": {
        "enum": [
          "memory",
          "voice"
        ],
        "examples": [
          "memory"
        ],
        "title": "ConsentKind",
        "type": "string"
      },
      "DataMode": {
        "description": "Режим данных ответа: заглушка или реальные данные.",
        "enum": [
          "mock",
          "real"
        ],
        "examples": [
          "mock"
        ],
        "title": "DataMode",
        "type": "string"
      },
      "DataSource": {
        "additionalProperties": false,
        "examples": [
          {
            "attribution": "© участники OpenStreetMap",
            "data_date": "2026-10-22",
            "freshness_days": 1,
            "id": "SRC-01",
            "last_check": "2026-10-23T08:00:00Z",
            "last_error": null,
            "license": "ODbL",
            "limits": null,
            "name": "OpenStreetMap, выгрузка Geofabrik",
            "status": "ok",
            "url": null,
            "version": null,
            "what": "Отели, здания, дороги, заведения"
          }
        ],
        "properties": {
          "attribution": {
            "maxLength": 300,
            "minLength": 1,
            "title": "Attribution",
            "type": "string"
          },
          "data_date": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Data Date"
          },
          "freshness_days": {
            "anyOf": [
              {
                "maximum": 10000.0,
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Freshness Days"
          },
          "id": {
            "description": "ID источника",
            "maxLength": 6,
            "minLength": 6,
            "pattern": "^SRC-[0-9]{2}$",
            "title": "Id",
            "type": "string"
          },
          "last_check": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Check"
          },
          "last_error": {
            "anyOf": [
              {
                "maxLength": 300,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Error"
          },
          "license": {
            "maxLength": 80,
            "minLength": 1,
            "title": "License",
            "type": "string"
          },
          "limits": {
            "anyOf": [
              {
                "maxLength": 200,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Limits"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SourceStatus"
          },
          "url": {
            "anyOf": [
              {
                "maxLength": 500,
                "minLength": 1,
                "pattern": "^(https://|/)",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Url"
          },
          "version": {
            "anyOf": [
              {
                "maxLength": 60,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Version"
          },
          "what": {
            "description": "Что берём",
            "maxLength": 200,
            "minLength": 1,
            "title": "What",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "what",
          "license",
          "attribution",
          "version",
          "data_date",
          "freshness_days",
          "status",
          "last_check",
          "last_error",
          "limits",
          "url"
        ],
        "title": "DataSource",
        "type": "object"
      },
      "DataSourcePage": {
        "additionalProperties": false,
        "description": "Источники данных.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "attribution": "© участники OpenStreetMap",
                "data_date": "2026-10-22",
                "freshness_days": 1,
                "id": "SRC-01",
                "last_check": "2026-10-23T08:00:00Z",
                "last_error": null,
                "license": "ODbL",
                "limits": null,
                "name": "OpenStreetMap, выгрузка Geofabrik",
                "status": "ok",
                "url": null,
                "version": null,
                "what": "Отели, здания, дороги, заведения"
              },
              {
                "attribution": "© DLR e.V., © Airbus DS, ESA",
                "data_date": null,
                "freshness_days": null,
                "id": "SRC-02",
                "last_check": "2026-10-23T08:00:00Z",
                "last_error": null,
                "license": "Copernicus DEM",
                "limits": null,
                "name": "Copernicus GLO-30",
                "status": "ok",
                "url": null,
                "version": null,
                "what": "Рельеф: вид, закат, горы"
              },
              {
                "attribution": "Protomaps, © участники OpenStreetMap",
                "data_date": "2026-10-23",
                "freshness_days": 0,
                "id": "SRC-03",
                "last_check": "2026-10-23T08:00:00Z",
                "last_error": null,
                "license": "BSD и ODbL",
                "limits": null,
                "name": "Подложка Protomaps",
                "status": "ok",
                "url": null,
                "version": null,
                "what": "Подложка карты"
              },
              {
                "attribution": "Open-Meteo, CC BY 4.0",
                "data_date": null,
                "freshness_days": null,
                "id": "SRC-06",
                "last_check": "2026-10-23T08:00:00Z",
                "last_error": null,
                "license": "CC BY 4.0",
                "limits": "600 запросов в минуту, только некоммерчески",
                "name": "Open-Meteo Historical (ERA5)",
                "status": "ok",
                "url": null,
                "version": null,
                "what": "Климат по месяцам"
              },
              {
                "attribution": "Open-Meteo, CC BY 4.0",
                "data_date": null,
                "freshness_days": null,
                "id": "SRC-07",
                "last_check": "2026-10-23T08:00:00Z",
                "last_error": "HTTP 503 два раза подряд, повтор через 10 минут",
                "license": "CC BY 4.0",
                "limits": "600 запросов в минуту, только некоммерчески",
                "name": "Open-Meteo Marine",
                "status": "degraded",
                "url": null,
                "version": null,
                "what": "Температура моря"
              },
              {
                "attribution": "Open-Meteo, CC BY 4.0",
                "data_date": null,
                "freshness_days": null,
                "id": "SRC-08",
                "last_check": "2026-10-23T08:00:00Z",
                "last_error": null,
                "license": "CC BY 4.0",
                "limits": "600 запросов в минуту, только некоммерчески",
                "name": "Open-Meteo Air Quality (CAMS)",
                "status": "ok",
                "url": null,
                "version": null,
                "what": "Качество воздуха"
              },
              {
                "attribution": "Автор и лицензия у каждого фото",
                "data_date": null,
                "freshness_days": null,
                "id": "SRC-09",
                "last_check": "2026-10-23T08:00:00Z",
                "last_error": null,
                "license": "Своя у каждого файла",
                "limits": null,
                "name": "Wikimedia Commons",
                "status": "ok",
                "url": null,
                "version": null,
                "what": "Фото"
              },
              {
                "attribution": "Wikidata, CC0",
                "data_date": null,
                "freshness_days": null,
                "id": "SRC-10",
                "last_check": "2026-10-23T08:00:00Z",
                "last_error": "Таймаут 60 с",
                "license": "CC0",
                "limits": null,
                "name": "Wikidata",
                "status": "unavailable",
                "url": null,
                "version": null,
                "what": "История зданий, ориентиры"
              },
              {
                "attribution": "Собрано вручную",
                "data_date": "2026-10-20",
                "freshness_days": 3,
                "id": "SRC-11",
                "last_check": "2026-10-23T08:00:00Z",
                "last_error": null,
                "license": "Свои записи",
                "limits": null,
                "name": "Мини-расписание",
                "status": "ok",
                "url": null,
                "version": null,
                "what": "Дорога из аэропорта"
              }
            ],
            "page": 1,
            "page_size": 10,
            "total": 9
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/DataSource"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "DataSourcePage",
        "type": "object"
      },
      "DateRange": {
        "additionalProperties": false,
        "description": "Даты поездки, включительно: from не позже to, не больше 31 дня.",
        "examples": [
          {
            "from": "2026-05-03",
            "to": "2026-05-07"
          }
        ],
        "properties": {
          "from": {
            "format": "date",
            "title": "From",
            "type": "string"
          },
          "to": {
            "format": "date",
            "title": "To",
            "type": "string"
          }
        },
        "required": [
          "from",
          "to"
        ],
        "title": "DateRange",
        "type": "object"
      },
      "Demand": {
        "additionalProperties": false,
        "examples": [
          {
            "daily": [
              {
                "date": "2026-10-17",
                "impressions": 18,
                "searches": 30
              },
              {
                "date": "2026-10-18",
                "impressions": 23,
                "searches": 37
              },
              {
                "date": "2026-10-19",
                "impressions": 28,
                "searches": 31
              },
              {
                "date": "2026-10-20",
                "impressions": 22,
                "searches": 38
              },
              {
                "date": "2026-10-21",
                "impressions": 27,
                "searches": 32
              },
              {
                "date": "2026-10-22",
                "impressions": 21,
                "searches": 39
              },
              {
                "date": "2026-10-23",
                "impressions": 26,
                "searches": 33
              }
            ],
            "data_mode": "real",
            "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "kpi": {
              "impressions": 165,
              "matches": 96,
              "unknown_items": 23
            },
            "needs": [
              {
                "action": "confirm",
                "characteristic_id": "CH-SUNSET",
                "label": "Закат из окна",
                "question_id": null,
                "searches": 64,
                "share": 0.379,
                "state": "yes"
              },
              {
                "action": "confirm",
                "characteristic_id": "CH-QUIET",
                "label": "Тишина ночью",
                "question_id": null,
                "searches": 51,
                "share": 0.302,
                "state": "partial"
              },
              {
                "action": "answer",
                "characteristic_id": "CH-PETS",
                "label": "С питомцем",
                "question_id": "c1e32eee-a4be-586d-b005-fe47e494a111",
                "searches": 23,
                "share": 0.136,
                "state": "unknown"
              },
              {
                "action": "none",
                "characteristic_id": "CH-WALK-SEA",
                "label": "Пешком до моря",
                "question_id": null,
                "searches": 19,
                "share": 0.112,
                "state": "yes"
              },
              {
                "action": "none",
                "characteristic_id": "CH-FAMILY",
                "label": "Семьям с детьми",
                "question_id": null,
                "searches": 12,
                "share": 0.071,
                "state": "no"
              }
            ],
            "period_days": 7,
            "top_hint": "Ответьте про питомцев: это ищут 23 раза в неделю, а у вас «нет данных»"
          }
        ],
        "properties": {
          "daily": {
            "items": {
              "$ref": "#/components/schemas/DemandDay"
            },
            "maxItems": 31,
            "title": "Daily",
            "type": "array"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "kpi": {
            "$ref": "#/components/schemas/DemandKpi"
          },
          "needs": {
            "items": {
              "$ref": "#/components/schemas/DemandNeed"
            },
            "maxItems": 30,
            "title": "Needs",
            "type": "array"
          },
          "period_days": {
            "enum": [
              7,
              30
            ],
            "title": "Period Days",
            "type": "integer"
          },
          "top_hint": {
            "anyOf": [
              {
                "maxLength": 200,
                "minLength": 1,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Какой один ответ поднимет отель в выдаче",
            "title": "Top Hint"
          }
        },
        "required": [
          "data_mode",
          "hotel_id",
          "period_days",
          "kpi",
          "daily",
          "needs",
          "top_hint"
        ],
        "title": "Demand",
        "type": "object"
      },
      "DemandAction": {
        "description": "Что отельер может сделать по потребности в H5.",
        "enum": [
          "confirm",
          "answer",
          "none"
        ],
        "examples": [
          "confirm"
        ],
        "title": "DemandAction",
        "type": "string"
      },
      "DemandDay": {
        "additionalProperties": false,
        "examples": [
          {
            "date": "2026-10-17",
            "impressions": 18,
            "searches": 30
          }
        ],
        "properties": {
          "date": {
            "format": "date",
            "title": "Date",
            "type": "string"
          },
          "impressions": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Impressions",
            "type": "integer"
          },
          "searches": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Searches",
            "type": "integer"
          }
        },
        "required": [
          "date",
          "searches",
          "impressions"
        ],
        "title": "DemandDay",
        "type": "object"
      },
      "DemandKpi": {
        "additionalProperties": false,
        "examples": [
          {
            "impressions": 165,
            "matches": 96,
            "unknown_items": 23
          }
        ],
        "properties": {
          "impressions": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Impressions",
            "type": "integer"
          },
          "matches": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Matches",
            "type": "integer"
          },
          "unknown_items": {
            "description": "Пункты «?» в выдаче",
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Unknown Items",
            "type": "integer"
          }
        },
        "required": [
          "impressions",
          "matches",
          "unknown_items"
        ],
        "title": "DemandKpi",
        "type": "object"
      },
      "DemandNeed": {
        "additionalProperties": false,
        "description": "Потребность гостей рядом: сколько искали и что с ней у отеля.",
        "examples": [
          {
            "action": "confirm",
            "characteristic_id": "CH-SUNSET",
            "label": "Закат из окна",
            "question_id": null,
            "searches": 64,
            "share": 0.379,
            "state": "yes"
          }
        ],
        "properties": {
          "action": {
            "$ref": "#/components/schemas/DemandAction"
          },
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "label": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Label",
            "type": "string"
          },
          "question_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Question Id"
          },
          "searches": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Searches",
            "type": "integer"
          },
          "share": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Share",
            "type": "number"
          },
          "state": {
            "$ref": "#/components/schemas/MatchStatus"
          }
        },
        "required": [
          "characteristic_id",
          "label",
          "searches",
          "state",
          "share",
          "action",
          "question_id"
        ],
        "title": "DemandNeed",
        "type": "object"
      },
      "DemoLoginIn": {
        "additionalProperties": false,
        "description": "Вход по роли одной кнопкой. Только при DEMO_MODE=1.",
        "examples": [
          {
            "role": "guest"
          }
        ],
        "properties": {
          "role": {
            "$ref": "#/components/schemas/Role"
          }
        },
        "required": [
          "role"
        ],
        "title": "DemoLoginIn",
        "type": "object"
      },
      "District": {
        "description": "Район зоны.",
        "enum": [
          "center",
          "matsesta",
          "khosta",
          "kudepsta",
          "adler",
          "imeretinka"
        ],
        "examples": [
          "center"
        ],
        "title": "District",
        "type": "string"
      },
      "DryRun": {
        "additionalProperties": false,
        "description": "Пробный прогон на 5 отелях в наборе dry_run.",
        "examples": [
          {
            "hotels": [
              {
                "confidence": 0.82,
                "confidence_label": "high",
                "display": "Пекарня в 4 мин, открыта с 7:00",
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "name": "Гостиница «Бриз»"
              },
              {
                "confidence": 0.75,
                "confidence_label": "medium",
                "display": "Пекарня в 5 мин, открыта с 7:00",
                "hotel_id": "e30ee3ba-c3ae-5cf1-acb1-5d70c7b6f0a4",
                "name": "Отель «Кипарис»"
              },
              {
                "confidence": 0.68,
                "confidence_label": "medium",
                "display": "Пекарня в 6 мин, открыта с 7:00",
                "hotel_id": "f597787a-31ae-5fcb-948a-f75388c093a9",
                "name": "Гостевой дом «Магнолия»"
              },
              {
                "confidence": 0.61,
                "confidence_label": "medium",
                "display": "Пекарня в 7 мин, открыта с 7:00",
                "hotel_id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                "name": "Апарт-отель «Платан»"
              },
              {
                "confidence": 0.54,
                "confidence_label": "medium",
                "display": "Пекарня в 8 мин, открыта с 7:00",
                "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                "name": "Отель «Мацеста Хиллс»"
              }
            ],
            "run_id": "64d320cc-9e9a-58de-bd33-77c3318290c0",
            "state": "done"
          }
        ],
        "properties": {
          "hotels": {
            "items": {
              "$ref": "#/components/schemas/DryRunHotel"
            },
            "maxItems": 5,
            "title": "Hotels",
            "type": "array"
          },
          "run_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Run Id"
          },
          "state": {
            "$ref": "#/components/schemas/RunState"
          }
        },
        "required": [
          "state",
          "run_id",
          "hotels"
        ],
        "title": "DryRun",
        "type": "object"
      },
      "DryRunHotel": {
        "additionalProperties": false,
        "examples": [
          {
            "confidence": 0.82,
            "confidence_label": "high",
            "display": "Пекарня в 4 мин, открыта с 7:00",
            "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "name": "Гостиница «Бриз»"
          }
        ],
        "properties": {
          "confidence": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Confidence",
            "type": "number"
          },
          "confidence_label": {
            "$ref": "#/components/schemas/ConfidenceLabel"
          },
          "display": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Display",
            "type": "string"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          }
        },
        "required": [
          "hotel_id",
          "name",
          "display",
          "confidence",
          "confidence_label"
        ],
        "title": "DryRunHotel",
        "type": "object"
      },
      "EventLevel": {
        "enum": [
          "info",
          "warning",
          "error"
        ],
        "examples": [
          "info"
        ],
        "title": "EventLevel",
        "type": "string"
      },
      "Evidence": {
        "additionalProperties": false,
        "description": "Доказательство значения: объект OSM, растр, ответ API, маршрут, фото, ответ отельера.",
        "examples": [
          {
            "kind": "raster",
            "label": "Профиль горизонта: Copernicus GLO-30 и здания OSM",
            "ref": "SRC-01@2026-10-22",
            "url": null
          }
        ],
        "properties": {
          "kind": {
            "$ref": "#/components/schemas/EvidenceKind"
          },
          "label": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Label",
            "type": "string"
          },
          "ref": {
            "maxLength": 300,
            "minLength": 1,
            "title": "Ref",
            "type": "string"
          },
          "url": {
            "anyOf": [
              {
                "maxLength": 500,
                "minLength": 1,
                "pattern": "^(https://|/)",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Url"
          }
        },
        "required": [
          "kind",
          "label",
          "ref",
          "url"
        ],
        "title": "Evidence",
        "type": "object"
      },
      "EvidenceKind": {
        "description": "Вид доказательства.",
        "enum": [
          "osm",
          "raster",
          "api",
          "route",
          "panorama",
          "photo",
          "answer",
          "quote"
        ],
        "examples": [
          "osm"
        ],
        "title": "EvidenceKind",
        "type": "string"
      },
      "Explanation": {
        "additionalProperties": false,
        "examples": [
          {
            "claims": [
              {
                "observation_ids": [
                  "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
                ],
                "text": "Закат над морем с 3-го этажа, сторона ЮЗ"
              },
              {
                "observation_ids": [
                  "39a55f35-0329-5c71-b687-483a2ed6c8a0"
                ],
                "text": "Ночью тихо: трасса за домом"
              },
              {
                "observation_ids": [
                  "d8d773fc-576c-5524-8894-06520f25b5cd"
                ],
                "text": "Ночью рядом: есть"
              }
            ],
            "generated_by": "template"
          }
        ],
        "properties": {
          "claims": {
            "items": {
              "$ref": "#/components/schemas/Claim"
            },
            "maxItems": 8,
            "title": "Claims",
            "type": "array"
          },
          "generated_by": {
            "$ref": "#/components/schemas/GeneratedBy"
          }
        },
        "required": [
          "claims",
          "generated_by"
        ],
        "title": "Explanation",
        "type": "object"
      },
      "ExplanationReadyEvent": {
        "additionalProperties": false,
        "description": "SSE `explanation.ready`: объяснение от LLM для варианта.",
        "examples": [
          {
            "data_mode": "real",
            "explanation": {
              "claims": [
                {
                  "observation_ids": [
                    "1bc28e5c-a481-58d2-9505-2de53fa58a4f",
                    "de0db691-db80-59df-badb-64035219ad81"
                  ],
                  "text": "Из окон стороны ЮЗ с 3-го этажа закат садится в море с апреля по август. До пляжа 7 минут пешком."
                }
              ],
              "generated_by": "llm"
            },
            "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "search_id": "1e349b6c-6a30-5d6e-98bc-040269a86280",
            "variant_id": "v1"
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "explanation": {
            "$ref": "#/components/schemas/Explanation"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "search_id": {
            "format": "uuid",
            "title": "Search Id",
            "type": "string"
          },
          "variant_id": {
            "maxLength": 5,
            "minLength": 2,
            "pattern": "^v[0-9]{1,4}$",
            "title": "Variant Id",
            "type": "string"
          }
        },
        "required": [
          "data_mode",
          "search_id",
          "variant_id",
          "hotel_id",
          "explanation"
        ],
        "title": "ExplanationReadyEvent",
        "type": "object"
      },
      "ExplanationsDoneEvent": {
        "additionalProperties": false,
        "description": "SSE `explanations.done`: поток закончен, остальное остаётся шаблоном.",
        "examples": [
          {
            "data_mode": "real",
            "ready": 3,
            "search_id": "1e349b6c-6a30-5d6e-98bc-040269a86280",
            "timed_out": false
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "ready": {
            "maximum": 100,
            "minimum": 0,
            "title": "Ready",
            "type": "integer"
          },
          "search_id": {
            "format": "uuid",
            "title": "Search Id",
            "type": "string"
          },
          "timed_out": {
            "title": "Timed Out",
            "type": "boolean"
          }
        },
        "required": [
          "data_mode",
          "search_id",
          "ready",
          "timed_out"
        ],
        "title": "ExplanationsDoneEvent",
        "type": "object"
      },
      "Fact": {
        "additionalProperties": false,
        "description": "Итоговое значение характеристики, которое видит гость (Fact).",
        "examples": [
          {
            "characteristic_id": "CH-SEA-VIEW",
            "confidence": 0.86,
            "confidence_label": "high",
            "display": "Море видно с 2-го этажа, сектор 62°",
            "hotelier_confirmed": false,
            "observed_at": "2026-10-20T09:00:00Z",
            "scope": {
              "district": null,
              "floor": 5,
              "key": "floor:ЮЗ:5",
              "kind": "floor",
              "room": null,
              "side": "ЮЗ"
            },
            "source_types": [
              "computed"
            ],
            "state": "ok",
            "title": "Вид на море",
            "valid_until": "2027-04-18",
            "value": {
              "from_floor": 2,
              "sector_deg": 62,
              "visible": true
            }
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "confidence": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Confidence",
            "type": "number"
          },
          "confidence_label": {
            "$ref": "#/components/schemas/ConfidenceLabel"
          },
          "display": {
            "description": "Короткое значение для людей",
            "maxLength": 200,
            "minLength": 1,
            "title": "Display",
            "type": "string"
          },
          "hotelier_confirmed": {
            "title": "Hotelier Confirmed",
            "type": "boolean"
          },
          "observed_at": {
            "format": "date-time",
            "title": "Observed At",
            "type": "string"
          },
          "scope": {
            "$ref": "#/components/schemas/Scope"
          },
          "source_types": {
            "items": {
              "$ref": "#/components/schemas/SourceType"
            },
            "maxItems": 7,
            "title": "Source Types",
            "type": "array"
          },
          "state": {
            "$ref": "#/components/schemas/ValueState"
          },
          "title": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Title",
            "type": "string"
          },
          "valid_until": {
            "format": "date",
            "title": "Valid Until",
            "type": "string"
          },
          "value": {
            "additionalProperties": true,
            "maxProperties": 40,
            "title": "Value",
            "type": "object"
          }
        },
        "required": [
          "characteristic_id",
          "title",
          "scope",
          "value",
          "display",
          "confidence",
          "confidence_label",
          "state",
          "source_types",
          "observed_at",
          "valid_until",
          "hotelier_confirmed"
        ],
        "title": "Fact",
        "type": "object"
      },
      "FactActionResult": {
        "additionalProperties": false,
        "examples": [
          {
            "conflict_id": null,
            "data_mode": "real",
            "fact": {
              "characteristic_id": "CH-SUNSET",
              "confidence": 0.84,
              "confidence_label": "high",
              "display": "Закат над морем со стороны ЮЗ, апрель - август",
              "hotelier_confirmed": true,
              "id": "2478e67b-904f-56d3-91ac-136b4382bb2b",
              "observed_at": "2026-10-20T09:00:00Z",
              "scope": {
                "district": null,
                "floor": 5,
                "key": "floor:ЮЗ:5",
                "kind": "floor",
                "room": null,
                "side": "ЮЗ"
              },
              "source_types": [
                "computed",
                "hotelier"
              ],
              "state": "ok",
              "title": "Закат из окна",
              "valid_until": "2027-04-18",
              "value": {
                "class": "sea",
                "months": [
                  "apr",
                  "may",
                  "jun",
                  "jul",
                  "aug"
                ]
              }
            },
            "observation_id": "6dc41f10-b077-5ad1-9378-bb99764fd9d4"
          }
        ],
        "properties": {
          "conflict_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Conflict Id"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "fact": {
            "$ref": "#/components/schemas/HotelierFact"
          },
          "observation_id": {
            "format": "uuid",
            "title": "Observation Id",
            "type": "string"
          }
        },
        "required": [
          "data_mode",
          "fact",
          "observation_id",
          "conflict_id"
        ],
        "title": "FactActionResult",
        "type": "object"
      },
      "FactCorrectionIn": {
        "additionalProperties": false,
        "description": "Исправление значения: новое наблюдение hotelier, при расхождении - конфликт.",
        "examples": [
          {
            "comment": "С 3-го этажа море видно над крышей соседнего дома",
            "scope_key": "floor:ЮЗ:3",
            "value": {
              "from_floor": 3,
              "visible": true
            }
          }
        ],
        "properties": {
          "comment": {
            "anyOf": [
              {
                "maxLength": 500,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Comment"
          },
          "scope_key": {
            "anyOf": [
              {
                "description": "Ключ уровня значения: hotel, side:ЮЗ, floor:ЮЗ:5, room:401 или room:<uuid>, district:adler; с датами - плюс |2026-06-01..2026-06-14",
                "maxLength": 80,
                "minLength": 5,
                "pattern": "^(hotel|side:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ)|floor:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ):([1-9]|[1-3][0-9]|40)|room:([0-9A-Za-z-]{1,20}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|district:(center|matsesta|khosta|kudepsta|adler|imeretinka))(\\|[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\\.\\.[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]))?$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Уточнить уровень, например floor:ЮЗ:3",
            "title": "Scope Key"
          },
          "value": {
            "additionalProperties": {
              "anyOf": [
                {
                  "maxLength": 500,
                  "type": "string"
                },
                {
                  "maximum": 1000000000.0,
                  "minimum": -1000000000.0,
                  "type": "integer"
                },
                {
                  "maximum": 1000000000.0,
                  "minimum": -1000000000.0,
                  "type": "number"
                },
                {
                  "type": "boolean"
                },
                {
                  "type": "null"
                },
                {
                  "items": {
                    "anyOf": [
                      {
                        "maxLength": 500,
                        "type": "string"
                      },
                      {
                        "maximum": 1000000000.0,
                        "minimum": -1000000000.0,
                        "type": "integer"
                      },
                      {
                        "maximum": 1000000000.0,
                        "minimum": -1000000000.0,
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "maxItems": 20,
                  "type": "array"
                }
              ]
            },
            "description": "Значение по value_schema характеристики, плоский объект",
            "maxProperties": 20,
            "minProperties": 1,
            "propertyNames": {
              "maxLength": 41,
              "not": {
                "enum": [
                  "confidence",
                  "method",
                  "observed_at",
                  "source",
                  "status",
                  "trust",
                  "valid_until"
                ]
              },
              "pattern": "^[a-z][a-z0-9_]{0,40}$",
              "type": "string"
            },
            "title": "Value",
            "type": "object"
          }
        },
        "required": [
          "value"
        ],
        "title": "FactCorrectionIn",
        "type": "object"
      },
      "FactFilter": {
        "description": "Сегменты H2: ждут подтверждения, конфликты, устарели.",
        "enum": [
          "pending",
          "conflict",
          "stale"
        ],
        "examples": [
          "pending"
        ],
        "title": "FactFilter",
        "type": "string"
      },
      "FloorSummary": {
        "additionalProperties": false,
        "description": "Сводка по этажу для отметок ползунка: «море с 5-го».",
        "examples": [
          {
            "floor": 1,
            "sea_sector_deg": 86.0,
            "sunset_over": "buildings"
          }
        ],
        "properties": {
          "floor": {
            "description": "Этаж, 1-40",
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floor",
            "type": "integer"
          },
          "sea_sector_deg": {
            "maximum": 160.0,
            "minimum": 0.0,
            "title": "Sea Sector Deg",
            "type": "number"
          },
          "sunset_over": {
            "$ref": "#/components/schemas/SunsetOver"
          }
        },
        "required": [
          "floor",
          "sea_sector_deg",
          "sunset_over"
        ],
        "title": "FloorSummary",
        "type": "object"
      },
      "GeneratedBy": {
        "enum": [
          "llm",
          "template"
        ],
        "examples": [
          "llm"
        ],
        "title": "GeneratedBy",
        "type": "string"
      },
      "GeoPoint": {
        "additionalProperties": false,
        "description": "Точка WGS84 в рамке подложки карты (app.zone.ZONE.frame).",
        "examples": [
          {
            "lat": 43.579,
            "lon": 39.723
          }
        ],
        "properties": {
          "lat": {
            "maximum": 43.72,
            "minimum": 43.35,
            "title": "Lat",
            "type": "number"
          },
          "lon": {
            "maximum": 40.1,
            "minimum": 39.55,
            "title": "Lon",
            "type": "number"
          }
        },
        "required": [
          "lat",
          "lon"
        ],
        "title": "GeoPoint",
        "type": "object"
      },
      "Guarantee": {
        "additionalProperties": false,
        "description": "Что гарантировано и кем: «вид на море с 5-го этажа: подтвердил отельер 20.10.2026».",
        "examples": [
          {
            "by": "system",
            "confirmed_at": null,
            "text": "Закат над морем со стороны ЮЗ: расчёт по рельефу и зданиям"
          }
        ],
        "properties": {
          "by": {
            "$ref": "#/components/schemas/GuaranteeBy"
          },
          "confirmed_at": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Confirmed At"
          },
          "text": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Text",
            "type": "string"
          }
        },
        "required": [
          "text",
          "by",
          "confirmed_at"
        ],
        "title": "Guarantee",
        "type": "object"
      },
      "GuaranteeBy": {
        "description": "Кто гарантирует пункт мок-брони.",
        "enum": [
          "hotelier",
          "system"
        ],
        "examples": [
          "hotelier"
        ],
        "title": "GuaranteeBy",
        "type": "string"
      },
      "Health": {
        "additionalProperties": false,
        "description": "Процесс жив. Тело закреплено, поэтому без data_mode.",
        "examples": [
          {
            "status": "ok"
          }
        ],
        "properties": {
          "status": {
            "const": "ok",
            "title": "Status",
            "type": "string"
          }
        },
        "required": [
          "status"
        ],
        "title": "Health",
        "type": "object"
      },
      "HeightSource": {
        "description": "Откуда высота здания.",
        "enum": [
          "osm_height",
          "osm_levels",
          "dsm",
          "neighbors",
          "type_default"
        ],
        "examples": [
          "osm_height"
        ],
        "title": "HeightSource",
        "type": "string"
      },
      "HotelDetail": {
        "additionalProperties": false,
        "description": "Карточка отеля G3: стороны, итоговые значения, фото.",
        "examples": [
          {
            "building": {
              "height_confidence": 0.75,
              "height_m": 28.0,
              "height_source": "osm_levels",
              "levels": 9,
              "osm_ref": "way/245601234"
            },
            "data_mode": "real",
            "facts": [
              {
                "characteristic_id": "CH-SEA-VIEW",
                "confidence": 0.86,
                "confidence_label": "high",
                "display": "Море видно с 2-го этажа, сектор 62°",
                "hotelier_confirmed": false,
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:ЮЗ:5",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "source_types": [
                  "computed"
                ],
                "state": "ok",
                "title": "Вид на море",
                "valid_until": "2027-04-18",
                "value": {
                  "from_floor": 2,
                  "sector_deg": 62,
                  "visible": true
                }
              },
              {
                "characteristic_id": "CH-SUNSET",
                "confidence": 0.84,
                "confidence_label": "high",
                "display": "Закат над морем со стороны ЮЗ, апрель - август",
                "hotelier_confirmed": true,
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:ЮЗ:5",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "source_types": [
                  "computed",
                  "hotelier"
                ],
                "state": "ok",
                "title": "Закат из окна",
                "valid_until": "2027-04-18",
                "value": {
                  "class": "sea",
                  "months": [
                    "apr",
                    "may",
                    "jun",
                    "jul",
                    "aug"
                  ]
                }
              },
              {
                "characteristic_id": "CH-QUIET",
                "confidence": 0.71,
                "confidence_label": "medium",
                "display": "Тихо ночью",
                "hotelier_confirmed": false,
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": null,
                  "key": "side:ЮЗ",
                  "kind": "side",
                  "room": null,
                  "side": "ЮЗ"
                },
                "source_types": [
                  "computed"
                ],
                "state": "ok",
                "title": "Тишина ночью",
                "valid_until": "2027-04-18",
                "value": {
                  "category": "quiet",
                  "night_index": 31
                }
              },
              {
                "characteristic_id": "CH-WALK-SEA",
                "confidence": 0.81,
                "confidence_label": "high",
                "display": "7 мин к морю, обратно 11",
                "hotelier_confirmed": false,
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": null,
                  "key": "hotel",
                  "kind": "hotel",
                  "room": null,
                  "side": null
                },
                "source_types": [
                  "computed"
                ],
                "state": "ok",
                "title": "Пешком до моря",
                "valid_until": "2027-04-18",
                "value": {
                  "climb_m": 6,
                  "minutes_back": 11,
                  "minutes_there": 7,
                  "stairs": 0
                }
              },
              {
                "characteristic_id": "CH-NIGHT",
                "confidence": 0.58,
                "confidence_label": "medium",
                "display": "Ночью рядом: аптек 1, магазинов 2",
                "hotelier_confirmed": false,
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": null,
                  "key": "hotel",
                  "kind": "hotel",
                  "room": null,
                  "side": null
                },
                "source_types": [
                  "osm_tag"
                ],
                "state": "ok",
                "title": "Ночью рядом",
                "valid_until": "2027-04-18",
                "value": {
                  "bars": 0,
                  "pharmacies": 1,
                  "shops": 2
                }
              },
              {
                "characteristic_id": "CH-CLIMATE",
                "confidence": 0.9,
                "confidence_label": "high",
                "display": "В мае воздух около 19,5°, море 16,8°",
                "hotelier_confirmed": false,
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": "center",
                  "floor": null,
                  "key": "district:center",
                  "kind": "district",
                  "room": null,
                  "side": null
                },
                "source_types": [
                  "open_api"
                ],
                "state": "ok",
                "title": "Климат и море",
                "valid_until": "2027-04-18",
                "value": {
                  "air_c": 19.5,
                  "month": "may",
                  "rain_days": 9,
                  "sea_c": 16.8
                }
              },
              {
                "characteristic_id": "CH-MOUNTAIN-VIEW",
                "confidence": 0.77,
                "confidence_label": "medium",
                "display": "Горы видны с северо-востока",
                "hotelier_confirmed": false,
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:ЮЗ:5",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "source_types": [
                  "computed"
                ],
                "state": "ok",
                "title": "Вид на горы",
                "valid_until": "2027-04-18",
                "value": {
                  "sector_deg": 40,
                  "visible": true
                }
              },
              {
                "characteristic_id": "CH-ROOM-SIDES",
                "confidence": 0.0,
                "confidence_label": "low",
                "display": "Нет данных: ждём ответа отельера",
                "hotelier_confirmed": false,
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": null,
                  "key": "room:501",
                  "kind": "room",
                  "room": "501",
                  "side": null
                },
                "source_types": [],
                "state": "unknown",
                "title": "Номера по сторонам",
                "valid_until": "2027-04-18",
                "value": {}
              }
            ],
            "hotel": {
              "address": "Курортный проспект, 18",
              "district": "center",
              "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
              "kind": "hotel",
              "location": {
                "lat": 43.579,
                "lon": 39.723
              },
              "name": "Гостиница «Бриз»",
              "photo": {
                "author": "Участник Викисклада",
                "license": "CC BY-SA 4.0",
                "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
                "url": "/media/photos/briz.jpg"
              },
              "stars": 4,
              "walk_back_min": 11,
              "walk_to_sea_min": 7
            },
            "photos": [
              {
                "author": "Участник Викисклада",
                "license": "CC BY-SA 4.0",
                "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
                "url": "/media/photos/briz.jpg"
              }
            ],
            "sides": [
              {
                "azimuth_deg": 225.0,
                "blank_facade": null,
                "floors_count": 9,
                "length_m": 18.0,
                "rumb": "ЮЗ",
                "rumb_ambiguous": false
              },
              {
                "azimuth_deg": 45.0,
                "blank_facade": null,
                "floors_count": 9,
                "length_m": 24.0,
                "rumb": "СВ",
                "rumb_ambiguous": false
              },
              {
                "azimuth_deg": 315.0,
                "blank_facade": null,
                "floors_count": 9,
                "length_m": 30.0,
                "rumb": "СЗ",
                "rumb_ambiguous": false
              },
              {
                "azimuth_deg": 135.0,
                "blank_facade": null,
                "floors_count": 9,
                "length_m": 36.0,
                "rumb": "ЮВ",
                "rumb_ambiguous": false
              }
            ],
            "website": null
          }
        ],
        "properties": {
          "building": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Building"
              },
              {
                "type": "null"
              }
            ]
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "facts": {
            "items": {
              "$ref": "#/components/schemas/Fact"
            },
            "maxItems": 200,
            "title": "Facts",
            "type": "array"
          },
          "hotel": {
            "$ref": "#/components/schemas/HotelSummary"
          },
          "photos": {
            "items": {
              "$ref": "#/components/schemas/Photo"
            },
            "maxItems": 12,
            "title": "Photos",
            "type": "array"
          },
          "sides": {
            "items": {
              "$ref": "#/components/schemas/Side"
            },
            "maxItems": 8,
            "title": "Sides",
            "type": "array"
          },
          "website": {
            "anyOf": [
              {
                "maxLength": 500,
                "minLength": 1,
                "pattern": "^(https://|/)",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Website"
          }
        },
        "required": [
          "data_mode",
          "hotel",
          "website",
          "building",
          "sides",
          "facts",
          "photos"
        ],
        "title": "HotelDetail",
        "type": "object"
      },
      "HotelKind": {
        "enum": [
          "hotel",
          "guest_house",
          "apartment",
          "hostel",
          "motel"
        ],
        "examples": [
          "hotel"
        ],
        "title": "HotelKind",
        "type": "string"
      },
      "HotelPage": {
        "additionalProperties": false,
        "description": "Страница отелей зоны.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "address": "ул. Черноморская, 4",
                "district": "center",
                "id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                "kind": "apartment",
                "location": {
                  "lat": 43.5935,
                  "lon": 39.7105
                },
                "name": "Апарт-отель «Платан»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_platan.jpg",
                  "url": "/media/photos/platan.jpg"
                },
                "stars": null,
                "walk_back_min": 6,
                "walk_to_sea_min": 4
              },
              {
                "address": "ул. Голубая, 4",
                "district": "imeretinka",
                "id": "49493573-598b-544a-b886-baa32164a3af",
                "kind": "apartment",
                "location": {
                  "lat": 43.4012,
                  "lon": 39.9625
                },
                "name": "Апартаменты «Голубая бухта»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_golubaya.jpg",
                  "url": "/media/photos/golubaya.jpg"
                },
                "stars": null,
                "walk_back_min": 8,
                "walk_to_sea_min": 8
              },
              {
                "address": "ул. Навагинская, 12",
                "district": "center",
                "id": "f597787a-31ae-5fcb-948a-f75388c093a9",
                "kind": "guest_house",
                "location": {
                  "lat": 43.5712,
                  "lon": 39.7335
                },
                "name": "Гостевой дом «Магнолия»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_magnoliya.jpg",
                  "url": "/media/photos/magnoliya.jpg"
                },
                "stars": null,
                "walk_back_min": 17,
                "walk_to_sea_min": 12
              },
              {
                "address": "ул. Ленина, 219",
                "district": "adler",
                "id": "ac62f78d-6aba-5791-a00a-ae7c92e71f8b",
                "kind": "hotel",
                "location": {
                  "lat": 43.4325,
                  "lon": 39.918
                },
                "name": "Гостиница «Адлер Марин»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_adler_marin.jpg",
                  "url": "/media/photos/adler-marin.jpg"
                },
                "stars": 3,
                "walk_back_min": 6,
                "walk_to_sea_min": 5
              },
              {
                "address": "Курортный проспект, 18",
                "district": "center",
                "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "kind": "hotel",
                "location": {
                  "lat": 43.579,
                  "lon": 39.723
                },
                "name": "Гостиница «Бриз»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
                  "url": "/media/photos/briz.jpg"
                },
                "stars": 4,
                "walk_back_min": 11,
                "walk_to_sea_min": 7
              },
              {
                "address": "ул. Платановая, 3",
                "district": "khosta",
                "id": "f8fb8162-484a-520b-a734-0a7d0315cd62",
                "kind": "hotel",
                "location": {
                  "lat": 43.515,
                  "lon": 39.865
                },
                "name": "Гостиница «Хоста Вью»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_khosta_view.jpg",
                  "url": "/media/photos/khosta-view.jpg"
                },
                "stars": 3,
                "walk_back_min": 15,
                "walk_to_sea_min": 9
              },
              {
                "address": "ул. Кирова, 30",
                "district": "adler",
                "id": "fc6971ad-f8c2-5645-8f7c-77d53ddbc0f6",
                "kind": "motel",
                "location": {
                  "lat": 43.427,
                  "lon": 39.9255
                },
                "name": "Мотель «Чайка»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_chayka.jpg",
                  "url": "/media/photos/chayka.jpg"
                },
                "stars": 2,
                "walk_back_min": 13,
                "walk_to_sea_min": 13
              },
              {
                "address": "Олимпийский проспект, 21",
                "district": "imeretinka",
                "id": "b31b83c7-4c47-5ff9-bb5e-38efed4e7d81",
                "kind": "hotel",
                "location": {
                  "lat": 43.405,
                  "lon": 39.956
                },
                "name": "Отель «Имерети Бэй»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_imereti_bay.jpg",
                  "url": "/media/photos/imereti-bay.jpg"
                },
                "stars": 5,
                "walk_back_min": 3,
                "walk_to_sea_min": 3
              },
              {
                "address": "ул. Приморская, 9",
                "district": "center",
                "id": "e30ee3ba-c3ae-5cf1-acb1-5d70c7b6f0a4",
                "kind": "hotel",
                "location": {
                  "lat": 43.5868,
                  "lon": 39.7158
                },
                "name": "Отель «Кипарис»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_kiparis.jpg",
                  "url": "/media/photos/kiparis.jpg"
                },
                "stars": 3,
                "walk_back_min": 8,
                "walk_to_sea_min": 5
              },
              {
                "address": "ул. Таврическая, 2",
                "district": "kudepsta",
                "id": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                "kind": "hotel",
                "location": {
                  "lat": 43.4985,
                  "lon": 39.892
                },
                "name": "Отель «Кудепста Плаза»",
                "photo": {
                  "author": "Участник Викисклада",
                  "license": "CC BY-SA 4.0",
                  "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                  "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_kudepsta_plaza.jpg",
                  "url": "/media/photos/kudepsta-plaza.jpg"
                },
                "stars": 4,
                "walk_back_min": 9,
                "walk_to_sea_min": 6
              }
            ],
            "page": 1,
            "page_size": 10,
            "total": 12
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/HotelSummary"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "HotelPage",
        "type": "object"
      },
      "HotelSummary": {
        "additionalProperties": false,
        "description": "Отель в списках, выдаче и карточках.",
        "examples": [
          {
            "address": "Курортный проспект, 18",
            "district": "center",
            "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "kind": "hotel",
            "location": {
              "lat": 43.579,
              "lon": 39.723
            },
            "name": "Гостиница «Бриз»",
            "photo": {
              "author": "Участник Викисклада",
              "license": "CC BY-SA 4.0",
              "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
              "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
              "url": "/media/photos/briz.jpg"
            },
            "stars": 4,
            "walk_back_min": 11,
            "walk_to_sea_min": 7
          }
        ],
        "properties": {
          "address": {
            "anyOf": [
              {
                "maxLength": 200,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Address"
          },
          "district": {
            "$ref": "#/components/schemas/District"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/HotelKind"
          },
          "location": {
            "$ref": "#/components/schemas/GeoPoint"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          },
          "photo": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Photo"
              },
              {
                "type": "null"
              }
            ]
          },
          "stars": {
            "anyOf": [
              {
                "maximum": 5.0,
                "minimum": 1.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stars"
          },
          "walk_back_min": {
            "anyOf": [
              {
                "maximum": 600.0,
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Минут обратно, с подъёмом",
            "title": "Walk Back Min"
          },
          "walk_to_sea_min": {
            "anyOf": [
              {
                "maximum": 600.0,
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "description": "Минут пешком до моря",
            "title": "Walk To Sea Min"
          }
        },
        "required": [
          "id",
          "name",
          "kind",
          "stars",
          "district",
          "address",
          "location",
          "photo",
          "walk_to_sea_min",
          "walk_back_min"
        ],
        "title": "HotelSummary",
        "type": "object"
      },
      "HotelierConfirmation": {
        "additionalProperties": false,
        "examples": [
          {
            "confirmed_at": "2026-10-20T09:00:00Z",
            "scope_key": "floor:ЮЗ:5"
          }
        ],
        "properties": {
          "confirmed_at": {
            "format": "date-time",
            "title": "Confirmed At",
            "type": "string"
          },
          "scope_key": {
            "description": "Ключ уровня значения: hotel, side:ЮЗ, floor:ЮЗ:5, room:401 или room:<uuid>, district:adler; с датами - плюс |2026-06-01..2026-06-14",
            "maxLength": 80,
            "minLength": 5,
            "pattern": "^(hotel|side:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ)|floor:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ):([1-9]|[1-3][0-9]|40)|room:([0-9A-Za-z-]{1,20}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|district:(center|matsesta|khosta|kudepsta|adler|imeretinka))(\\|[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\\.\\.[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]))?$",
            "title": "Scope Key",
            "type": "string"
          }
        },
        "required": [
          "scope_key",
          "confirmed_at"
        ],
        "title": "HotelierConfirmation",
        "type": "object"
      },
      "HotelierFact": {
        "additionalProperties": false,
        "description": "Значение в H2: то же, что видит гость, плюс ID для подтверждения и исправления.",
        "examples": [
          {
            "characteristic_id": "CH-CLIMATE",
            "confidence": 0.9,
            "confidence_label": "high",
            "display": "В мае воздух около 19,5°, море 16,8°",
            "hotelier_confirmed": false,
            "id": "fa5326d4-df7b-55eb-a153-f4846c6144b9",
            "observed_at": "2026-10-20T09:00:00Z",
            "scope": {
              "district": "center",
              "floor": null,
              "key": "district:center",
              "kind": "district",
              "room": null,
              "side": null
            },
            "source_types": [
              "open_api"
            ],
            "state": "ok",
            "title": "Климат и море",
            "valid_until": "2027-04-18",
            "value": {
              "air_c": 19.5,
              "month": "may",
              "rain_days": 9,
              "sea_c": 16.8
            }
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "confidence": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Confidence",
            "type": "number"
          },
          "confidence_label": {
            "$ref": "#/components/schemas/ConfidenceLabel"
          },
          "display": {
            "description": "Короткое значение для людей",
            "maxLength": 200,
            "minLength": 1,
            "title": "Display",
            "type": "string"
          },
          "hotelier_confirmed": {
            "title": "Hotelier Confirmed",
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "observed_at": {
            "format": "date-time",
            "title": "Observed At",
            "type": "string"
          },
          "scope": {
            "$ref": "#/components/schemas/Scope"
          },
          "source_types": {
            "items": {
              "$ref": "#/components/schemas/SourceType"
            },
            "maxItems": 7,
            "title": "Source Types",
            "type": "array"
          },
          "state": {
            "$ref": "#/components/schemas/ValueState"
          },
          "title": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Title",
            "type": "string"
          },
          "valid_until": {
            "format": "date",
            "title": "Valid Until",
            "type": "string"
          },
          "value": {
            "additionalProperties": true,
            "maxProperties": 40,
            "title": "Value",
            "type": "object"
          }
        },
        "required": [
          "characteristic_id",
          "title",
          "scope",
          "value",
          "display",
          "confidence",
          "confidence_label",
          "state",
          "source_types",
          "observed_at",
          "valid_until",
          "hotelier_confirmed",
          "id"
        ],
        "title": "HotelierFact",
        "type": "object"
      },
      "HotelierFactPage": {
        "additionalProperties": false,
        "description": "Что система знает об отеле.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "characteristic_id": "CH-CLIMATE",
                "confidence": 0.9,
                "confidence_label": "high",
                "display": "В мае воздух около 19,5°, море 16,8°",
                "hotelier_confirmed": false,
                "id": "fa5326d4-df7b-55eb-a153-f4846c6144b9",
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": "center",
                  "floor": null,
                  "key": "district:center",
                  "kind": "district",
                  "room": null,
                  "side": null
                },
                "source_types": [
                  "open_api"
                ],
                "state": "ok",
                "title": "Климат и море",
                "valid_until": "2027-04-18",
                "value": {
                  "air_c": 19.5,
                  "month": "may",
                  "rain_days": 9,
                  "sea_c": 16.8
                }
              },
              {
                "characteristic_id": "CH-MOUNTAIN-VIEW",
                "confidence": 0.77,
                "confidence_label": "medium",
                "display": "Горы видны с северо-востока",
                "hotelier_confirmed": false,
                "id": "19591291-66de-5c7d-a9ca-927da51dd35b",
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:ЮЗ:5",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "source_types": [
                  "computed"
                ],
                "state": "ok",
                "title": "Вид на горы",
                "valid_until": "2027-04-18",
                "value": {
                  "sector_deg": 40,
                  "visible": true
                }
              },
              {
                "characteristic_id": "CH-NIGHT",
                "confidence": 0.58,
                "confidence_label": "medium",
                "display": "Ночью рядом: аптек 1, магазинов 2",
                "hotelier_confirmed": false,
                "id": "55fe3fd6-d4f6-5906-bdea-96c18b0c3ac9",
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": null,
                  "key": "hotel",
                  "kind": "hotel",
                  "room": null,
                  "side": null
                },
                "source_types": [
                  "osm_tag"
                ],
                "state": "ok",
                "title": "Ночью рядом",
                "valid_until": "2027-04-18",
                "value": {
                  "bars": 0,
                  "pharmacies": 1,
                  "shops": 2
                }
              },
              {
                "characteristic_id": "CH-QUIET",
                "confidence": 0.71,
                "confidence_label": "medium",
                "display": "Тихо ночью",
                "hotelier_confirmed": false,
                "id": "e81f01dc-bb83-569b-acc1-9ef4f6b0e1ff",
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": null,
                  "key": "side:ЮЗ",
                  "kind": "side",
                  "room": null,
                  "side": "ЮЗ"
                },
                "source_types": [
                  "computed"
                ],
                "state": "ok",
                "title": "Тишина ночью",
                "valid_until": "2027-04-18",
                "value": {
                  "category": "quiet",
                  "night_index": 31
                }
              },
              {
                "characteristic_id": "CH-ROOM-SIDES",
                "confidence": 0.0,
                "confidence_label": "low",
                "display": "Нет данных: ждём ответа отельера",
                "hotelier_confirmed": false,
                "id": "905284d2-752e-5ed5-8628-1688dad573db",
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": null,
                  "key": "room:501",
                  "kind": "room",
                  "room": "501",
                  "side": null
                },
                "source_types": [],
                "state": "unknown",
                "title": "Номера по сторонам",
                "valid_until": "2027-04-18",
                "value": {}
              },
              {
                "characteristic_id": "CH-SEA-VIEW",
                "confidence": 0.86,
                "confidence_label": "high",
                "display": "Море видно с 2-го этажа, сектор 62°",
                "hotelier_confirmed": false,
                "id": "31aff00e-3ac2-5dcc-b26e-d8a4336a491a",
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:ЮЗ:5",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "source_types": [
                  "computed"
                ],
                "state": "ok",
                "title": "Вид на море",
                "valid_until": "2027-04-18",
                "value": {
                  "from_floor": 2,
                  "sector_deg": 62,
                  "visible": true
                }
              },
              {
                "characteristic_id": "CH-SUNSET",
                "confidence": 0.84,
                "confidence_label": "high",
                "display": "Закат над морем со стороны ЮЗ, апрель - август",
                "hotelier_confirmed": true,
                "id": "2478e67b-904f-56d3-91ac-136b4382bb2b",
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:ЮЗ:5",
                  "kind": "floor",
                  "room": null,
                  "side": "ЮЗ"
                },
                "source_types": [
                  "computed",
                  "hotelier"
                ],
                "state": "ok",
                "title": "Закат из окна",
                "valid_until": "2027-04-18",
                "value": {
                  "class": "sea",
                  "months": [
                    "apr",
                    "may",
                    "jun",
                    "jul",
                    "aug"
                  ]
                }
              },
              {
                "characteristic_id": "CH-WALK-SEA",
                "confidence": 0.81,
                "confidence_label": "high",
                "display": "7 мин к морю, обратно 11",
                "hotelier_confirmed": false,
                "id": "e0bb5169-4d5b-5dcc-9953-d8cbb89f1aa1",
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": null,
                  "key": "hotel",
                  "kind": "hotel",
                  "room": null,
                  "side": null
                },
                "source_types": [
                  "computed"
                ],
                "state": "ok",
                "title": "Пешком до моря",
                "valid_until": "2027-04-18",
                "value": {
                  "climb_m": 6,
                  "minutes_back": 11,
                  "minutes_there": 7,
                  "stairs": 0
                }
              }
            ],
            "page": 1,
            "page_size": 10,
            "total": 8
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/HotelierFact"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "HotelierFactPage",
        "type": "object"
      },
      "HotelierHotel": {
        "additionalProperties": false,
        "examples": [
          {
            "address": "Курортный проспект, 18",
            "district": "center",
            "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "kpi": {
              "confirmed": 1,
              "conflicts": 0,
              "impressions_7d": 252,
              "open_questions": 2,
              "pending": 6,
              "stale": 0
            },
            "name": "Гостиница «Бриз»"
          }
        ],
        "properties": {
          "address": {
            "anyOf": [
              {
                "maxLength": 200,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Address"
          },
          "district": {
            "$ref": "#/components/schemas/District"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "kpi": {
            "$ref": "#/components/schemas/HotelierKpi"
          },
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "district",
          "address",
          "kpi"
        ],
        "title": "HotelierHotel",
        "type": "object"
      },
      "HotelierHotelPage": {
        "additionalProperties": false,
        "description": "Свои отели со сводкой.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "address": "Курортный проспект, 18",
                "district": "center",
                "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "kpi": {
                  "confirmed": 1,
                  "conflicts": 0,
                  "impressions_7d": 252,
                  "open_questions": 2,
                  "pending": 6,
                  "stale": 0
                },
                "name": "Гостиница «Бриз»"
              },
              {
                "address": "Олимпийский проспект, 21",
                "district": "imeretinka",
                "id": "b31b83c7-4c47-5ff9-bb5e-38efed4e7d81",
                "kpi": {
                  "confirmed": 0,
                  "conflicts": 0,
                  "impressions_7d": 273,
                  "open_questions": 2,
                  "pending": 7,
                  "stale": 0
                },
                "name": "Отель «Имерети Бэй»"
              },
              {
                "address": "ул. Таврическая, 2",
                "district": "kudepsta",
                "id": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                "kpi": {
                  "confirmed": 0,
                  "conflicts": 0,
                  "impressions_7d": 294,
                  "open_questions": 2,
                  "pending": 7,
                  "stale": 0
                },
                "name": "Отель «Кудепста Плаза»"
              }
            ],
            "page": 1,
            "page_size": 25,
            "total": 3
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/HotelierHotel"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "HotelierHotelPage",
        "type": "object"
      },
      "HotelierKpi": {
        "additionalProperties": false,
        "description": "Сводка отеля для H1 и выбора отеля.",
        "examples": [
          {
            "confirmed": 1,
            "conflicts": 0,
            "impressions_7d": 252,
            "open_questions": 2,
            "pending": 6,
            "stale": 0
          }
        ],
        "properties": {
          "confirmed": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Confirmed",
            "type": "integer"
          },
          "conflicts": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Conflicts",
            "type": "integer"
          },
          "impressions_7d": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Impressions 7D",
            "type": "integer"
          },
          "open_questions": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Open Questions",
            "type": "integer"
          },
          "pending": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Pending",
            "type": "integer"
          },
          "stale": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Stale",
            "type": "integer"
          }
        },
        "required": [
          "confirmed",
          "pending",
          "open_questions",
          "conflicts",
          "stale",
          "impressions_7d"
        ],
        "title": "HotelierKpi",
        "type": "object"
      },
      "Importance": {
        "enum": [
          "must",
          "should"
        ],
        "examples": [
          "must"
        ],
        "title": "Importance",
        "type": "string"
      },
      "LlmUsage": {
        "additionalProperties": false,
        "examples": [
          {
            "cost_usd_24h": 1.42,
            "tokens_in_24h": 184000,
            "tokens_out_24h": 31500
          }
        ],
        "properties": {
          "cost_usd_24h": {
            "maximum": 100000.0,
            "minimum": 0.0,
            "title": "Cost Usd 24H",
            "type": "number"
          },
          "tokens_in_24h": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Tokens In 24H",
            "type": "integer"
          },
          "tokens_out_24h": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Tokens Out 24H",
            "type": "integer"
          }
        },
        "required": [
          "cost_usd_24h",
          "tokens_in_24h",
          "tokens_out_24h"
        ],
        "title": "LlmUsage",
        "type": "object"
      },
      "MarkUnknownIn": {
        "additionalProperties": false,
        "description": "Отметить значение как неизвестное.",
        "examples": [
          {
            "action": "mark_unknown"
          }
        ],
        "properties": {
          "action": {
            "const": "mark_unknown",
            "title": "Action",
            "type": "string"
          }
        },
        "required": [
          "action"
        ],
        "title": "MarkUnknownIn",
        "type": "object"
      },
      "Match": {
        "additionalProperties": false,
        "description": "Совпадение варианта с потребностью.",
        "examples": [
          {
            "confidence": 0.88,
            "need_index": 0,
            "observation_ids": [
              "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
            ],
            "reason": "Закат над морем с 3-го этажа, сторона ЮЗ",
            "status": "yes"
          }
        ],
        "properties": {
          "confidence": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Confidence",
            "type": "number"
          },
          "need_index": {
            "maximum": 11.0,
            "minimum": 0.0,
            "title": "Need Index",
            "type": "integer"
          },
          "observation_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 20,
            "title": "Observation Ids",
            "type": "array"
          },
          "reason": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Reason",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/MatchStatus"
          }
        },
        "required": [
          "need_index",
          "status",
          "reason",
          "observation_ids",
          "confidence"
        ],
        "title": "Match",
        "type": "object"
      },
      "MatchStatus": {
        "description": "Совпадение с потребностью: ✓, ~, ✗ или ?.",
        "enum": [
          "yes",
          "partial",
          "no",
          "unknown"
        ],
        "examples": [
          "yes"
        ],
        "title": "MatchStatus",
        "type": "string"
      },
      "MetricErrors": {
        "additionalProperties": false,
        "examples": [
          {
            "floor_within_one_share": 0.83,
            "sunset_minutes_mae": 3.4,
            "walk_minutes_mae": 2.1
          }
        ],
        "properties": {
          "floor_within_one_share": {
            "anyOf": [
              {
                "maximum": 1.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Floor Within One Share"
          },
          "sunset_minutes_mae": {
            "anyOf": [
              {
                "maximum": 600.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sunset Minutes Mae"
          },
          "walk_minutes_mae": {
            "anyOf": [
              {
                "maximum": 600.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Walk Minutes Mae"
          }
        },
        "required": [
          "walk_minutes_mae",
          "sunset_minutes_mae",
          "floor_within_one_share"
        ],
        "title": "MetricErrors",
        "type": "object"
      },
      "Metrics": {
        "additionalProperties": false,
        "examples": [
          {
            "accuracy": [
              {
                "characteristic_id": "CH-SEA-VIEW",
                "f1": 0.86,
                "precision": 0.88,
                "recall": 0.84,
                "support": 112
              },
              {
                "characteristic_id": "CH-SUNSET",
                "f1": 0.834,
                "precision": 0.86,
                "recall": 0.81,
                "support": 96
              },
              {
                "characteristic_id": "CH-QUIET",
                "f1": 0.719,
                "precision": 0.74,
                "recall": 0.7,
                "support": 60
              }
            ],
            "answer_parse_accuracy": 0.87,
            "conflict_share": 0.03,
            "coverage": [
              {
                "characteristic_id": "CH-SEA-VIEW",
                "coverage": 0.93,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-SUNSET",
                "coverage": 0.87,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-QUIET",
                "coverage": 0.81,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-WALK-SEA",
                "coverage": 0.75,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-NIGHT",
                "coverage": 0.69,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-CLIMATE",
                "coverage": 0.63,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-ROOM-SIDES",
                "coverage": 0.57,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-MOUNTAIN-VIEW",
                "coverage": 0.51,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-FAMILY",
                "coverage": 0.93,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-CAR",
                "coverage": 0.87,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-PETS",
                "coverage": 0.81,
                "target": 0.8
              },
              {
                "characteristic_id": "CH-SOUNDPROOF",
                "coverage": 0.75,
                "target": 0.8
              }
            ],
            "data_mode": "real",
            "errors": {
              "floor_within_one_share": 0.83,
              "sunset_minutes_mae": 3.4,
              "walk_minutes_mae": 2.1
            },
            "explanation_faithfulness": 1.0,
            "generated_at": "2026-10-23T08:00:00Z",
            "hotelier_question_share": 0.12,
            "llm": {
              "cost_usd_24h": 1.42,
              "tokens_in_24h": 184000,
              "tokens_out_24h": 31500
            },
            "ranking": {
              "ndcg_at_5": 0.82,
              "precision_at_3": 0.71
            },
            "voice_latency": {
              "p50_ms": 1150,
              "p90_ms": 1900
            }
          }
        ],
        "properties": {
          "accuracy": {
            "items": {
              "$ref": "#/components/schemas/CharacteristicAccuracy"
            },
            "maxItems": 30,
            "title": "Accuracy",
            "type": "array"
          },
          "answer_parse_accuracy": {
            "anyOf": [
              {
                "maximum": 1.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Answer Parse Accuracy"
          },
          "conflict_share": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Conflict Share",
            "type": "number"
          },
          "coverage": {
            "items": {
              "$ref": "#/components/schemas/CharacteristicCoverage"
            },
            "maxItems": 30,
            "title": "Coverage",
            "type": "array"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "errors": {
            "$ref": "#/components/schemas/MetricErrors"
          },
          "explanation_faithfulness": {
            "anyOf": [
              {
                "maximum": 1.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Explanation Faithfulness"
          },
          "generated_at": {
            "format": "date-time",
            "title": "Generated At",
            "type": "string"
          },
          "hotelier_question_share": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Hotelier Question Share",
            "type": "number"
          },
          "llm": {
            "$ref": "#/components/schemas/LlmUsage"
          },
          "ranking": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RankingMetrics"
              },
              {
                "type": "null"
              }
            ]
          },
          "voice_latency": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/VoiceLatency"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "data_mode",
          "generated_at",
          "accuracy",
          "errors",
          "coverage",
          "hotelier_question_share",
          "conflict_share",
          "answer_parse_accuracy",
          "ranking",
          "explanation_faithfulness",
          "llm",
          "voice_latency"
        ],
        "title": "Metrics",
        "type": "object"
      },
      "NeedIn": {
        "additionalProperties": false,
        "description": "Потребность во входе: правка чипа или чип с главного экрана.",
        "examples": [
          {
            "characteristic_id": "CH-SUNSET",
            "condition": {
              "op": "sea_on_dates",
              "value": null
            },
            "importance": "must",
            "source": "chip"
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "condition": {
            "$ref": "#/components/schemas/Condition"
          },
          "importance": {
            "$ref": "#/components/schemas/Importance"
          },
          "source": {
            "$ref": "#/components/schemas/NeedSource"
          }
        },
        "required": [
          "characteristic_id",
          "condition",
          "importance",
          "source"
        ],
        "title": "NeedIn",
        "type": "object"
      },
      "NeedOut": {
        "additionalProperties": false,
        "description": "Потребность после разбора запроса: чип в строке поиска.",
        "examples": [
          {
            "characteristic_id": "CH-SUNSET",
            "condition": {
              "op": "sea_on_dates",
              "value": null
            },
            "id": "n1",
            "importance": "must",
            "label": "Закат из окна",
            "source": "query"
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "condition": {
            "$ref": "#/components/schemas/Condition"
          },
          "id": {
            "maxLength": 3,
            "minLength": 2,
            "pattern": "^n[0-9]{1,2}$",
            "title": "Id",
            "type": "string"
          },
          "importance": {
            "$ref": "#/components/schemas/Importance"
          },
          "label": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Label",
            "type": "string"
          },
          "source": {
            "$ref": "#/components/schemas/NeedSource"
          }
        },
        "required": [
          "id",
          "characteristic_id",
          "label",
          "condition",
          "importance",
          "source"
        ],
        "title": "NeedOut",
        "type": "object"
      },
      "NeedSource": {
        "enum": [
          "query",
          "memory",
          "chip"
        ],
        "examples": [
          "query"
        ],
        "title": "NeedSource",
        "type": "string"
      },
      "NotUsedSource": {
        "additionalProperties": false,
        "description": "«Почему мы не используем».",
        "examples": [
          {
            "name": "Mapillary",
            "reason": "Нужен ключ, компания-владелец признана в РФ экстремистской"
          }
        ],
        "properties": {
          "name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          },
          "reason": {
            "maxLength": 300,
            "minLength": 1,
            "title": "Reason",
            "type": "string"
          }
        },
        "required": [
          "name",
          "reason"
        ],
        "title": "NotUsedSource",
        "type": "object"
      },
      "Observation": {
        "additionalProperties": false,
        "description": "Одно значение из одного источника (Observation).",
        "examples": [
          {
            "confidence": 0.81,
            "confidence_breakdown": {
              "freshness": 1.0,
              "quality": 0.95,
              "trust": 0.85
            },
            "confidence_label": "high",
            "display": "Море видно с 3-го этажа, сектор 61°",
            "evidence": [
              {
                "kind": "raster",
                "label": "Профиль горизонта: Copernicus GLO-30 и здания OSM",
                "ref": "SRC-01@2026-10-22",
                "url": null
              }
            ],
            "id": "c2283979-9a71-5935-8a3a-7d9fda8a89d9",
            "method": {
              "inputs_hash": "6367f30a0dbd3ef8eb8147c2e255a6e67093eb9e30c7f99f4f0a8749760b5144",
              "params_hash": "c04088b7e397681464b423ef3fb605c44bb561ba605010169fcf9838ef8163e4",
              "tool": "geo.sea_visibility",
              "version": "1.0.0"
            },
            "observed_at": "2026-10-19T09:00:00Z",
            "scope": {
              "district": null,
              "floor": 5,
              "key": "floor:З:5",
              "kind": "floor",
              "room": null,
              "side": "З"
            },
            "source_date": "2026-10-22",
            "source_ref": "SRC-01@2026-10-22",
            "source_type": "computed",
            "status": "active",
            "used": true,
            "valid_until": "2027-04-20",
            "value": {
              "from_floor": 3,
              "sector_deg": 61,
              "visible": true
            }
          }
        ],
        "properties": {
          "confidence": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Confidence",
            "type": "number"
          },
          "confidence_breakdown": {
            "$ref": "#/components/schemas/ConfidenceBreakdown"
          },
          "confidence_label": {
            "$ref": "#/components/schemas/ConfidenceLabel"
          },
          "display": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Display",
            "type": "string"
          },
          "evidence": {
            "items": {
              "$ref": "#/components/schemas/Evidence"
            },
            "maxItems": 10,
            "title": "Evidence",
            "type": "array"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "method": {
            "$ref": "#/components/schemas/ObservationMethod"
          },
          "observed_at": {
            "format": "date-time",
            "title": "Observed At",
            "type": "string"
          },
          "scope": {
            "$ref": "#/components/schemas/Scope"
          },
          "source_date": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Source Date"
          },
          "source_ref": {
            "description": "SRC-NN@срез, URL, node/123@v7 или ID ответа",
            "maxLength": 300,
            "minLength": 1,
            "title": "Source Ref",
            "type": "string"
          },
          "source_type": {
            "$ref": "#/components/schemas/SourceType"
          },
          "status": {
            "$ref": "#/components/schemas/ObservationStatus"
          },
          "used": {
            "description": "Наблюдение выбрано для итогового значения",
            "title": "Used",
            "type": "boolean"
          },
          "valid_until": {
            "format": "date",
            "title": "Valid Until",
            "type": "string"
          },
          "value": {
            "additionalProperties": true,
            "maxProperties": 20,
            "title": "Value",
            "type": "object"
          }
        },
        "required": [
          "id",
          "scope",
          "value",
          "display",
          "source_type",
          "source_ref",
          "method",
          "confidence",
          "confidence_label",
          "confidence_breakdown",
          "observed_at",
          "source_date",
          "valid_until",
          "evidence",
          "status",
          "used"
        ],
        "title": "Observation",
        "type": "object"
      },
      "ObservationMethod": {
        "additionalProperties": false,
        "description": "Метод наблюдения: инструмент, версия и хэши.",
        "examples": [
          {
            "inputs_hash": "6367f30a0dbd3ef8eb8147c2e255a6e67093eb9e30c7f99f4f0a8749760b5144",
            "params_hash": "c04088b7e397681464b423ef3fb605c44bb561ba605010169fcf9838ef8163e4",
            "tool": "geo.sea_visibility",
            "version": "1.0.0"
          }
        ],
        "properties": {
          "inputs_hash": {
            "maxLength": 64,
            "minLength": 64,
            "pattern": "^[0-9a-f]{64}$",
            "title": "Inputs Hash",
            "type": "string"
          },
          "params_hash": {
            "maxLength": 64,
            "minLength": 64,
            "pattern": "^[0-9a-f]{64}$",
            "title": "Params Hash",
            "type": "string"
          },
          "tool": {
            "maxLength": 60,
            "minLength": 3,
            "title": "Tool",
            "type": "string"
          },
          "version": {
            "maxLength": 20,
            "minLength": 5,
            "title": "Version",
            "type": "string"
          }
        },
        "required": [
          "tool",
          "version",
          "params_hash",
          "inputs_hash"
        ],
        "title": "ObservationMethod",
        "type": "object"
      },
      "ObservationStatus": {
        "enum": [
          "active",
          "superseded",
          "rejected"
        ],
        "examples": [
          "active"
        ],
        "title": "ObservationStatus",
        "type": "string"
      },
      "Obstacle": {
        "additionalProperties": false,
        "description": "Что закрывает горизонт в секторе азимутов.",
        "examples": [
          {
            "azimuth_from": 285.0,
            "azimuth_to": 300.0,
            "building_id": "way/245601999",
            "distance_m": 85.0,
            "height_m": 12.0,
            "kind": "building"
          }
        ],
        "properties": {
          "azimuth_from": {
            "description": "Азимут от севера по часовой, градусы",
            "maximum": 360.0,
            "minimum": 0.0,
            "title": "Azimuth From",
            "type": "number"
          },
          "azimuth_to": {
            "maximum": 720.0,
            "minimum": 0.0,
            "title": "Azimuth To",
            "type": "number"
          },
          "building_id": {
            "anyOf": [
              {
                "maxLength": 40,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Здание OSM, например way/245601234",
            "title": "Building Id"
          },
          "distance_m": {
            "maximum": 60000.0,
            "minimum": 0.0,
            "title": "Distance M",
            "type": "number"
          },
          "height_m": {
            "anyOf": [
              {
                "maximum": 5000.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Height M"
          },
          "kind": {
            "$ref": "#/components/schemas/ObstacleKind"
          }
        },
        "required": [
          "azimuth_from",
          "azimuth_to",
          "kind",
          "distance_m",
          "height_m",
          "building_id"
        ],
        "title": "Obstacle",
        "type": "object"
      },
      "ObstacleKind": {
        "enum": [
          "building",
          "terrain"
        ],
        "examples": [
          "building"
        ],
        "title": "ObstacleKind",
        "type": "string"
      },
      "Overview": {
        "additionalProperties": false,
        "examples": [
          {
            "attention": [
              {
                "action": "Разобрать",
                "kind": "conflict",
                "object_id": "531809e2-a4c0-503c-9c1e-c6898ed7c409",
                "object_type": "conflict",
                "title": "«Кипарис»: отельер и расчёт расходятся по виду на море"
              },
              {
                "action": "Проверить",
                "kind": "source_failure",
                "object_id": "SRC-07",
                "object_type": "source",
                "title": "Open-Meteo Marine отвечает с ошибками"
              },
              {
                "action": "Перепроверить устаревшие",
                "kind": "stale",
                "object_id": null,
                "object_type": "run",
                "title": "12 значений устарели"
              }
            ],
            "data_mode": "real",
            "events": [
              {
                "kind": "sandbox",
                "text": "Рядом с «Бризом» построили дом: вид с ЮЗ пересчитан",
                "ts": "2026-10-23T08:48:00Z"
              },
              {
                "kind": "hotelier",
                "text": "Отельер «Адлер Марин» подтвердил 3 значения",
                "ts": "2026-10-23T08:20:00Z"
              },
              {
                "kind": "characteristic",
                "text": "Новая характеристика CH-BAKERY включена",
                "ts": "2026-10-23T07:00:00Z"
              }
            ],
            "kpi": {
              "accuracy_f1": 0.86,
              "avg_confidence": 0.74,
              "coverage": 0.82,
              "hotels_total": 805,
              "llm_cost_usd_24h": 1.42,
              "open_conflicts": 7,
              "stale_values": 12
            },
            "period": "24h",
            "queue": {
              "failed": 1,
              "queued": 14,
              "running": 2,
              "workers_alive": 1
            },
            "running": [
              {
                "agent": "AG-ORCH",
                "cost_usd": 0.0,
                "created_by": "Админ Островка (демо)",
                "error": null,
                "finished_at": null,
                "id": "11176bcc-8d14-58b7-9c4f-6a655b8b9578",
                "kind": "collect",
                "progress": 0.64,
                "scope": "CH-SUNSET × 30 витринных отелей",
                "started_at": "2026-10-23T08:00:00Z",
                "state": "running",
                "tokens_in": 0,
                "tokens_out": 0
              },
              {
                "agent": "AG-ORCH",
                "cost_usd": 0.0,
                "created_by": "Админ Островка (демо)",
                "error": null,
                "finished_at": null,
                "id": "d799c4ea-2aec-5051-a411-0ef7da9dc0a1",
                "kind": "collect",
                "progress": 0.64,
                "scope": "CH-SUNSET × 30 витринных отелей",
                "started_at": "2026-10-23T02:00:00Z",
                "state": "running",
                "tokens_in": 0,
                "tokens_out": 0
              },
              {
                "agent": "AG-ORCH",
                "cost_usd": 0.0,
                "created_by": "Админ Островка (демо)",
                "error": null,
                "finished_at": null,
                "id": "c334590f-ba79-577f-b51f-f0b8f495f36f",
                "kind": "collect",
                "progress": 0.64,
                "scope": "CH-SUNSET × 30 витринных отелей",
                "started_at": "2026-10-22T20:00:00Z",
                "state": "running",
                "tokens_in": 0,
                "tokens_out": 0
              }
            ]
          }
        ],
        "properties": {
          "attention": {
            "items": {
              "$ref": "#/components/schemas/AttentionItem"
            },
            "maxItems": 20,
            "title": "Attention",
            "type": "array"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "events": {
            "items": {
              "$ref": "#/components/schemas/RecentEvent"
            },
            "maxItems": 20,
            "title": "Events",
            "type": "array"
          },
          "kpi": {
            "$ref": "#/components/schemas/OverviewKpi"
          },
          "period": {
            "$ref": "#/components/schemas/OverviewPeriod"
          },
          "queue": {
            "$ref": "#/components/schemas/QueueState"
          },
          "running": {
            "items": {
              "$ref": "#/components/schemas/Run"
            },
            "maxItems": 10,
            "title": "Running",
            "type": "array"
          }
        },
        "required": [
          "data_mode",
          "period",
          "kpi",
          "attention",
          "running",
          "queue",
          "events"
        ],
        "title": "Overview",
        "type": "object"
      },
      "OverviewKpi": {
        "additionalProperties": false,
        "examples": [
          {
            "accuracy_f1": 0.86,
            "avg_confidence": 0.74,
            "coverage": 0.82,
            "hotels_total": 805,
            "llm_cost_usd_24h": 1.42,
            "open_conflicts": 7,
            "stale_values": 12
          }
        ],
        "properties": {
          "accuracy_f1": {
            "anyOf": [
              {
                "maximum": 1.0,
                "minimum": 0.0,
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "description": "Точность на эталоне, если посчитана",
            "title": "Accuracy F1"
          },
          "avg_confidence": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Avg Confidence",
            "type": "number"
          },
          "coverage": {
            "description": "Доля пар «отель × характеристика» с уверенностью от 0,5",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Coverage",
            "type": "number"
          },
          "hotels_total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Hotels Total",
            "type": "integer"
          },
          "llm_cost_usd_24h": {
            "maximum": 100000.0,
            "minimum": 0.0,
            "title": "Llm Cost Usd 24H",
            "type": "number"
          },
          "open_conflicts": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Open Conflicts",
            "type": "integer"
          },
          "stale_values": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Stale Values",
            "type": "integer"
          }
        },
        "required": [
          "hotels_total",
          "coverage",
          "open_conflicts",
          "stale_values",
          "avg_confidence",
          "accuracy_f1",
          "llm_cost_usd_24h"
        ],
        "title": "OverviewKpi",
        "type": "object"
      },
      "OverviewPeriod": {
        "description": "Сегменты пульта: сейчас, 24 часа, 7 дней.",
        "enum": [
          "now",
          "24h",
          "7d"
        ],
        "examples": [
          "now"
        ],
        "title": "OverviewPeriod",
        "type": "string"
      },
      "ParserMode": {
        "description": "Чем разобран запрос: LLM или правилами без ключа.",
        "enum": [
          "llm",
          "rules"
        ],
        "examples": [
          "llm"
        ],
        "title": "ParserMode",
        "type": "string"
      },
      "Photo": {
        "additionalProperties": false,
        "description": "Фото с автором и лицензией.",
        "examples": [
          {
            "author": "Участник Викисклада",
            "license": "CC BY-SA 4.0",
            "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
            "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
            "url": "/media/photos/briz.jpg"
          }
        ],
        "properties": {
          "author": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Author",
            "type": "string"
          },
          "license": {
            "maxLength": 80,
            "minLength": 1,
            "title": "License",
            "type": "string"
          },
          "license_url": {
            "anyOf": [
              {
                "maxLength": 500,
                "minLength": 1,
                "pattern": "^(https://|/)",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "License Url"
          },
          "page_url": {
            "maxLength": 500,
            "minLength": 1,
            "pattern": "^(https://|/)",
            "title": "Page Url",
            "type": "string"
          },
          "url": {
            "maxLength": 500,
            "minLength": 1,
            "pattern": "^(https://|/)",
            "title": "Url",
            "type": "string"
          }
        },
        "required": [
          "url",
          "page_url",
          "author",
          "license",
          "license_url"
        ],
        "title": "Photo",
        "type": "object"
      },
      "Preference": {
        "additionalProperties": false,
        "examples": [
          {
            "characteristic_id": "CH-QUIET",
            "condition": {
              "op": "night_quiet",
              "value": null
            },
            "expires_at": "2027-04-21T09:00:00Z",
            "importance": "must",
            "key": "quiet_night",
            "label": "Тишина ночью",
            "source": "explicit",
            "updated_at": "2026-10-23T09:00:00Z",
            "weight": 1.0
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "condition": {
            "$ref": "#/components/schemas/Condition"
          },
          "expires_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Expires At"
          },
          "importance": {
            "$ref": "#/components/schemas/Importance"
          },
          "key": {
            "maxLength": 41,
            "minLength": 3,
            "pattern": "^[a-z][a-z0-9_]{2,40}$",
            "title": "Key",
            "type": "string"
          },
          "label": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Label",
            "type": "string"
          },
          "source": {
            "$ref": "#/components/schemas/PreferenceSource"
          },
          "updated_at": {
            "format": "date-time",
            "title": "Updated At",
            "type": "string"
          },
          "weight": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Weight",
            "type": "number"
          }
        },
        "required": [
          "key",
          "label",
          "characteristic_id",
          "condition",
          "importance",
          "source",
          "weight",
          "updated_at",
          "expires_at"
        ],
        "title": "Preference",
        "type": "object"
      },
      "PreferenceIn": {
        "additionalProperties": false,
        "description": "Предпочтение без свободного текста: характеристика, условие, важность.",
        "examples": [
          {
            "characteristic_id": "CH-QUIET",
            "condition": {
              "op": "night_quiet",
              "value": null
            },
            "importance": "must"
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "condition": {
            "$ref": "#/components/schemas/Condition"
          },
          "importance": {
            "$ref": "#/components/schemas/Importance"
          }
        },
        "required": [
          "characteristic_id",
          "condition",
          "importance"
        ],
        "title": "PreferenceIn",
        "type": "object"
      },
      "PreferencePage": {
        "additionalProperties": false,
        "description": "Что система помнит о госте.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "characteristic_id": "CH-QUIET",
                "condition": {
                  "op": "night_quiet",
                  "value": null
                },
                "expires_at": "2027-04-21T09:00:00Z",
                "importance": "must",
                "key": "quiet_night",
                "label": "Тишина ночью",
                "source": "explicit",
                "updated_at": "2026-10-23T09:00:00Z",
                "weight": 1.0
              },
              {
                "characteristic_id": "CH-SUNSET",
                "condition": {
                  "op": "sea_on_dates",
                  "value": null
                },
                "expires_at": "2027-04-20T09:00:00Z",
                "importance": "should",
                "key": "sunset_view",
                "label": "Закат из окна",
                "source": "learned",
                "updated_at": "2026-10-22T09:00:00Z",
                "weight": 0.6
              },
              {
                "characteristic_id": "CH-WALK-SEA",
                "condition": {
                  "op": "max_minutes",
                  "value": 10
                },
                "expires_at": "2027-04-19T09:00:00Z",
                "importance": "should",
                "key": "walk_flat",
                "label": "Пешком до моря",
                "source": "explicit",
                "updated_at": "2026-10-21T09:00:00Z",
                "weight": 1.0
              }
            ],
            "page": 1,
            "page_size": 25,
            "total": 3
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/Preference"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "PreferencePage",
        "type": "object"
      },
      "PreferenceSaved": {
        "additionalProperties": false,
        "examples": [
          {
            "data_mode": "real",
            "preference": {
              "characteristic_id": "CH-QUIET",
              "condition": {
                "op": "night_quiet",
                "value": null
              },
              "expires_at": "2027-04-21T09:00:00Z",
              "importance": "must",
              "key": "quiet_night_quiet",
              "label": "Тишина ночью",
              "source": "explicit",
              "updated_at": "2026-10-23T09:00:00Z",
              "weight": 1.0
            }
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "preference": {
            "$ref": "#/components/schemas/Preference"
          }
        },
        "required": [
          "data_mode",
          "preference"
        ],
        "title": "PreferenceSaved",
        "type": "object"
      },
      "PreferenceSource": {
        "enum": [
          "explicit",
          "learned"
        ],
        "examples": [
          "explicit"
        ],
        "title": "PreferenceSource",
        "type": "string"
      },
      "Priority": {
        "description": "Приоритет характеристики, MoSCoW.",
        "enum": [
          "must",
          "should",
          "could",
          "wont"
        ],
        "examples": [
          "must"
        ],
        "title": "Priority",
        "type": "string"
      },
      "Problem": {
        "additionalProperties": false,
        "description": "Ошибка по RFC 9457, тип application/problem+json. Обработчики делает.",
        "examples": [
          {
            "detail": "Этаж - целое число от 1 до 40",
            "errors": [
              {
                "loc": [
                  "query",
                  "floor"
                ],
                "msg": "Ожидается целое число",
                "type": "int_parsing"
              }
            ],
            "instance": "urn:okno:request:7f3c9e2a",
            "status": 422,
            "title": "Ошибка в данных запроса",
            "type": "about:blank"
          }
        ],
        "properties": {
          "detail": {
            "anyOf": [
              {
                "maxLength": 2000,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Detail"
          },
          "errors": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/ProblemFieldError"
                },
                "maxItems": 50,
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Errors"
          },
          "instance": {
            "anyOf": [
              {
                "maxLength": 300,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "ID запроса для поддержки",
            "title": "Instance"
          },
          "status": {
            "maximum": 599,
            "minimum": 400,
            "title": "Status",
            "type": "integer"
          },
          "title": {
            "maxLength": 200,
            "title": "Title",
            "type": "string"
          },
          "type": {
            "description": "URI типа ошибки, по умолчанию about:blank",
            "maxLength": 300,
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "instance",
          "errors"
        ],
        "title": "Problem",
        "type": "object"
      },
      "ProblemFieldError": {
        "additionalProperties": false,
        "description": "Ошибка в поле запроса.",
        "examples": [
          {
            "loc": [
              "query",
              "floor"
            ],
            "msg": "Ожидается целое число",
            "type": "int_parsing"
          }
        ],
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "maxItems": 10,
            "title": "Loc",
            "type": "array"
          },
          "msg": {
            "maxLength": 500,
            "title": "Msg",
            "type": "string"
          },
          "type": {
            "maxLength": 100,
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ProblemFieldError",
        "type": "object"
      },
      "Provenance": {
        "additionalProperties": false,
        "description": "«Как мы это узнали»: все наблюдения, конфликты и подтверждение отельера.",
        "examples": [
          {
            "characteristic_id": "CH-SEA-VIEW",
            "conflicts": [
              {
                "id": "531809e2-a4c0-503c-9c1e-c6898ed7c409",
                "observation_ids": [
                  "c2283979-9a71-5935-8a3a-7d9fda8a89d9",
                  "79aa5334-56ee-5547-8b8e-60540ace5bb5"
                ],
                "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 км. Море засчитано, где видимая полоса воды выше 0,1°.",
            "observations": [
              {
                "confidence": 0.81,
                "confidence_breakdown": {
                  "freshness": 1.0,
                  "quality": 0.95,
                  "trust": 0.85
                },
                "confidence_label": "high",
                "display": "Море видно с 3-го этажа, сектор 61°",
                "evidence": [
                  {
                    "kind": "raster",
                    "label": "Профиль горизонта: Copernicus GLO-30 и здания OSM",
                    "ref": "SRC-01@2026-10-22",
                    "url": null
                  }
                ],
                "id": "c2283979-9a71-5935-8a3a-7d9fda8a89d9",
                "method": {
                  "inputs_hash": "6367f30a0dbd3ef8eb8147c2e255a6e67093eb9e30c7f99f4f0a8749760b5144",
                  "params_hash": "c04088b7e397681464b423ef3fb605c44bb561ba605010169fcf9838ef8163e4",
                  "tool": "geo.sea_visibility",
                  "version": "1.0.0"
                },
                "observed_at": "2026-10-19T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:З:5",
                  "kind": "floor",
                  "room": null,
                  "side": "З"
                },
                "source_date": "2026-10-22",
                "source_ref": "SRC-01@2026-10-22",
                "source_type": "computed",
                "status": "active",
                "used": true,
                "valid_until": "2027-04-20",
                "value": {
                  "from_floor": 3,
                  "sector_deg": 61,
                  "visible": true
                }
              },
              {
                "confidence": 0.63,
                "confidence_breakdown": {
                  "freshness": 1.0,
                  "quality": 0.7,
                  "trust": 0.9
                },
                "confidence_label": "medium",
                "display": "Отельер: море видно со 2-го этажа",
                "evidence": [
                  {
                    "kind": "answer",
                    "label": "Ответ отельера в кабинете",
                    "ref": "answer:4c84aafd-b957-52f6-97a9-4fa8a3c1618f",
                    "url": null
                  }
                ],
                "id": "79aa5334-56ee-5547-8b8e-60540ace5bb5",
                "method": {
                  "inputs_hash": "99185a04e8b8c288c9c1c4612a360a5e6404a36d39828479d6acd43607090332",
                  "params_hash": "98680d26607b926fd9308f202ea726ea535e090e8fa61980b44628db25094dbd",
                  "tool": "hotelier.ask",
                  "version": "1.0.0"
                },
                "observed_at": "2026-10-18T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:З:5",
                  "kind": "floor",
                  "room": null,
                  "side": "З"
                },
                "source_date": "2026-10-22",
                "source_ref": "answer:4c84aafd-b957-52f6-97a9-4fa8a3c1618f",
                "source_type": "hotelier",
                "status": "active",
                "used": false,
                "valid_until": "2027-04-20",
                "value": {
                  "from_floor": 3,
                  "sector_deg": 61,
                  "visible": true
                }
              }
            ],
            "question": "Видно ли море из окна и с какого этажа?",
            "title": "Вид на море",
            "truth_note": "Выбрано значение расчёта: у него выше уверенность, срок годности не истёк.",
            "values": [
              {
                "characteristic_id": "CH-SEA-VIEW",
                "confidence": 0.62,
                "confidence_label": "medium",
                "display": "Море видно с 3-го этажа, сектор 61°",
                "hotelier_confirmed": false,
                "observed_at": "2026-10-20T09:00:00Z",
                "scope": {
                  "district": null,
                  "floor": 5,
                  "key": "floor:З:5",
                  "kind": "floor",
                  "room": null,
                  "side": "З"
                },
                "source_types": [
                  "computed",
                  "hotelier"
                ],
                "state": "conflict",
                "title": "Вид на море",
                "valid_until": "2027-04-18",
                "value": {
                  "from_floor": 3,
                  "sector_deg": 61,
                  "visible": true
                }
              }
            ]
          }
        ],
        "properties": {
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "conflicts": {
            "items": {
              "$ref": "#/components/schemas/ConflictInfo"
            },
            "maxItems": 10,
            "title": "Conflicts",
            "type": "array"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "hotelier_confirmations": {
            "items": {
              "$ref": "#/components/schemas/HotelierConfirmation"
            },
            "maxItems": 64,
            "title": "Hotelier Confirmations",
            "type": "array"
          },
          "method_note": {
            "description": "Как получено значение, словами AG-EXPL или шаблона",
            "maxLength": 1000,
            "minLength": 1,
            "title": "Method Note",
            "type": "string"
          },
          "observations": {
            "items": {
              "$ref": "#/components/schemas/Observation"
            },
            "maxItems": 100,
            "title": "Observations",
            "type": "array"
          },
          "question": {
            "maxLength": 160,
            "minLength": 1,
            "title": "Question",
            "type": "string"
          },
          "title": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Title",
            "type": "string"
          },
          "truth_note": {
            "description": "Почему выбрано это значение (AG-TRUTH)",
            "maxLength": 1000,
            "minLength": 1,
            "title": "Truth Note",
            "type": "string"
          },
          "values": {
            "items": {
              "$ref": "#/components/schemas/Fact"
            },
            "maxItems": 64,
            "title": "Values",
            "type": "array"
          }
        },
        "required": [
          "data_mode",
          "hotel_id",
          "characteristic_id",
          "title",
          "question",
          "values",
          "observations",
          "conflicts",
          "method_note",
          "truth_note",
          "hotelier_confirmations"
        ],
        "title": "Provenance",
        "type": "object"
      },
      "Question": {
        "additionalProperties": false,
        "description": "Вопрос агента в H3 (QuestionOut).",
        "examples": [
          {
            "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": "Какие номера и этажи выходят на юго-западную сторону, к морю?"
          }
        ],
        "properties": {
          "characteristic_ids": {
            "items": {
              "description": "ID характеристики, CH-*",
              "maxLength": 43,
              "minLength": 5,
              "pattern": "^CH-[A-Z0-9-]{2,40}$",
              "type": "string"
            },
            "maxItems": 3,
            "minItems": 1,
            "title": "Characteristic Ids",
            "type": "array"
          },
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "evidence": {
            "items": {
              "$ref": "#/components/schemas/Evidence"
            },
            "maxItems": 5,
            "title": "Evidence",
            "type": "array"
          },
          "fields_schema": {
            "additionalProperties": true,
            "description": "Схема полей ответа из спецификации",
            "maxProperties": 20,
            "title": "Fields Schema",
            "type": "object"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "hotel_name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Hotel Name",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "priority": {
            "description": "Спрос × (1 − уверенность) × влияние",
            "maximum": 100.0,
            "minimum": 0.0,
            "title": "Priority",
            "type": "number"
          },
          "reason": {
            "$ref": "#/components/schemas/QuestionReason"
          },
          "state": {
            "$ref": "#/components/schemas/QuestionState"
          },
          "text": {
            "maxLength": 1000,
            "minLength": 1,
            "title": "Text",
            "type": "string"
          }
        },
        "required": [
          "id",
          "hotel_id",
          "hotel_name",
          "text",
          "reason",
          "characteristic_ids",
          "fields_schema",
          "priority",
          "state",
          "evidence",
          "created_at"
        ],
        "title": "Question",
        "type": "object"
      },
      "QuestionPage": {
        "additionalProperties": false,
        "description": "Лента вопросов агента.",
        "examples": [
          {
            "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": "Какие номера и этажи выходят на юго-западную сторону, к морю?"
              },
              {
                "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": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                "hotel_name": "Отель «Кудепста Плаза»",
                "id": "0495f6a8-16f4-5191-b8e5-09bfaf86dc1a",
                "priority": 6.4,
                "reason": "missing",
                "state": "open",
                "text": "Какие номера и этажи выходят на юго-западную сторону, к морю?"
              },
              {
                "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": "b31b83c7-4c47-5ff9-bb5e-38efed4e7d81",
                "hotel_name": "Отель «Имерети Бэй»",
                "id": "f67018b0-f4f6-51ba-af9d-4465217b748a",
                "priority": 6.4,
                "reason": "missing",
                "state": "open",
                "text": "Какие номера и этажи выходят на юго-западную сторону, к морю?"
              },
              {
                "characteristic_ids": [
                  "CH-SEA-VIEW"
                ],
                "created_at": "2026-10-22T07:00:00Z",
                "evidence": [
                  {
                    "kind": "panorama",
                    "label": "Панорама горизонта, сторона ЮЗ, 3-й этаж",
                    "ref": "SRC-01@2026-10-22",
                    "url": null
                  }
                ],
                "fields_schema": {
                  "properties": {
                    "from_floor": {
                      "type": "integer"
                    },
                    "rooms": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "hotel_name": "Гостиница «Бриз»",
                "id": "6487b0f2-8694-5fb9-90e7-1345fcad87c6",
                "priority": 5.1,
                "reason": "conflict",
                "state": "open",
                "text": "По нашей схеме с 3-го этажа юго-западной стороны море закрывает дом на ул. Приморской (около 27 м по OSM). Из каких номеров и с какого этажа море видно?"
              },
              {
                "characteristic_ids": [
                  "CH-SEA-VIEW"
                ],
                "created_at": "2026-10-22T07:00:00Z",
                "evidence": [
                  {
                    "kind": "panorama",
                    "label": "Панорама горизонта, сторона ЮЗ, 3-й этаж",
                    "ref": "SRC-01@2026-10-22",
                    "url": null
                  }
                ],
                "fields_schema": {
                  "properties": {
                    "from_floor": {
                      "type": "integer"
                    },
                    "rooms": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "hotel_id": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                "hotel_name": "Отель «Кудепста Плаза»",
                "id": "1d840f59-8dd6-5c28-b085-275c6e9c3d77",
                "priority": 5.1,
                "reason": "conflict",
                "state": "open",
                "text": "По нашей схеме с 3-го этажа юго-западной стороны море закрывает дом на ул. Приморской (около 27 м по OSM). Из каких номеров и с какого этажа море видно?"
              },
              {
                "characteristic_ids": [
                  "CH-SEA-VIEW"
                ],
                "created_at": "2026-10-22T07:00:00Z",
                "evidence": [
                  {
                    "kind": "panorama",
                    "label": "Панорама горизонта, сторона ЮЗ, 3-й этаж",
                    "ref": "SRC-01@2026-10-22",
                    "url": null
                  }
                ],
                "fields_schema": {
                  "properties": {
                    "from_floor": {
                      "type": "integer"
                    },
                    "rooms": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "hotel_id": "b31b83c7-4c47-5ff9-bb5e-38efed4e7d81",
                "hotel_name": "Отель «Имерети Бэй»",
                "id": "25419fe6-f267-5a4b-86ff-cb893c9f85e8",
                "priority": 5.1,
                "reason": "conflict",
                "state": "open",
                "text": "По нашей схеме с 3-го этажа юго-западной стороны море закрывает дом на ул. Приморской (около 27 м по OSM). Из каких номеров и с какого этажа море видно?"
              },
              {
                "characteristic_ids": [
                  "CH-PETS"
                ],
                "created_at": "2026-10-21T07:00:00Z",
                "evidence": [],
                "fields_schema": {
                  "properties": {
                    "allowed": {
                      "type": "boolean"
                    },
                    "conditions": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "hotel_name": "Гостиница «Бриз»",
                "id": "c1e32eee-a4be-586d-b005-fe47e494a111",
                "priority": 3.8,
                "reason": "demand",
                "state": "open",
                "text": "Гости рядом ищут отели с питомцами. Пускаете ли вы с животными и на каких условиях?"
              },
              {
                "characteristic_ids": [
                  "CH-PETS"
                ],
                "created_at": "2026-10-21T07:00:00Z",
                "evidence": [],
                "fields_schema": {
                  "properties": {
                    "allowed": {
                      "type": "boolean"
                    },
                    "conditions": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "hotel_id": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                "hotel_name": "Отель «Кудепста Плаза»",
                "id": "7c004334-884c-5db8-a871-7dacbb02dc27",
                "priority": 3.8,
                "reason": "demand",
                "state": "open",
                "text": "Гости рядом ищут отели с питомцами. Пускаете ли вы с животными и на каких условиях?"
              },
              {
                "characteristic_ids": [
                  "CH-PETS"
                ],
                "created_at": "2026-10-21T07:00:00Z",
                "evidence": [],
                "fields_schema": {
                  "properties": {
                    "allowed": {
                      "type": "boolean"
                    },
                    "conditions": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "hotel_id": "b31b83c7-4c47-5ff9-bb5e-38efed4e7d81",
                "hotel_name": "Отель «Имерети Бэй»",
                "id": "a0284892-d67e-5eb7-8494-24227f28f29a",
                "priority": 3.8,
                "reason": "demand",
                "state": "open",
                "text": "Гости рядом ищут отели с питомцами. Пускаете ли вы с животными и на каких условиях?"
              },
              {
                "characteristic_ids": [
                  "CH-SOUNDPROOF"
                ],
                "created_at": "2026-10-20T07:00:00Z",
                "evidence": [],
                "fields_schema": {
                  "properties": {
                    "soundproof": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                },
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "hotel_name": "Гостиница «Бриз»",
                "id": "4e9c657b-ca3d-5477-a086-f0df45ba72c6",
                "priority": 2.5,
                "reason": "low_confidence",
                "state": "answered",
                "text": "Верно ли, что в номерах на трассу стоят шумоизолирующие окна?"
              }
            ],
            "page": 1,
            "page_size": 10,
            "total": 12
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/Question"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "QuestionPage",
        "type": "object"
      },
      "QuestionReason": {
        "description": "Почему спрашиваем отельера.",
        "enum": [
          "missing",
          "conflict",
          "low_confidence",
          "demand"
        ],
        "examples": [
          "missing"
        ],
        "title": "QuestionReason",
        "type": "string"
      },
      "QuestionState": {
        "enum": [
          "open",
          "answered",
          "expired",
          "cancelled"
        ],
        "examples": [
          "open"
        ],
        "title": "QuestionState",
        "type": "string"
      },
      "QueueState": {
        "additionalProperties": false,
        "examples": [
          {
            "failed": 1,
            "queued": 14,
            "running": 2,
            "workers_alive": 1
          }
        ],
        "properties": {
          "failed": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Failed",
            "type": "integer"
          },
          "queued": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Queued",
            "type": "integer"
          },
          "running": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Running",
            "type": "integer"
          },
          "workers_alive": {
            "maximum": 64.0,
            "minimum": 0.0,
            "title": "Workers Alive",
            "type": "integer"
          }
        },
        "required": [
          "queued",
          "running",
          "failed",
          "workers_alive"
        ],
        "title": "QueueState",
        "type": "object"
      },
      "RankingMetrics": {
        "additionalProperties": false,
        "examples": [
          {
            "ndcg_at_5": 0.82,
            "precision_at_3": 0.71
          }
        ],
        "properties": {
          "ndcg_at_5": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Ndcg At 5",
            "type": "number"
          },
          "precision_at_3": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Precision At 3",
            "type": "number"
          }
        },
        "required": [
          "ndcg_at_5",
          "precision_at_3"
        ],
        "title": "RankingMetrics",
        "type": "object"
      },
      "Ready": {
        "additionalProperties": false,
        "description": "Готовность: база доступна. Тело закреплено, поэтому без data_mode.",
        "examples": [
          {
            "db": "ok",
            "status": "ready"
          }
        ],
        "properties": {
          "db": {
            "enum": [
              "ok",
              "unavailable"
            ],
            "title": "Db",
            "type": "string"
          },
          "status": {
            "enum": [
              "ready",
              "not_ready"
            ],
            "title": "Status",
            "type": "string"
          }
        },
        "required": [
          "status",
          "db"
        ],
        "title": "Ready",
        "type": "object"
      },
      "RecentEvent": {
        "additionalProperties": false,
        "examples": [
          {
            "kind": "sandbox",
            "text": "Рядом с «Бризом» построили дом: вид с ЮЗ пересчитан",
            "ts": "2026-10-23T08:48:00Z"
          }
        ],
        "properties": {
          "kind": {
            "maxLength": 40,
            "minLength": 1,
            "title": "Kind",
            "type": "string"
          },
          "text": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Text",
            "type": "string"
          },
          "ts": {
            "format": "date-time",
            "title": "Ts",
            "type": "string"
          }
        },
        "required": [
          "ts",
          "text",
          "kind"
        ],
        "title": "RecentEvent",
        "type": "object"
      },
      "RejectedVariant": {
        "additionalProperties": false,
        "description": "Отель из группы «Не подходят»: какая потребность помешала.",
        "examples": [
          {
            "blocking_need_index": 0,
            "hotel": {
              "address": "Курортный проспект, 103",
              "district": "matsesta",
              "id": "0466edb1-1103-5a78-9983-12bd845aa81f",
              "kind": "hotel",
              "location": {
                "lat": 43.5402,
                "lon": 39.7835
              },
              "name": "Отель «Мацеста Хиллс»",
              "photo": {
                "author": "Участник Викисклада",
                "license": "CC BY-SA 4.0",
                "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_matsesta_hills.jpg",
                "url": "/media/photos/matsesta-hills.jpg"
              },
              "stars": 4,
              "walk_back_min": 22,
              "walk_to_sea_min": 14
            },
            "why_not": "Не подходит по пункту «закат из окна». Сделайте его желательным, и отель вернётся в выдачу"
          }
        ],
        "properties": {
          "blocking_need_index": {
            "maximum": 11.0,
            "minimum": 0.0,
            "title": "Blocking Need Index",
            "type": "integer"
          },
          "hotel": {
            "$ref": "#/components/schemas/HotelSummary"
          },
          "why_not": {
            "maxLength": 300,
            "minLength": 1,
            "title": "Why Not",
            "type": "string"
          }
        },
        "required": [
          "hotel",
          "blocking_need_index",
          "why_not"
        ],
        "title": "RejectedVariant",
        "type": "object"
      },
      "Role": {
        "enum": [
          "guest",
          "hotelier",
          "admin"
        ],
        "examples": [
          "guest"
        ],
        "title": "Role",
        "type": "string"
      },
      "RoomGroup": {
        "additionalProperties": false,
        "examples": [
          {
            "floor_from": 2,
            "floor_to": 2,
            "id": "71601bd8-2de7-5b16-8da8-0ee0fa9f34ed",
            "label": "201-210",
            "sides": [
              "ЮЗ"
            ],
            "source": "hotelier",
            "updated_at": "2026-10-13T09:00:00Z"
          }
        ],
        "properties": {
          "floor_from": {
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floor From",
            "type": "integer"
          },
          "floor_to": {
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floor To",
            "type": "integer"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "label": {
            "maxLength": 40,
            "minLength": 1,
            "title": "Label",
            "type": "string"
          },
          "sides": {
            "items": {
              "$ref": "#/components/schemas/Rumb"
            },
            "maxItems": 8,
            "minItems": 1,
            "title": "Sides",
            "type": "array"
          },
          "source": {
            "$ref": "#/components/schemas/SourceType"
          },
          "updated_at": {
            "format": "date-time",
            "title": "Updated At",
            "type": "string"
          }
        },
        "required": [
          "id",
          "label",
          "floor_from",
          "floor_to",
          "sides",
          "source",
          "updated_at"
        ],
        "title": "RoomGroup",
        "type": "object"
      },
      "RoomGroupIn": {
        "additionalProperties": false,
        "examples": [
          {
            "floor_from": 3,
            "floor_to": 3,
            "label": "301-310",
            "sides": [
              "ЮЗ"
            ]
          }
        ],
        "properties": {
          "floor_from": {
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floor From",
            "type": "integer"
          },
          "floor_to": {
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floor To",
            "type": "integer"
          },
          "label": {
            "description": "Номера, например 301-310",
            "maxLength": 40,
            "minLength": 1,
            "title": "Label",
            "type": "string"
          },
          "sides": {
            "items": {
              "$ref": "#/components/schemas/Rumb"
            },
            "maxItems": 8,
            "minItems": 1,
            "title": "Sides",
            "type": "array"
          }
        },
        "required": [
          "label",
          "floor_from",
          "floor_to",
          "sides"
        ],
        "title": "RoomGroupIn",
        "type": "object"
      },
      "Rooms": {
        "additionalProperties": false,
        "examples": [
          {
            "data_mode": "real",
            "floors_count": 9,
            "footprint": [
              {
                "lat": 43.578943,
                "lon": 39.722764
              },
              {
                "lat": 43.579172,
                "lon": 39.723079
              },
              {
                "lat": 43.579057,
                "lon": 39.723236
              },
              {
                "lat": 43.578828,
                "lon": 39.722921
              }
            ],
            "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "rooms": [
              {
                "floor_from": 2,
                "floor_to": 2,
                "id": "71601bd8-2de7-5b16-8da8-0ee0fa9f34ed",
                "label": "201-210",
                "sides": [
                  "ЮЗ"
                ],
                "source": "hotelier",
                "updated_at": "2026-10-13T09:00:00Z"
              },
              {
                "floor_from": 3,
                "floor_to": 3,
                "id": "4cc2b384-f975-51c6-89d5-350543053199",
                "label": "301-310",
                "sides": [
                  "ЮЗ"
                ],
                "source": "hotelier",
                "updated_at": "2026-10-13T09:00:00Z"
              },
              {
                "floor_from": 4,
                "floor_to": 4,
                "id": "0c24b615-faae-57fe-9019-7977fe16a40b",
                "label": "401-410",
                "sides": [
                  "ЮЗ"
                ],
                "source": "hotelier",
                "updated_at": "2026-10-13T09:00:00Z"
              },
              {
                "floor_from": 5,
                "floor_to": 5,
                "id": "d19fe800-fb97-5914-9e91-4f5dbc18d56c",
                "label": "501-510",
                "sides": [
                  "ЮЗ"
                ],
                "source": "hotelier",
                "updated_at": "2026-10-13T09:00:00Z"
              },
              {
                "floor_from": 6,
                "floor_to": 6,
                "id": "9bfb36bd-04db-530e-b2b3-cc0ff440e8a5",
                "label": "601-610",
                "sides": [
                  "ЮЗ"
                ],
                "source": "hotelier",
                "updated_at": "2026-10-13T09:00:00Z"
              }
            ],
            "sides": [
              {
                "azimuth_deg": 225.0,
                "floors_count": 9,
                "length_m": 36.0,
                "rumb": "ЮЗ",
                "segment": [
                  {
                    "lat": 43.578866,
                    "lon": 39.722974
                  },
                  {
                    "lat": 43.578981,
                    "lon": 39.722816
                  }
                ]
              },
              {
                "azimuth_deg": 45.0,
                "floors_count": 9,
                "length_m": 18.0,
                "rumb": "СВ",
                "segment": [
                  {
                    "lat": 43.579134,
                    "lon": 39.723026
                  },
                  {
                    "lat": 43.579019,
                    "lon": 39.723184
                  }
                ]
              },
              {
                "azimuth_deg": 315.0,
                "floors_count": 9,
                "length_m": 36.0,
                "rumb": "СЗ",
                "segment": [
                  {
                    "lat": 43.579019,
                    "lon": 39.722816
                  },
                  {
                    "lat": 43.579134,
                    "lon": 39.722974
                  }
                ]
              },
              {
                "azimuth_deg": 135.0,
                "floors_count": 9,
                "length_m": 18.0,
                "rumb": "ЮВ",
                "segment": [
                  {
                    "lat": 43.578981,
                    "lon": 39.723184
                  },
                  {
                    "lat": 43.578866,
                    "lon": 39.723026
                  }
                ]
              }
            ],
            "updated_at": "2026-10-13T09:00:00Z"
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "floors_count": {
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floors Count",
            "type": "integer"
          },
          "footprint": {
            "description": "Контур здания из OSM",
            "items": {
              "$ref": "#/components/schemas/GeoPoint"
            },
            "maxItems": 200,
            "minItems": 3,
            "title": "Footprint",
            "type": "array"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "rooms": {
            "items": {
              "$ref": "#/components/schemas/RoomGroup"
            },
            "maxItems": 200,
            "title": "Rooms",
            "type": "array"
          },
          "sides": {
            "items": {
              "$ref": "#/components/schemas/SideGeometry"
            },
            "maxItems": 8,
            "title": "Sides",
            "type": "array"
          },
          "updated_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Updated At"
          }
        },
        "required": [
          "data_mode",
          "hotel_id",
          "footprint",
          "sides",
          "floors_count",
          "rooms",
          "updated_at"
        ],
        "title": "Rooms",
        "type": "object"
      },
      "RoomsIn": {
        "additionalProperties": false,
        "examples": [
          {
            "rooms": [
              {
                "floor_from": 3,
                "floor_to": 3,
                "label": "301-310",
                "sides": [
                  "ЮЗ"
                ]
              },
              {
                "floor_from": 4,
                "floor_to": 4,
                "label": "401-410",
                "sides": [
                  "ЮЗ",
                  "СЗ"
                ]
              }
            ]
          }
        ],
        "properties": {
          "rooms": {
            "items": {
              "$ref": "#/components/schemas/RoomGroupIn"
            },
            "maxItems": 200,
            "title": "Rooms",
            "type": "array"
          }
        },
        "required": [
          "rooms"
        ],
        "title": "RoomsIn",
        "type": "object"
      },
      "Rumb": {
        "description": "Сторона здания по 8 румбам.",
        "enum": [
          "С",
          "СВ",
          "В",
          "ЮВ",
          "Ю",
          "ЮЗ",
          "З",
          "СЗ"
        ],
        "examples": [
          "С"
        ],
        "title": "Rumb",
        "type": "string"
      },
      "Run": {
        "additionalProperties": false,
        "description": "Запуск агента: охват, ход, токены и стоимость LLM.",
        "examples": [
          {
            "agent": "AG-ORCH",
            "cost_usd": 0.0,
            "created_by": "Админ Островка (демо)",
            "error": null,
            "finished_at": null,
            "id": "11176bcc-8d14-58b7-9c4f-6a655b8b9578",
            "kind": "collect",
            "progress": 0.64,
            "scope": "CH-SUNSET × 30 витринных отелей",
            "started_at": "2026-10-23T08:00:00Z",
            "state": "running",
            "tokens_in": 0,
            "tokens_out": 0
          }
        ],
        "properties": {
          "agent": {
            "$ref": "#/components/schemas/AgentId"
          },
          "cost_usd": {
            "maximum": 100000.0,
            "minimum": 0.0,
            "title": "Cost Usd",
            "type": "number"
          },
          "created_by": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Created By",
            "type": "string"
          },
          "error": {
            "anyOf": [
              {
                "maxLength": 500,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error"
          },
          "finished_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Finished At"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/RunKind"
          },
          "progress": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Progress",
            "type": "number"
          },
          "scope": {
            "description": "Охват словами: «CH-SUNSET × 30 отелей»",
            "maxLength": 200,
            "minLength": 1,
            "title": "Scope",
            "type": "string"
          },
          "started_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Started At"
          },
          "state": {
            "$ref": "#/components/schemas/RunState"
          },
          "tokens_in": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Tokens In",
            "type": "integer"
          },
          "tokens_out": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Tokens Out",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "kind",
          "agent",
          "scope",
          "state",
          "progress",
          "started_at",
          "finished_at",
          "tokens_in",
          "tokens_out",
          "cost_usd",
          "error",
          "created_by"
        ],
        "title": "Run",
        "type": "object"
      },
      "RunAccepted": {
        "additionalProperties": false,
        "description": "202: запуск поставлен в очередь. Ход работы - SSE по events_path.",
        "examples": [
          {
            "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"
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "events_path": {
            "maxLength": 200,
            "minLength": 1,
            "pattern": "^/api/v1/admin/runs/",
            "title": "Events Path",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/RunKind"
          },
          "run_id": {
            "format": "uuid",
            "title": "Run Id",
            "type": "string"
          },
          "state": {
            "$ref": "#/components/schemas/RunState"
          }
        },
        "required": [
          "data_mode",
          "run_id",
          "kind",
          "state",
          "events_path"
        ],
        "title": "RunAccepted",
        "type": "object"
      },
      "RunCreateIn": {
        "additionalProperties": false,
        "examples": [
          {
            "kind": "recompute",
            "scope": {
              "characteristic_ids": [],
              "hotel_ids": [],
              "source_id": null,
              "stale_only": true
            }
          }
        ],
        "properties": {
          "kind": {
            "enum": [
              "collect",
              "recompute",
              "ask"
            ],
            "title": "Kind",
            "type": "string"
          },
          "scope": {
            "$ref": "#/components/schemas/RunScopeIn"
          }
        },
        "required": [
          "kind",
          "scope"
        ],
        "title": "RunCreateIn",
        "type": "object"
      },
      "RunDetail": {
        "additionalProperties": false,
        "examples": [
          {
            "changes": [
              {
                "after": "Закат над морем, апрель - август",
                "before": "Закат за здания",
                "characteristic_id": "CH-SUNSET",
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "scope_key": "floor:ЮЗ:5"
              }
            ],
            "data_mode": "real",
            "events": [
              {
                "data": {
                  "step": 1
                },
                "id": 1,
                "level": "info",
                "message": "Шаг 1 из 3: профиль горизонта для 4 сторон",
                "run_id": "d473b344-6128-5adc-8f72-36c89bc4e483",
                "ts": "2026-10-23T08:30:00Z",
                "type": "step"
              },
              {
                "data": {
                  "step": 2,
                  "tool": "geo.horizon_profile"
                },
                "id": 2,
                "level": "info",
                "message": "geo.horizon_profile: сторона ЮЗ, этажи 1-9, шаг 0,5°",
                "run_id": "d473b344-6128-5adc-8f72-36c89bc4e483",
                "ts": "2026-10-23T08:31:00Z",
                "type": "tool_call"
              },
              {
                "data": {
                  "step": 3
                },
                "id": 3,
                "level": "info",
                "message": "Профиль готов за 0,4 с, качество входов 0,9",
                "run_id": "d473b344-6128-5adc-8f72-36c89bc4e483",
                "ts": "2026-10-23T08:32:00Z",
                "type": "tool_result"
              },
              {
                "data": {
                  "step": 4
                },
                "id": 4,
                "level": "info",
                "message": "Высота соседнего дома по OSM неточна: считаем вилку высот",
                "run_id": "d473b344-6128-5adc-8f72-36c89bc4e483",
                "ts": "2026-10-23T08:33:00Z",
                "type": "decision"
              },
              {
                "data": {
                  "step": 5,
                  "tool": "geo.sun_visibility"
                },
                "id": 5,
                "level": "info",
                "message": "geo.sun_visibility: даты 1, 11 и 21 числа каждого месяца",
                "run_id": "d473b344-6128-5adc-8f72-36c89bc4e483",
                "ts": "2026-10-23T08:34:00Z",
                "type": "tool_call"
              },
              {
                "data": {
                  "step": 6
                },
                "id": 6,
                "level": "warning",
                "message": "Мини-расписание устарело: CH-AIRPORT пропущена",
                "run_id": "d473b344-6128-5adc-8f72-36c89bc4e483",
                "ts": "2026-10-23T08:35:00Z",
                "type": "error"
              },
              {
                "data": {
                  "step": 7
                },
                "id": 7,
                "level": "info",
                "message": "Новое наблюдение CH-SUNSET, прежнее помечено superseded",
                "run_id": "d473b344-6128-5adc-8f72-36c89bc4e483",
                "ts": "2026-10-23T08:36:00Z",
                "type": "decision"
              }
            ],
            "result": {
              "ch_id": "CH-BAKERY",
              "kind": "characteristic_version",
              "object_id": null,
              "version": 1
            },
            "run": {
              "agent": "AG-PLAN",
              "cost_usd": 0.03,
              "created_by": "Админ Островка (демо)",
              "error": null,
              "finished_at": "2026-10-23T07:00:00Z",
              "id": "d473b344-6128-5adc-8f72-36c89bc4e483",
              "kind": "plan",
              "progress": 1.0,
              "scope": "Характеристика из текста: пекарня с 7 утра",
              "started_at": "2026-10-23T06:00:00Z",
              "state": "done",
              "tokens_in": 4200,
              "tokens_out": 900
            }
          }
        ],
        "properties": {
          "changes": {
            "items": {
              "$ref": "#/components/schemas/ValueChange"
            },
            "maxItems": 50,
            "title": "Changes",
            "type": "array"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "events": {
            "items": {
              "$ref": "#/components/schemas/RunEvent"
            },
            "maxItems": 200,
            "title": "Events",
            "type": "array"
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RunResultRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "run": {
            "$ref": "#/components/schemas/Run"
          }
        },
        "required": [
          "data_mode",
          "run",
          "events",
          "changes",
          "result"
        ],
        "title": "RunDetail",
        "type": "object"
      },
      "RunEvent": {
        "additionalProperties": false,
        "description": "Событие журнала запуска. id - для повторного подключения по Last-Event-ID.",
        "examples": [
          {
            "data": {
              "step": 1
            },
            "id": 1,
            "level": "info",
            "message": "Шаг 1 из 3: профиль горизонта для 4 сторон",
            "run_id": "d473b344-6128-5adc-8f72-36c89bc4e483",
            "ts": "2026-10-23T08:30:00Z",
            "type": "step"
          }
        ],
        "properties": {
          "data": {
            "additionalProperties": true,
            "description": "Подробности без персональных данных",
            "maxProperties": 30,
            "title": "Data",
            "type": "object"
          },
          "id": {
            "maximum": 10000000.0,
            "minimum": 1.0,
            "title": "Id",
            "type": "integer"
          },
          "level": {
            "$ref": "#/components/schemas/EventLevel"
          },
          "message": {
            "maxLength": 500,
            "minLength": 1,
            "title": "Message",
            "type": "string"
          },
          "run_id": {
            "format": "uuid",
            "title": "Run Id",
            "type": "string"
          },
          "ts": {
            "format": "date-time",
            "title": "Ts",
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/RunEventType"
          }
        },
        "required": [
          "id",
          "run_id",
          "ts",
          "type",
          "level",
          "message",
          "data"
        ],
        "title": "RunEvent",
        "type": "object"
      },
      "RunEventMessage": {
        "additionalProperties": false,
        "description": "SSE `run_event`: событие журнала с режимом данных.",
        "examples": [
          {
            "data": {
              "step": 1
            },
            "data_mode": "real",
            "id": 1,
            "level": "info",
            "message": "Шаг 1 из 3: профиль горизонта для 4 сторон",
            "run_id": "d473b344-6128-5adc-8f72-36c89bc4e483",
            "ts": "2026-10-23T08:30:00Z",
            "type": "step"
          }
        ],
        "properties": {
          "data": {
            "additionalProperties": true,
            "description": "Подробности без персональных данных",
            "maxProperties": 30,
            "title": "Data",
            "type": "object"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "id": {
            "maximum": 10000000,
            "minimum": 1,
            "title": "Id",
            "type": "integer"
          },
          "level": {
            "$ref": "#/components/schemas/EventLevel"
          },
          "message": {
            "maxLength": 500,
            "minLength": 1,
            "title": "Message",
            "type": "string"
          },
          "run_id": {
            "format": "uuid",
            "title": "Run Id",
            "type": "string"
          },
          "ts": {
            "format": "date-time",
            "title": "Ts",
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/RunEventType"
          }
        },
        "required": [
          "id",
          "run_id",
          "ts",
          "type",
          "level",
          "message",
          "data",
          "data_mode"
        ],
        "title": "RunEventMessage",
        "type": "object"
      },
      "RunEventType": {
        "description": "Тип события журнала запуска (RunEvent).",
        "enum": [
          "step",
          "tool_call",
          "tool_result",
          "llm_call",
          "decision",
          "error"
        ],
        "examples": [
          "step"
        ],
        "title": "RunEventType",
        "type": "string"
      },
      "RunKind": {
        "description": "Вид запуска агента.",
        "enum": [
          "collect",
          "recompute",
          "plan",
          "ask",
          "simulate",
          "sandbox"
        ],
        "examples": [
          "collect"
        ],
        "title": "RunKind",
        "type": "string"
      },
      "RunPage": {
        "additionalProperties": false,
        "description": "Запуски агентов.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "agent": "AG-HTL",
                "cost_usd": 0.03,
                "created_by": "Планировщик сроков",
                "error": null,
                "finished_at": null,
                "id": "f8d86c17-fff9-585d-8620-c0b11a26c4a1",
                "kind": "ask",
                "progress": 0.0,
                "scope": "Вопросы отельерам: 3 отеля",
                "started_at": null,
                "state": "queued",
                "tokens_in": 4200,
                "tokens_out": 900
              },
              {
                "agent": "AG-HTL",
                "cost_usd": 0.03,
                "created_by": "Планировщик сроков",
                "error": null,
                "finished_at": null,
                "id": "0ec1458a-29c6-536a-9fd9-db62c742db4f",
                "kind": "ask",
                "progress": 0.0,
                "scope": "Вопросы отельерам: 3 отеля",
                "started_at": null,
                "state": "queued",
                "tokens_in": 4200,
                "tokens_out": 900
              },
              {
                "agent": "AG-HTL",
                "cost_usd": 0.03,
                "created_by": "Планировщик сроков",
                "error": null,
                "finished_at": null,
                "id": "8b44866b-9de9-5874-abf4-84a3135cfabf",
                "kind": "ask",
                "progress": 0.0,
                "scope": "Вопросы отельерам: 3 отеля",
                "started_at": null,
                "state": "queued",
                "tokens_in": 4200,
                "tokens_out": 900
              },
              {
                "agent": "AG-ORCH",
                "cost_usd": 0.0,
                "created_by": "Админ Островка (демо)",
                "error": null,
                "finished_at": null,
                "id": "11176bcc-8d14-58b7-9c4f-6a655b8b9578",
                "kind": "collect",
                "progress": 0.64,
                "scope": "CH-SUNSET × 30 витринных отелей",
                "started_at": "2026-10-23T08:00:00Z",
                "state": "running",
                "tokens_in": 0,
                "tokens_out": 0
              },
              {
                "agent": "AG-ORCH",
                "cost_usd": 0.0,
                "created_by": "Планировщик сроков",
                "error": null,
                "finished_at": "2026-10-23T08:00:00Z",
                "id": "cf41c6dd-c628-5081-9698-67fad6b4a920",
                "kind": "recompute",
                "progress": 1.0,
                "scope": "Отель «Бриз»: все характеристики",
                "started_at": "2026-10-23T07:00:00Z",
                "state": "done",
                "tokens_in": 0,
                "tokens_out": 0
              },
              {
                "agent": "AG-PLAN",
                "cost_usd": 0.03,
                "created_by": "Админ Островка (демо)",
                "error": null,
                "finished_at": "2026-10-23T07:00:00Z",
                "id": "d473b344-6128-5adc-8f72-36c89bc4e483",
                "kind": "plan",
                "progress": 1.0,
                "scope": "Характеристика из текста: пекарня с 7 утра",
                "started_at": "2026-10-23T06:00:00Z",
                "state": "done",
                "tokens_in": 4200,
                "tokens_out": 900
              },
              {
                "agent": "AG-SIM",
                "cost_usd": 0.03,
                "created_by": "Админ Островка (демо)",
                "error": null,
                "finished_at": "2026-10-23T05:00:00Z",
                "id": "308463b8-0fc4-5dcd-9387-d150bf8a4238",
                "kind": "simulate",
                "progress": 1.0,
                "scope": "LLM-отельер: «Кудепста Плаза»",
                "started_at": "2026-10-23T04:00:00Z",
                "state": "done",
                "tokens_in": 4200,
                "tokens_out": 900
              },
              {
                "agent": "AG-ORCH",
                "cost_usd": 0.0,
                "created_by": "Планировщик сроков",
                "error": "Valhalla не ответила за 30 с, задача повторится",
                "finished_at": "2026-10-23T04:00:00Z",
                "id": "2c5f49f2-10f9-5aac-b507-5db820788bcd",
                "kind": "sandbox",
                "progress": 0.4,
                "scope": "Песочница: дом 27 м у Морского вокзала",
                "started_at": "2026-10-23T03:00:00Z",
                "state": "failed",
                "tokens_in": 0,
                "tokens_out": 0
              },
              {
                "agent": "AG-ORCH",
                "cost_usd": 0.0,
                "created_by": "Админ Островка (демо)",
                "error": null,
                "finished_at": null,
                "id": "d799c4ea-2aec-5051-a411-0ef7da9dc0a1",
                "kind": "collect",
                "progress": 0.64,
                "scope": "CH-SUNSET × 30 витринных отелей",
                "started_at": "2026-10-23T02:00:00Z",
                "state": "running",
                "tokens_in": 0,
                "tokens_out": 0
              },
              {
                "agent": "AG-ORCH",
                "cost_usd": 0.0,
                "created_by": "Планировщик сроков",
                "error": null,
                "finished_at": "2026-10-23T02:00:00Z",
                "id": "77d396a6-3213-5eff-8d61-009e59674cca",
                "kind": "recompute",
                "progress": 1.0,
                "scope": "Отель «Бриз»: все характеристики",
                "started_at": "2026-10-23T01:00:00Z",
                "state": "done",
                "tokens_in": 0,
                "tokens_out": 0
              }
            ],
            "page": 1,
            "page_size": 10,
            "total": 18
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/Run"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "RunPage",
        "type": "object"
      },
      "RunResultRef": {
        "additionalProperties": false,
        "description": "Что создал запуск: версию спецификации или сценарий песочницы.",
        "examples": [
          {
            "ch_id": "CH-BAKERY",
            "kind": "characteristic_version",
            "object_id": null,
            "version": 1
          }
        ],
        "properties": {
          "ch_id": {
            "anyOf": [
              {
                "description": "ID характеристики, CH-*",
                "maxLength": 43,
                "minLength": 5,
                "pattern": "^CH-[A-Z0-9-]{2,40}$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ch Id"
          },
          "kind": {
            "enum": [
              "characteristic_version",
              "sandbox_scenario",
              "answer_draft"
            ],
            "title": "Kind",
            "type": "string"
          },
          "object_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Object Id"
          },
          "version": {
            "anyOf": [
              {
                "maximum": 999.0,
                "minimum": 1.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Version"
          }
        },
        "required": [
          "kind",
          "ch_id",
          "version",
          "object_id"
        ],
        "title": "RunResultRef",
        "type": "object"
      },
      "RunScopeIn": {
        "additionalProperties": false,
        "examples": [
          {
            "characteristic_ids": [],
            "hotel_ids": [],
            "source_id": null,
            "stale_only": true
          }
        ],
        "properties": {
          "characteristic_ids": {
            "items": {
              "description": "ID характеристики, CH-*",
              "maxLength": 43,
              "minLength": 5,
              "pattern": "^CH-[A-Z0-9-]{2,40}$",
              "type": "string"
            },
            "maxItems": 20,
            "title": "Characteristic Ids",
            "type": "array"
          },
          "hotel_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 50,
            "title": "Hotel Ids",
            "type": "array"
          },
          "source_id": {
            "anyOf": [
              {
                "description": "ID источника",
                "maxLength": 6,
                "minLength": 6,
                "pattern": "^SRC-[0-9]{2}$",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Обновить источник из A6",
            "title": "Source Id"
          },
          "stale_only": {
            "default": false,
            "description": "Перепроверить устаревшие",
            "title": "Stale Only",
            "type": "boolean"
          }
        },
        "title": "RunScopeIn",
        "type": "object"
      },
      "RunState": {
        "enum": [
          "queued",
          "running",
          "done",
          "failed"
        ],
        "examples": [
          "queued"
        ],
        "title": "RunState",
        "type": "string"
      },
      "SandboxAccepted": {
        "additionalProperties": false,
        "description": "202: сценарий создан, пересчёт соседей в очереди.",
        "examples": [
          {
            "data_mode": "real",
            "run_id": "f5c42a31-bfe2-5d1f-81d4-cf0f8728280e",
            "scenario": {
              "affected": [
                {
                  "after": {
                    "sea_sector_deg": 21.5,
                    "sunset_over": "buildings"
                  },
                  "before": {
                    "sea_sector_deg": 62.0,
                    "sunset_over": "sea"
                  },
                  "changed": true,
                  "floor": 1,
                  "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                  "hotel_name": "Гостиница «Бриз»",
                  "side": "ЮЗ"
                },
                {
                  "after": {
                    "sea_sector_deg": 21.5,
                    "sunset_over": "buildings"
                  },
                  "before": {
                    "sea_sector_deg": 62.0,
                    "sunset_over": "sea"
                  },
                  "changed": true,
                  "floor": 2,
                  "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                  "hotel_name": "Гостиница «Бриз»",
                  "side": "ЮЗ"
                },
                {
                  "after": {
                    "sea_sector_deg": 21.5,
                    "sunset_over": "buildings"
                  },
                  "before": {
                    "sea_sector_deg": 62.0,
                    "sunset_over": "sea"
                  },
                  "changed": true,
                  "floor": 3,
                  "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                  "hotel_name": "Гостиница «Бриз»",
                  "side": "ЮЗ"
                },
                {
                  "after": {
                    "sea_sector_deg": 21.5,
                    "sunset_over": "buildings"
                  },
                  "before": {
                    "sea_sector_deg": 62.0,
                    "sunset_over": "sea"
                  },
                  "changed": true,
                  "floor": 4,
                  "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                  "hotel_name": "Гостиница «Бриз»",
                  "side": "ЮЗ"
                },
                {
                  "after": {
                    "sea_sector_deg": 21.5,
                    "sunset_over": "buildings"
                  },
                  "before": {
                    "sea_sector_deg": 62.0,
                    "sunset_over": "sea"
                  },
                  "changed": true,
                  "floor": 5,
                  "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                  "hotel_name": "Гостиница «Бриз»",
                  "side": "ЮЗ"
                },
                {
                  "after": {
                    "sea_sector_deg": 21.5,
                    "sunset_over": "buildings"
                  },
                  "before": {
                    "sea_sector_deg": 62.0,
                    "sunset_over": "sea"
                  },
                  "changed": true,
                  "floor": 6,
                  "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                  "hotel_name": "Гостиница «Бриз»",
                  "side": "ЮЗ"
                }
              ],
              "applied_at": null,
              "building": {
                "center": {
                  "lat": 43.5784,
                  "lon": 39.7221
                },
                "footprint": [
                  {
                    "lat": 43.578397,
                    "lon": 39.721922
                  },
                  {
                    "lat": 43.578521,
                    "lon": 39.722165
                  },
                  {
                    "lat": 43.578403,
                    "lon": 39.722278
                  },
                  {
                    "lat": 43.578279,
                    "lon": 39.722035
                  },
                  {
                    "lat": 43.578397,
                    "lon": 39.721922
                  }
                ],
                "height_m": 27.0
              },
              "created_at": "2026-10-23T09:00:00Z",
              "id": "331928b8-b916-5bc7-8628-8bb0fc431967",
              "label": "Дом 27 м напротив «Бриза»",
              "notified_hotels": 1,
              "state": "computing"
            }
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "run_id": {
            "format": "uuid",
            "title": "Run Id",
            "type": "string"
          },
          "scenario": {
            "$ref": "#/components/schemas/SandboxScenario"
          }
        },
        "required": [
          "data_mode",
          "scenario",
          "run_id"
        ],
        "title": "SandboxAccepted",
        "type": "object"
      },
      "SandboxBuilding": {
        "additionalProperties": false,
        "examples": [
          {
            "center": {
              "lat": 43.5784,
              "lon": 39.7221
            },
            "footprint": [
              {
                "lat": 43.578397,
                "lon": 39.721922
              },
              {
                "lat": 43.578521,
                "lon": 39.722165
              },
              {
                "lat": 43.578403,
                "lon": 39.722278
              },
              {
                "lat": 43.578279,
                "lon": 39.722035
              },
              {
                "lat": 43.578397,
                "lon": 39.721922
              }
            ],
            "height_m": 27.0
          }
        ],
        "properties": {
          "center": {
            "$ref": "#/components/schemas/GeoPoint"
          },
          "footprint": {
            "items": {
              "$ref": "#/components/schemas/GeoPoint"
            },
            "maxItems": 50,
            "minItems": 4,
            "title": "Footprint",
            "type": "array"
          },
          "height_m": {
            "maximum": 300.0,
            "minimum": 3.0,
            "title": "Height M",
            "type": "number"
          }
        },
        "required": [
          "center",
          "footprint",
          "height_m"
        ],
        "title": "SandboxBuilding",
        "type": "object"
      },
      "SandboxBuildingIn": {
        "additionalProperties": false,
        "description": "«Дом в центре карты»: прямоугольник с размерами, высотой и поворотом.",
        "examples": [
          {
            "center": {
              "lat": 43.5784,
              "lon": 39.7221
            },
            "depth_m": 16.0,
            "height_m": 27.0,
            "rotation_deg": 35.0,
            "width_m": 24.0
          }
        ],
        "properties": {
          "center": {
            "$ref": "#/components/schemas/GeoPoint"
          },
          "depth_m": {
            "maximum": 200.0,
            "minimum": 5.0,
            "title": "Depth M",
            "type": "number"
          },
          "height_m": {
            "maximum": 300.0,
            "minimum": 3.0,
            "title": "Height M",
            "type": "number"
          },
          "rotation_deg": {
            "exclusiveMaximum": 360.0,
            "minimum": 0.0,
            "title": "Rotation Deg",
            "type": "number"
          },
          "width_m": {
            "maximum": 200.0,
            "minimum": 5.0,
            "title": "Width M",
            "type": "number"
          }
        },
        "required": [
          "center",
          "width_m",
          "depth_m",
          "height_m",
          "rotation_deg"
        ],
        "title": "SandboxBuildingIn",
        "type": "object"
      },
      "SandboxPage": {
        "additionalProperties": false,
        "description": "Сценарии песочницы.",
        "examples": [
          {
            "data_mode": "real",
            "items": [
              {
                "affected": [
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 1,
                    "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                    "hotel_name": "Гостиница «Бриз»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 2,
                    "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                    "hotel_name": "Гостиница «Бриз»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 3,
                    "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                    "hotel_name": "Гостиница «Бриз»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 4,
                    "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                    "hotel_name": "Гостиница «Бриз»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 5,
                    "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                    "hotel_name": "Гостиница «Бриз»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 6,
                    "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                    "hotel_name": "Гостиница «Бриз»",
                    "side": "ЮЗ"
                  }
                ],
                "applied_at": null,
                "building": {
                  "center": {
                    "lat": 43.5784,
                    "lon": 39.7221
                  },
                  "footprint": [
                    {
                      "lat": 43.578397,
                      "lon": 39.721922
                    },
                    {
                      "lat": 43.578521,
                      "lon": 39.722165
                    },
                    {
                      "lat": 43.578403,
                      "lon": 39.722278
                    },
                    {
                      "lat": 43.578279,
                      "lon": 39.722035
                    },
                    {
                      "lat": 43.578397,
                      "lon": 39.721922
                    }
                  ],
                  "height_m": 27.0
                },
                "created_at": "2026-10-23T09:00:00Z",
                "id": "4cad8456-2fa8-5cff-9579-5ab0100aced3",
                "label": "Дом 27 м напротив «Бриза»",
                "notified_hotels": 1,
                "state": "ready"
              },
              {
                "affected": [
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 1,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 2,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 3,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 4,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 5,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 6,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  }
                ],
                "applied_at": "2026-10-23T09:00:00Z",
                "building": {
                  "center": {
                    "lat": 43.5784,
                    "lon": 39.7221
                  },
                  "footprint": [
                    {
                      "lat": 43.578397,
                      "lon": 39.721922
                    },
                    {
                      "lat": 43.578521,
                      "lon": 39.722165
                    },
                    {
                      "lat": 43.578403,
                      "lon": 39.722278
                    },
                    {
                      "lat": 43.578279,
                      "lon": 39.722035
                    },
                    {
                      "lat": 43.578397,
                      "lon": 39.721922
                    }
                  ],
                  "height_m": 27.0
                },
                "created_at": "2026-10-23T08:00:00Z",
                "id": "40e16e72-f512-5cde-8d1a-2259342ead48",
                "label": "Дом 27 м напротив «Бриза»",
                "notified_hotels": 1,
                "state": "applied"
              },
              {
                "affected": [
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 1,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 2,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 3,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 4,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 5,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 6,
                    "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                    "hotel_name": "Отель «Мацеста Хиллс»",
                    "side": "ЮЗ"
                  }
                ],
                "applied_at": null,
                "building": {
                  "center": {
                    "lat": 43.5784,
                    "lon": 39.7221
                  },
                  "footprint": [
                    {
                      "lat": 43.578397,
                      "lon": 39.721922
                    },
                    {
                      "lat": 43.578521,
                      "lon": 39.722165
                    },
                    {
                      "lat": 43.578403,
                      "lon": 39.722278
                    },
                    {
                      "lat": 43.578279,
                      "lon": 39.722035
                    },
                    {
                      "lat": 43.578397,
                      "lon": 39.721922
                    }
                  ],
                  "height_m": 27.0
                },
                "created_at": "2026-10-23T07:00:00Z",
                "id": "b2b82a19-c749-5fb0-a08a-d531aea48454",
                "label": "Дом 27 м напротив «Бриза»",
                "notified_hotels": 1,
                "state": "computing"
              },
              {
                "affected": [
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 1,
                    "hotel_id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                    "hotel_name": "Апарт-отель «Платан»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 2,
                    "hotel_id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                    "hotel_name": "Апарт-отель «Платан»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 3,
                    "hotel_id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                    "hotel_name": "Апарт-отель «Платан»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 4,
                    "hotel_id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                    "hotel_name": "Апарт-отель «Платан»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 5,
                    "hotel_id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                    "hotel_name": "Апарт-отель «Платан»",
                    "side": "ЮЗ"
                  },
                  {
                    "after": {
                      "sea_sector_deg": 21.5,
                      "sunset_over": "buildings"
                    },
                    "before": {
                      "sea_sector_deg": 62.0,
                      "sunset_over": "sea"
                    },
                    "changed": true,
                    "floor": 6,
                    "hotel_id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                    "hotel_name": "Апарт-отель «Платан»",
                    "side": "ЮЗ"
                  }
                ],
                "applied_at": null,
                "building": {
                  "center": {
                    "lat": 43.5784,
                    "lon": 39.7221
                  },
                  "footprint": [
                    {
                      "lat": 43.578397,
                      "lon": 39.721922
                    },
                    {
                      "lat": 43.578521,
                      "lon": 39.722165
                    },
                    {
                      "lat": 43.578403,
                      "lon": 39.722278
                    },
                    {
                      "lat": 43.578279,
                      "lon": 39.722035
                    },
                    {
                      "lat": 43.578397,
                      "lon": 39.721922
                    }
                  ],
                  "height_m": 27.0
                },
                "created_at": "2026-10-23T06:00:00Z",
                "id": "54cff7dd-365b-5e58-8bec-d4efed4e350f",
                "label": "Дом 27 м напротив «Бриза»",
                "notified_hotels": 1,
                "state": "ready"
              }
            ],
            "page": 1,
            "page_size": 25,
            "total": 4
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/SandboxScenario"
            },
            "maxItems": 100,
            "title": "Items",
            "type": "array"
          },
          "page": {
            "maximum": 10000.0,
            "minimum": 1.0,
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "enum": [
              10,
              25,
              50,
              100
            ],
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "items",
          "page",
          "page_size",
          "total"
        ],
        "title": "SandboxPage",
        "type": "object"
      },
      "SandboxScenario": {
        "additionalProperties": false,
        "examples": [
          {
            "affected": [
              {
                "after": {
                  "sea_sector_deg": 21.5,
                  "sunset_over": "buildings"
                },
                "before": {
                  "sea_sector_deg": 62.0,
                  "sunset_over": "sea"
                },
                "changed": true,
                "floor": 1,
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "hotel_name": "Гостиница «Бриз»",
                "side": "ЮЗ"
              },
              {
                "after": {
                  "sea_sector_deg": 21.5,
                  "sunset_over": "buildings"
                },
                "before": {
                  "sea_sector_deg": 62.0,
                  "sunset_over": "sea"
                },
                "changed": true,
                "floor": 2,
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "hotel_name": "Гостиница «Бриз»",
                "side": "ЮЗ"
              },
              {
                "after": {
                  "sea_sector_deg": 21.5,
                  "sunset_over": "buildings"
                },
                "before": {
                  "sea_sector_deg": 62.0,
                  "sunset_over": "sea"
                },
                "changed": true,
                "floor": 3,
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "hotel_name": "Гостиница «Бриз»",
                "side": "ЮЗ"
              },
              {
                "after": {
                  "sea_sector_deg": 21.5,
                  "sunset_over": "buildings"
                },
                "before": {
                  "sea_sector_deg": 62.0,
                  "sunset_over": "sea"
                },
                "changed": true,
                "floor": 4,
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "hotel_name": "Гостиница «Бриз»",
                "side": "ЮЗ"
              },
              {
                "after": {
                  "sea_sector_deg": 21.5,
                  "sunset_over": "buildings"
                },
                "before": {
                  "sea_sector_deg": 62.0,
                  "sunset_over": "sea"
                },
                "changed": true,
                "floor": 5,
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "hotel_name": "Гостиница «Бриз»",
                "side": "ЮЗ"
              },
              {
                "after": {
                  "sea_sector_deg": 21.5,
                  "sunset_over": "buildings"
                },
                "before": {
                  "sea_sector_deg": 62.0,
                  "sunset_over": "sea"
                },
                "changed": true,
                "floor": 6,
                "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                "hotel_name": "Гостиница «Бриз»",
                "side": "ЮЗ"
              }
            ],
            "applied_at": null,
            "building": {
              "center": {
                "lat": 43.5784,
                "lon": 39.7221
              },
              "footprint": [
                {
                  "lat": 43.578397,
                  "lon": 39.721922
                },
                {
                  "lat": 43.578521,
                  "lon": 39.722165
                },
                {
                  "lat": 43.578403,
                  "lon": 39.722278
                },
                {
                  "lat": 43.578279,
                  "lon": 39.722035
                },
                {
                  "lat": 43.578397,
                  "lon": 39.721922
                }
              ],
              "height_m": 27.0
            },
            "created_at": "2026-10-23T09:00:00Z",
            "id": "4cad8456-2fa8-5cff-9579-5ab0100aced3",
            "label": "Дом 27 м напротив «Бриза»",
            "notified_hotels": 1,
            "state": "ready"
          }
        ],
        "properties": {
          "affected": {
            "items": {
              "$ref": "#/components/schemas/AffectedViewpoint"
            },
            "maxItems": 100,
            "title": "Affected",
            "type": "array"
          },
          "applied_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Applied At"
          },
          "building": {
            "$ref": "#/components/schemas/SandboxBuilding"
          },
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "label": {
            "anyOf": [
              {
                "maxLength": 80,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Label"
          },
          "notified_hotels": {
            "maximum": 10000000.0,
            "minimum": 0.0,
            "title": "Notified Hotels",
            "type": "integer"
          },
          "state": {
            "$ref": "#/components/schemas/SandboxState"
          }
        },
        "required": [
          "id",
          "label",
          "state",
          "building",
          "affected",
          "notified_hotels",
          "created_at",
          "applied_at"
        ],
        "title": "SandboxScenario",
        "type": "object"
      },
      "SandboxScenarioIn": {
        "additionalProperties": false,
        "examples": [
          {
            "building": {
              "center": {
                "lat": 43.5784,
                "lon": 39.7221
              },
              "depth_m": 16.0,
              "height_m": 27.0,
              "rotation_deg": 35.0,
              "width_m": 24.0
            },
            "label": "Дом 27 м напротив «Бриза»"
          }
        ],
        "properties": {
          "building": {
            "$ref": "#/components/schemas/SandboxBuildingIn"
          },
          "label": {
            "anyOf": [
              {
                "maxLength": 80,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Label"
          }
        },
        "required": [
          "building"
        ],
        "title": "SandboxScenarioIn",
        "type": "object"
      },
      "SandboxState": {
        "enum": [
          "computing",
          "ready",
          "applied",
          "failed"
        ],
        "examples": [
          "computing"
        ],
        "title": "SandboxState",
        "type": "string"
      },
      "Scope": {
        "additionalProperties": false,
        "description": "Где действует значение: отель, сторона, точка обзора, номер или район.",
        "examples": [
          {
            "district": null,
            "floor": 5,
            "key": "floor:ЮЗ:5",
            "kind": "floor",
            "room": null,
            "side": "ЮЗ"
          }
        ],
        "properties": {
          "district": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/District"
              },
              {
                "type": "null"
              }
            ]
          },
          "floor": {
            "anyOf": [
              {
                "maximum": 40.0,
                "minimum": 1.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Floor"
          },
          "key": {
            "description": "Ключ уровня значения: hotel, side:ЮЗ, floor:ЮЗ:5, room:401 или room:<uuid>, district:adler; с датами - плюс |2026-06-01..2026-06-14",
            "maxLength": 80,
            "minLength": 5,
            "pattern": "^(hotel|side:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ)|floor:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ):([1-9]|[1-3][0-9]|40)|room:([0-9A-Za-z-]{1,20}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|district:(center|matsesta|khosta|kudepsta|adler|imeretinka))(\\|[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\\.\\.[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]))?$",
            "title": "Key",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/ScopeKind"
          },
          "room": {
            "anyOf": [
              {
                "maxLength": 36,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Номер, например 401, или id номера (uuid) из ключа",
            "title": "Room"
          },
          "side": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rumb"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "kind",
          "key",
          "side",
          "floor",
          "room",
          "district"
        ],
        "title": "Scope",
        "type": "object"
      },
      "ScopeKind": {
        "description": "Уровень значения.",
        "enum": [
          "hotel",
          "side",
          "floor",
          "room",
          "district"
        ],
        "examples": [
          "hotel"
        ],
        "title": "ScopeKind",
        "type": "string"
      },
      "SearchRequest": {
        "additionalProperties": false,
        "description": "Запрос гостя своими словами. Без текста - поиск по чипам из needs_override.",
        "examples": [
          {
            "dates": {
              "from": "2026-05-03",
              "to": "2026-05-07"
            },
            "needs_override": null,
            "query": "Закат над морем из окна и тихо ночью",
            "use_memory": false
          }
        ],
        "properties": {
          "dates": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateRange"
              },
              {
                "type": "null"
              }
            ]
          },
          "needs_override": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/NeedIn"
                },
                "maxItems": 12,
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "description": "Потребности после правки чипов, без повторного разбора",
            "title": "Needs Override"
          },
          "query": {
            "default": "",
            "description": "Запрос своими словами, до 500 символов",
            "maxLength": 500,
            "title": "Query",
            "type": "string"
          },
          "use_memory": {
            "default": false,
            "title": "Use Memory",
            "type": "boolean"
          }
        },
        "title": "SearchRequest",
        "type": "object"
      },
      "SearchResponse": {
        "additionalProperties": false,
        "examples": [
          {
            "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",
                "label": "Закат из окна",
                "source": "query"
              },
              {
                "characteristic_id": "CH-QUIET",
                "condition": {
                  "op": "night_quiet",
                  "value": null
                },
                "id": "n2",
                "importance": "must",
                "label": "Тишина ночью",
                "source": "query"
              },
              {
                "characteristic_id": "CH-NIGHT",
                "condition": {
                  "op": "pharmacy_24_7",
                  "value": null
                },
                "id": "n3",
                "importance": "should",
                "label": "Ночью рядом",
                "source": "query"
              }
            ],
            "parser": "rules",
            "rejected": [
              {
                "blocking_need_index": 0,
                "hotel": {
                  "address": "Курортный проспект, 103",
                  "district": "matsesta",
                  "id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                  "kind": "hotel",
                  "location": {
                    "lat": 43.5402,
                    "lon": 39.7835
                  },
                  "name": "Отель «Мацеста Хиллс»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_matsesta_hills.jpg",
                    "url": "/media/photos/matsesta-hills.jpg"
                  },
                  "stars": 4,
                  "walk_back_min": 22,
                  "walk_to_sea_min": 14
                },
                "why_not": "Не подходит по пункту «закат из окна». Сделайте его желательным, и отель вернётся в выдачу"
              },
              {
                "blocking_need_index": 0,
                "hotel": {
                  "address": "ул. Кирова, 30",
                  "district": "adler",
                  "id": "fc6971ad-f8c2-5645-8f7c-77d53ddbc0f6",
                  "kind": "motel",
                  "location": {
                    "lat": 43.427,
                    "lon": 39.9255
                  },
                  "name": "Мотель «Чайка»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_chayka.jpg",
                    "url": "/media/photos/chayka.jpg"
                  },
                  "stars": 2,
                  "walk_back_min": 13,
                  "walk_to_sea_min": 13
                },
                "why_not": "Не подходит по пункту «закат из окна». Сделайте его желательным, и отель вернётся в выдачу"
              },
              {
                "blocking_need_index": 0,
                "hotel": {
                  "address": "ул. Яна Фабрициуса, 8",
                  "district": "khosta",
                  "id": "04d3c1f3-6a62-558e-87a9-49e4ca3f35fa",
                  "kind": "hostel",
                  "location": {
                    "lat": 43.5195,
                    "lon": 39.8702
                  },
                  "name": "Хостел «Лагуна»",
                  "photo": null,
                  "stars": null,
                  "walk_back_min": 14,
                  "walk_to_sea_min": 11
                },
                "why_not": "Не подходит по пункту «закат из окна». Сделайте его желательным, и отель вернётся в выдачу"
              }
            ],
            "results": [
              {
                "explanation": {
                  "claims": [
                    {
                      "observation_ids": [
                        "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
                      ],
                      "text": "Закат над морем с 3-го этажа, сторона ЮЗ"
                    },
                    {
                      "observation_ids": [
                        "39a55f35-0329-5c71-b687-483a2ed6c8a0"
                      ],
                      "text": "Ночью тихо: трасса за домом"
                    },
                    {
                      "observation_ids": [
                        "d8d773fc-576c-5524-8894-06520f25b5cd"
                      ],
                      "text": "Ночью рядом: есть"
                    }
                  ],
                  "generated_by": "template"
                },
                "floor_from": 3,
                "hotel": {
                  "address": "Курортный проспект, 18",
                  "district": "center",
                  "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                  "kind": "hotel",
                  "location": {
                    "lat": 43.579,
                    "lon": 39.723
                  },
                  "name": "Гостиница «Бриз»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
                    "url": "/media/photos/briz.jpg"
                  },
                  "stars": 4,
                  "walk_back_min": 11,
                  "walk_to_sea_min": 7
                },
                "limits": [],
                "matches": [
                  {
                    "confidence": 0.88,
                    "need_index": 0,
                    "observation_ids": [
                      "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
                    ],
                    "reason": "Закат над морем с 3-го этажа, сторона ЮЗ",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.83,
                    "need_index": 1,
                    "observation_ids": [
                      "39a55f35-0329-5c71-b687-483a2ed6c8a0"
                    ],
                    "reason": "Ночью тихо: трасса за домом",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.78,
                    "need_index": 2,
                    "observation_ids": [
                      "d8d773fc-576c-5524-8894-06520f25b5cd"
                    ],
                    "reason": "Ночью рядом: есть",
                    "status": "yes"
                  }
                ],
                "score": 0.94,
                "side": "ЮЗ",
                "variant_id": "v1"
              },
              {
                "explanation": {
                  "claims": [
                    {
                      "observation_ids": [
                        "16377e7c-2277-5be8-8872-5988c294d3c6"
                      ],
                      "text": "Закат над морем с 4-го этажа, сторона ЮЗ"
                    },
                    {
                      "observation_ids": [
                        "d5f8ccd4-5f4e-55a8-a820-761512f3a86d"
                      ],
                      "text": "Ночью тихо: трасса за домом"
                    },
                    {
                      "observation_ids": [
                        "0f8161eb-a98c-59f3-a96d-d164e2ce78b7"
                      ],
                      "text": "Ночью рядом: частично"
                    }
                  ],
                  "generated_by": "template"
                },
                "floor_from": 4,
                "hotel": {
                  "address": "ул. Таврическая, 2",
                  "district": "kudepsta",
                  "id": "bf3f1cc9-b55c-571e-80c8-051e7dc64b9b",
                  "kind": "hotel",
                  "location": {
                    "lat": 43.4985,
                    "lon": 39.892
                  },
                  "name": "Отель «Кудепста Плаза»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_kudepsta_plaza.jpg",
                    "url": "/media/photos/kudepsta-plaza.jpg"
                  },
                  "stars": 4,
                  "walk_back_min": 9,
                  "walk_to_sea_min": 6
                },
                "limits": [
                  "Ночью рядом: частично"
                ],
                "matches": [
                  {
                    "confidence": 0.86,
                    "need_index": 0,
                    "observation_ids": [
                      "16377e7c-2277-5be8-8872-5988c294d3c6"
                    ],
                    "reason": "Закат над морем с 4-го этажа, сторона ЮЗ",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.81,
                    "need_index": 1,
                    "observation_ids": [
                      "d5f8ccd4-5f4e-55a8-a820-761512f3a86d"
                    ],
                    "reason": "Ночью тихо: трасса за домом",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.76,
                    "need_index": 2,
                    "observation_ids": [
                      "0f8161eb-a98c-59f3-a96d-d164e2ce78b7"
                    ],
                    "reason": "Ночью рядом: частично",
                    "status": "partial"
                  }
                ],
                "score": 0.87,
                "side": "ЮЗ",
                "variant_id": "v2"
              },
              {
                "explanation": {
                  "claims": [
                    {
                      "observation_ids": [
                        "8b2aa13e-3348-5cd7-8490-c2b7433e5926"
                      ],
                      "text": "Закат над морем с 5-го этажа, сторона ЮЗ"
                    },
                    {
                      "observation_ids": [
                        "344bb89b-6159-5921-94d9-af52bce2d566"
                      ],
                      "text": "Ночью шумно со стороны трассы"
                    },
                    {
                      "observation_ids": [
                        "be58cf7a-6aa5-5d5e-a792-144bba76ea38"
                      ],
                      "text": "Ночью рядом: есть"
                    }
                  ],
                  "generated_by": "template"
                },
                "floor_from": 5,
                "hotel": {
                  "address": "Олимпийский проспект, 21",
                  "district": "imeretinka",
                  "id": "b31b83c7-4c47-5ff9-bb5e-38efed4e7d81",
                  "kind": "hotel",
                  "location": {
                    "lat": 43.405,
                    "lon": 39.956
                  },
                  "name": "Отель «Имерети Бэй»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_imereti_bay.jpg",
                    "url": "/media/photos/imereti-bay.jpg"
                  },
                  "stars": 5,
                  "walk_back_min": 3,
                  "walk_to_sea_min": 3
                },
                "limits": [
                  "Ночью шумно со стороны трассы"
                ],
                "matches": [
                  {
                    "confidence": 0.84,
                    "need_index": 0,
                    "observation_ids": [
                      "8b2aa13e-3348-5cd7-8490-c2b7433e5926"
                    ],
                    "reason": "Закат над морем с 5-го этажа, сторона ЮЗ",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.79,
                    "need_index": 1,
                    "observation_ids": [
                      "344bb89b-6159-5921-94d9-af52bce2d566"
                    ],
                    "reason": "Ночью шумно со стороны трассы",
                    "status": "partial"
                  },
                  {
                    "confidence": 0.74,
                    "need_index": 2,
                    "observation_ids": [
                      "be58cf7a-6aa5-5d5e-a792-144bba76ea38"
                    ],
                    "reason": "Ночью рядом: есть",
                    "status": "yes"
                  }
                ],
                "score": 0.8,
                "side": "ЮЗ",
                "variant_id": "v3"
              },
              {
                "explanation": {
                  "claims": [
                    {
                      "observation_ids": [
                        "d204d914-abba-57d0-9131-1e2ec6a62970"
                      ],
                      "text": "Закат над морем только с 8-го этажа, сторона ЮЗ"
                    },
                    {
                      "observation_ids": [
                        "8761501c-0944-5f78-9cec-9e4bdbd8a309"
                      ],
                      "text": "Ночью тихо: трасса за домом"
                    },
                    {
                      "observation_ids": [
                        "9c378cda-cbb3-58f8-82a5-20e963120090"
                      ],
                      "text": "Ночью рядом: есть"
                    }
                  ],
                  "generated_by": "template"
                },
                "floor_from": 6,
                "hotel": {
                  "address": "ул. Ленина, 219",
                  "district": "adler",
                  "id": "ac62f78d-6aba-5791-a00a-ae7c92e71f8b",
                  "kind": "hotel",
                  "location": {
                    "lat": 43.4325,
                    "lon": 39.918
                  },
                  "name": "Гостиница «Адлер Марин»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_adler_marin.jpg",
                    "url": "/media/photos/adler-marin.jpg"
                  },
                  "stars": 3,
                  "walk_back_min": 6,
                  "walk_to_sea_min": 5
                },
                "limits": [
                  "Закат над морем только с 8-го этажа, сторона ЮЗ"
                ],
                "matches": [
                  {
                    "confidence": 0.82,
                    "need_index": 0,
                    "observation_ids": [
                      "d204d914-abba-57d0-9131-1e2ec6a62970"
                    ],
                    "reason": "Закат над морем только с 8-го этажа, сторона ЮЗ",
                    "status": "partial"
                  },
                  {
                    "confidence": 0.77,
                    "need_index": 1,
                    "observation_ids": [
                      "8761501c-0944-5f78-9cec-9e4bdbd8a309"
                    ],
                    "reason": "Ночью тихо: трасса за домом",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.72,
                    "need_index": 2,
                    "observation_ids": [
                      "9c378cda-cbb3-58f8-82a5-20e963120090"
                    ],
                    "reason": "Ночью рядом: есть",
                    "status": "yes"
                  }
                ],
                "score": 0.73,
                "side": "ЮЗ",
                "variant_id": "v4"
              },
              {
                "explanation": {
                  "claims": [
                    {
                      "observation_ids": [
                        "0d70cf62-adce-5329-ae17-d25839beec43"
                      ],
                      "text": "Закат над морем с 7-го этажа, сторона ЮЗ"
                    },
                    {
                      "observation_ids": [
                        "b633fa16-7e1a-538a-945a-c8f766ef5a8f"
                      ],
                      "text": "Ночью тихо: трасса за домом"
                    },
                    {
                      "observation_ids": [
                        "f5fe87a3-2d09-5138-8b93-c1580836f1ca"
                      ],
                      "text": "Ночью рядом: есть"
                    }
                  ],
                  "generated_by": "template"
                },
                "floor_from": 7,
                "hotel": {
                  "address": "ул. Черноморская, 4",
                  "district": "center",
                  "id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                  "kind": "apartment",
                  "location": {
                    "lat": 43.5935,
                    "lon": 39.7105
                  },
                  "name": "Апарт-отель «Платан»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_platan.jpg",
                    "url": "/media/photos/platan.jpg"
                  },
                  "stars": null,
                  "walk_back_min": 6,
                  "walk_to_sea_min": 4
                },
                "limits": [],
                "matches": [
                  {
                    "confidence": 0.8,
                    "need_index": 0,
                    "observation_ids": [
                      "0d70cf62-adce-5329-ae17-d25839beec43"
                    ],
                    "reason": "Закат над морем с 7-го этажа, сторона ЮЗ",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.75,
                    "need_index": 1,
                    "observation_ids": [
                      "b633fa16-7e1a-538a-945a-c8f766ef5a8f"
                    ],
                    "reason": "Ночью тихо: трасса за домом",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.7,
                    "need_index": 2,
                    "observation_ids": [
                      "f5fe87a3-2d09-5138-8b93-c1580836f1ca"
                    ],
                    "reason": "Ночью рядом: есть",
                    "status": "yes"
                  }
                ],
                "score": 0.66,
                "side": "ЮЗ",
                "variant_id": "v5"
              }
            ],
            "search_id": "a845ecb9-3522-5c78-9602-e232f3528df6",
            "unconfirmed": [
              {
                "explanation": {
                  "claims": [
                    {
                      "observation_ids": [
                        "b37548fc-b258-58f9-b760-654b5fb39f60"
                      ],
                      "text": "Ночью тихо: трасса за домом"
                    },
                    {
                      "observation_ids": [
                        "11c09355-cbc5-54a5-a2c8-66181ba7aaae"
                      ],
                      "text": "Ночью рядом: есть"
                    }
                  ],
                  "generated_by": "template"
                },
                "floor_from": 6,
                "hotel": {
                  "address": "ул. Платановая, 3",
                  "district": "khosta",
                  "id": "f8fb8162-484a-520b-a734-0a7d0315cd62",
                  "kind": "hotel",
                  "location": {
                    "lat": 43.515,
                    "lon": 39.865
                  },
                  "name": "Гостиница «Хоста Вью»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_khosta_view.jpg",
                    "url": "/media/photos/khosta-view.jpg"
                  },
                  "stars": 3,
                  "walk_back_min": 15,
                  "walk_to_sea_min": 9
                },
                "limits": [],
                "matches": [
                  {
                    "confidence": 0.0,
                    "need_index": 0,
                    "observation_ids": [],
                    "reason": "Нет данных: спросили отельера",
                    "status": "unknown"
                  },
                  {
                    "confidence": 0.73,
                    "need_index": 1,
                    "observation_ids": [
                      "b37548fc-b258-58f9-b760-654b5fb39f60"
                    ],
                    "reason": "Ночью тихо: трасса за домом",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.68,
                    "need_index": 2,
                    "observation_ids": [
                      "11c09355-cbc5-54a5-a2c8-66181ba7aaae"
                    ],
                    "reason": "Ночью рядом: есть",
                    "status": "yes"
                  }
                ],
                "score": 0.55,
                "side": "ЮЗ",
                "variant_id": "v6"
              },
              {
                "explanation": {
                  "claims": [
                    {
                      "observation_ids": [
                        "48d4a75f-c440-539a-b223-060718234c28"
                      ],
                      "text": "Ночью тихо: трасса за домом"
                    },
                    {
                      "observation_ids": [
                        "7a88d6b6-937e-540d-b945-5c264d7da1c7"
                      ],
                      "text": "Ночью рядом: есть"
                    }
                  ],
                  "generated_by": "template"
                },
                "floor_from": 7,
                "hotel": {
                  "address": "ул. Приморская, 9",
                  "district": "center",
                  "id": "e30ee3ba-c3ae-5cf1-acb1-5d70c7b6f0a4",
                  "kind": "hotel",
                  "location": {
                    "lat": 43.5868,
                    "lon": 39.7158
                  },
                  "name": "Отель «Кипарис»",
                  "photo": {
                    "author": "Участник Викисклада",
                    "license": "CC BY-SA 4.0",
                    "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                    "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_kiparis.jpg",
                    "url": "/media/photos/kiparis.jpg"
                  },
                  "stars": 3,
                  "walk_back_min": 8,
                  "walk_to_sea_min": 5
                },
                "limits": [],
                "matches": [
                  {
                    "confidence": 0.0,
                    "need_index": 0,
                    "observation_ids": [],
                    "reason": "Нет данных: спросили отельера",
                    "status": "unknown"
                  },
                  {
                    "confidence": 0.71,
                    "need_index": 1,
                    "observation_ids": [
                      "48d4a75f-c440-539a-b223-060718234c28"
                    ],
                    "reason": "Ночью тихо: трасса за домом",
                    "status": "yes"
                  },
                  {
                    "confidence": 0.66,
                    "need_index": 2,
                    "observation_ids": [
                      "7a88d6b6-937e-540d-b945-5c264d7da1c7"
                    ],
                    "reason": "Ночью рядом: есть",
                    "status": "yes"
                  }
                ],
                "score": 0.5,
                "side": "З",
                "variant_id": "v7"
              }
            ],
            "unknown_needs": []
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "dates": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateRange"
              },
              {
                "type": "null"
              }
            ]
          },
          "explanations_pending": {
            "description": "Версии объяснений от LLM придут по SSE",
            "title": "Explanations Pending",
            "type": "boolean"
          },
          "needs": {
            "items": {
              "$ref": "#/components/schemas/NeedOut"
            },
            "maxItems": 12,
            "title": "Needs",
            "type": "array"
          },
          "parser": {
            "$ref": "#/components/schemas/ParserMode"
          },
          "rejected": {
            "items": {
              "$ref": "#/components/schemas/RejectedVariant"
            },
            "maxItems": 200,
            "title": "Rejected",
            "type": "array"
          },
          "results": {
            "items": {
              "$ref": "#/components/schemas/Variant"
            },
            "maxItems": 100,
            "title": "Results",
            "type": "array"
          },
          "search_id": {
            "format": "uuid",
            "title": "Search Id",
            "type": "string"
          },
          "unconfirmed": {
            "description": "Не подтверждено",
            "items": {
              "$ref": "#/components/schemas/Variant"
            },
            "maxItems": 100,
            "title": "Unconfirmed",
            "type": "array"
          },
          "unknown_needs": {
            "description": "Пункты запроса без характеристики: «пока не знаем»",
            "items": {
              "maxLength": 120,
              "type": "string"
            },
            "maxItems": 10,
            "title": "Unknown Needs",
            "type": "array"
          }
        },
        "required": [
          "data_mode",
          "search_id",
          "parser",
          "needs",
          "unknown_needs",
          "dates",
          "results",
          "unconfirmed",
          "rejected",
          "explanations_pending"
        ],
        "title": "SearchResponse",
        "type": "object"
      },
      "ServiceStatus": {
        "additionalProperties": false,
        "description": "Что включено на стенде. Интерфейс показывает гостю и админу, подключён ли ИИ.",
        "examples": [
          {
            "data_mode": "real",
            "llm": "on"
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "llm": {
            "description": "on - ИИ (языковая модель) подключён: разбирает запросы гостей и ответы отельеров. off - ключа нет или ИИ выключен: разбираем правилами, результат тот же по смыслу, но проще",
            "enum": [
              "on",
              "off"
            ],
            "title": "Llm",
            "type": "string"
          }
        },
        "required": [
          "data_mode",
          "llm"
        ],
        "title": "ServiceStatus",
        "type": "object"
      },
      "SessionInfo": {
        "additionalProperties": false,
        "description": "Текущий пользователь или null, если сессии нет.",
        "examples": [
          {
            "data_mode": "real",
            "user": {
              "consents": [
                "memory"
              ],
              "display_name": "Гость (демо)",
              "hotel_ids": [],
              "id": "3e6d6986-ea53-573c-8997-a97c060ce78f",
              "is_anonymous": false,
              "is_demo": true,
              "role": "guest"
            }
          }
        ],
        "properties": {
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "user": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/User"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "data_mode",
          "user"
        ],
        "title": "SessionInfo",
        "type": "object"
      },
      "Side": {
        "additionalProperties": false,
        "description": "Сторона здания.",
        "examples": [
          {
            "azimuth_deg": 225.0,
            "blank_facade": null,
            "floors_count": 9,
            "length_m": 18.0,
            "rumb": "ЮЗ",
            "rumb_ambiguous": false
          }
        ],
        "properties": {
          "azimuth_deg": {
            "description": "Азимут от севера по часовой, градусы",
            "maximum": 360.0,
            "minimum": 0.0,
            "title": "Azimuth Deg",
            "type": "number"
          },
          "blank_facade": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "description": "Глухая стена, если известно",
            "title": "Blank Facade"
          },
          "floors_count": {
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floors Count",
            "type": "integer"
          },
          "length_m": {
            "maximum": 1000.0,
            "minimum": 0.0,
            "title": "Length M",
            "type": "number"
          },
          "rumb": {
            "$ref": "#/components/schemas/Rumb"
          },
          "rumb_ambiguous": {
            "description": "Нормаль ближе 3° к границе румбов",
            "title": "Rumb Ambiguous",
            "type": "boolean"
          }
        },
        "required": [
          "rumb",
          "azimuth_deg",
          "floors_count",
          "length_m",
          "rumb_ambiguous",
          "blank_facade"
        ],
        "title": "Side",
        "type": "object"
      },
      "SideGeometry": {
        "additionalProperties": false,
        "description": "Сторона на контуре здания: отрезок фасада-представителя.",
        "examples": [
          {
            "azimuth_deg": 225.0,
            "floors_count": 9,
            "length_m": 36.0,
            "rumb": "ЮЗ",
            "segment": [
              {
                "lat": 43.578866,
                "lon": 39.722974
              },
              {
                "lat": 43.578981,
                "lon": 39.722816
              }
            ]
          }
        ],
        "properties": {
          "azimuth_deg": {
            "description": "Азимут от севера по часовой, градусы",
            "maximum": 360.0,
            "minimum": 0.0,
            "title": "Azimuth Deg",
            "type": "number"
          },
          "floors_count": {
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floors Count",
            "type": "integer"
          },
          "length_m": {
            "maximum": 1000.0,
            "minimum": 0.0,
            "title": "Length M",
            "type": "number"
          },
          "rumb": {
            "$ref": "#/components/schemas/Rumb"
          },
          "segment": {
            "items": {
              "$ref": "#/components/schemas/GeoPoint"
            },
            "maxItems": 2,
            "minItems": 2,
            "title": "Segment",
            "type": "array"
          }
        },
        "required": [
          "rumb",
          "azimuth_deg",
          "length_m",
          "floors_count",
          "segment"
        ],
        "title": "SideGeometry",
        "type": "object"
      },
      "SourceChip": {
        "additionalProperties": false,
        "description": "Чип источника рядом с выводом: рельеф, здания, расчёт солнца.",
        "examples": [
          {
            "date": null,
            "label": "Рельеф Copernicus GLO-30",
            "source_id": "SRC-02"
          }
        ],
        "properties": {
          "date": {
            "anyOf": [
              {
                "format": "date",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Date"
          },
          "label": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Label",
            "type": "string"
          },
          "source_id": {
            "description": "ID источника",
            "maxLength": 6,
            "minLength": 6,
            "pattern": "^SRC-[0-9]{2}$",
            "title": "Source Id",
            "type": "string"
          }
        },
        "required": [
          "source_id",
          "label",
          "date"
        ],
        "title": "SourceChip",
        "type": "object"
      },
      "SourceStatus": {
        "description": "Состояние источника A6: загружен, работает с перебоями, проверка не прошла, ещё не загружали\nили подложка карты (её показывает браузер, в базу она не грузится).",
        "enum": [
          "ok",
          "degraded",
          "unavailable",
          "not_loaded",
          "map"
        ],
        "examples": [
          "ok"
        ],
        "title": "SourceStatus",
        "type": "string"
      },
      "SourceType": {
        "description": "Тип источника наблюдения.",
        "enum": [
          "computed",
          "osm_tag",
          "open_api",
          "hotelier",
          "simulator",
          "admin",
          "llm_text"
        ],
        "examples": [
          "computed"
        ],
        "title": "SourceType",
        "type": "string"
      },
      "SpecActivated": {
        "additionalProperties": false,
        "examples": [
          {
            "activated_at": "2026-10-23T09:00:00Z",
            "ch_id": "CH-BAKERY",
            "data_mode": "real",
            "status": "active",
            "version": 1
          }
        ],
        "properties": {
          "activated_at": {
            "format": "date-time",
            "title": "Activated At",
            "type": "string"
          },
          "ch_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Ch Id",
            "type": "string"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "status": {
            "$ref": "#/components/schemas/SpecStatus"
          },
          "version": {
            "maximum": 999.0,
            "minimum": 1.0,
            "title": "Version",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "ch_id",
          "version",
          "status",
          "activated_at"
        ],
        "title": "SpecActivated",
        "type": "object"
      },
      "SpecOrigin": {
        "description": "Встроенная спецификация из git или созданная на стенде.",
        "enum": [
          "builtin",
          "stand"
        ],
        "examples": [
          "builtin"
        ],
        "title": "SpecOrigin",
        "type": "string"
      },
      "SpecStatus": {
        "enum": [
          "draft",
          "active",
          "retired"
        ],
        "examples": [
          "draft"
        ],
        "title": "SpecStatus",
        "type": "string"
      },
      "SpecVersion": {
        "additionalProperties": false,
        "description": "Спецификация, ошибки проверки и объяснение выбора (SpecDraft).",
        "examples": [
          {
            "ch_id": "CH-BAKERY",
            "data_mode": "real",
            "dry_run": {
              "hotels": [
                {
                  "confidence": 0.82,
                  "confidence_label": "high",
                  "display": "Пекарня в 4 мин, открыта с 7:00",
                  "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
                  "name": "Гостиница «Бриз»"
                },
                {
                  "confidence": 0.75,
                  "confidence_label": "medium",
                  "display": "Пекарня в 5 мин, открыта с 7:00",
                  "hotel_id": "e30ee3ba-c3ae-5cf1-acb1-5d70c7b6f0a4",
                  "name": "Отель «Кипарис»"
                },
                {
                  "confidence": 0.68,
                  "confidence_label": "medium",
                  "display": "Пекарня в 6 мин, открыта с 7:00",
                  "hotel_id": "f597787a-31ae-5fcb-948a-f75388c093a9",
                  "name": "Гостевой дом «Магнолия»"
                },
                {
                  "confidence": 0.61,
                  "confidence_label": "medium",
                  "display": "Пекарня в 7 мин, открыта с 7:00",
                  "hotel_id": "43512fa7-447a-5f37-80fd-50173cedd2b6",
                  "name": "Апарт-отель «Платан»"
                },
                {
                  "confidence": 0.54,
                  "confidence_label": "medium",
                  "display": "Пекарня в 8 мин, открыта с 7:00",
                  "hotel_id": "0466edb1-1103-5a78-9983-12bd845aa81f",
                  "name": "Отель «Мацеста Хиллс»"
                }
              ],
              "run_id": "64d320cc-9e9a-58de-bd33-77c3318290c0",
              "state": "done"
            },
            "origin": "stand",
            "rationale": "Часы работы есть у 41% заведений OSM, поэтому без часов значение «неизвестно», а не догадка.",
            "spec": {
              "id": "CH-BAKERY",
              "scope": {
                "dates": false,
                "kind": "hotel"
              },
              "title": "Новая характеристика",
              "ttl_days": 30,
              "version": 1
            },
            "spec_yaml": "id: CH-BAKERY\nversion: 1\ntitle: Пекарня рядом с 7 утра\nquestion: Есть ли рядом пекарня, которая открыта с 7 утра?\nscope: {kind: hotel, dates: false}\nvalue_schema:\n  type: object\n  additionalProperties: false\n  properties:\n    nearest_min: {type: integer, minimum: 0, maximum: 60}\n    open_at_7: {type: boolean}\nmethod:\n  steps:\n    - id: bakeries\n      tool: osm.poi_nearby\n      params: {radius_m: 800, tags: [\"shop=bakery\"], with_walk: 10, open_check: {local_time: \"07:00\"}}\nttl_days: 30\n",
            "status": "draft",
            "validation_errors": [],
            "version": 1
          }
        ],
        "properties": {
          "ch_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Ch Id",
            "type": "string"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "dry_run": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DryRun"
              },
              {
                "type": "null"
              }
            ]
          },
          "origin": {
            "$ref": "#/components/schemas/SpecOrigin"
          },
          "rationale": {
            "maxLength": 2000,
            "minLength": 1,
            "title": "Rationale",
            "type": "string"
          },
          "spec": {
            "additionalProperties": true,
            "maxProperties": 40,
            "title": "Spec",
            "type": "object"
          },
          "spec_yaml": {
            "maxLength": 20000,
            "minLength": 1,
            "title": "Spec Yaml",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SpecStatus"
          },
          "validation_errors": {
            "items": {
              "maxLength": 300,
              "type": "string"
            },
            "maxItems": 50,
            "title": "Validation Errors",
            "type": "array"
          },
          "version": {
            "maximum": 999.0,
            "minimum": 1.0,
            "title": "Version",
            "type": "integer"
          }
        },
        "required": [
          "data_mode",
          "ch_id",
          "version",
          "status",
          "origin",
          "spec",
          "spec_yaml",
          "validation_errors",
          "rationale",
          "dry_run"
        ],
        "title": "SpecVersion",
        "type": "object"
      },
      "Suggestion": {
        "additionalProperties": false,
        "description": "Умный чип главного экрана (REQ-SRCH-007, 008).",
        "examples": [
          {
            "id": "chip-sunset",
            "label": "Закат из окна",
            "need": {
              "characteristic_id": "CH-SUNSET",
              "condition": {
                "op": "sea_on_dates",
                "value": null
              },
              "importance": "must",
              "source": "chip"
            },
            "source": "preset"
          }
        ],
        "properties": {
          "id": {
            "maxLength": 45,
            "minLength": 6,
            "pattern": "^chip-[a-z0-9-]{1,40}$",
            "title": "Id",
            "type": "string"
          },
          "label": {
            "maxLength": 60,
            "minLength": 1,
            "title": "Label",
            "type": "string"
          },
          "need": {
            "$ref": "#/components/schemas/NeedIn"
          },
          "source": {
            "$ref": "#/components/schemas/ChipSource"
          }
        },
        "required": [
          "id",
          "label",
          "need",
          "source"
        ],
        "title": "Suggestion",
        "type": "object"
      },
      "SuggestionsResponse": {
        "additionalProperties": false,
        "examples": [
          {
            "chips": [
              {
                "id": "chip-sunset",
                "label": "Закат из окна",
                "need": {
                  "characteristic_id": "CH-SUNSET",
                  "condition": {
                    "op": "sea_on_dates",
                    "value": null
                  },
                  "importance": "must",
                  "source": "chip"
                },
                "source": "preset"
              },
              {
                "id": "chip-quiet-night",
                "label": "Тихо ночью",
                "need": {
                  "characteristic_id": "CH-QUIET",
                  "condition": {
                    "op": "night_quiet",
                    "value": null
                  },
                  "importance": "must",
                  "source": "chip"
                },
                "source": "preset"
              },
              {
                "id": "chip-walk-flat",
                "label": "До моря без подъёма",
                "need": {
                  "characteristic_id": "CH-WALK-SEA",
                  "condition": {
                    "op": "no_steps",
                    "value": null
                  },
                  "importance": "should",
                  "source": "chip"
                },
                "source": "preset"
              },
              {
                "id": "chip-sea-view",
                "label": "Море из окна",
                "need": {
                  "characteristic_id": "CH-SEA-VIEW",
                  "condition": {
                    "op": "any",
                    "value": null
                  },
                  "importance": "must",
                  "source": "chip"
                },
                "source": "preset"
              },
              {
                "id": "chip-mountains",
                "label": "Вид на горы",
                "need": {
                  "characteristic_id": "CH-MOUNTAIN-VIEW",
                  "condition": {
                    "op": "any",
                    "value": null
                  },
                  "importance": "should",
                  "source": "chip"
                },
                "source": "popular"
              },
              {
                "id": "chip-kids",
                "label": "С детьми",
                "need": {
                  "characteristic_id": "CH-FAMILY",
                  "condition": {
                    "op": "with_kids",
                    "value": null
                  },
                  "importance": "should",
                  "source": "chip"
                },
                "source": "popular"
              },
              {
                "id": "chip-pets",
                "label": "С питомцем",
                "need": {
                  "characteristic_id": "CH-PETS",
                  "condition": {
                    "op": "pets_allowed",
                    "value": null
                  },
                  "importance": "should",
                  "source": "chip"
                },
                "source": "popular"
              },
              {
                "id": "chip-night-pharmacy",
                "label": "Аптека рядом ночью",
                "need": {
                  "characteristic_id": "CH-NIGHT",
                  "condition": {
                    "op": "pharmacy_24_7",
                    "value": null
                  },
                  "importance": "should",
                  "source": "chip"
                },
                "source": "popular"
              }
            ],
            "data_mode": "real"
          }
        ],
        "properties": {
          "chips": {
            "items": {
              "$ref": "#/components/schemas/Suggestion"
            },
            "maxItems": 12,
            "title": "Chips",
            "type": "array"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          }
        },
        "required": [
          "data_mode",
          "chips"
        ],
        "title": "SuggestionsResponse",
        "type": "object"
      },
      "SunPath": {
        "additionalProperties": false,
        "examples": [
          {
            "date": "2026-05-03",
            "points": [
              {
                "azimuth": 170.9,
                "elevation": 61.56,
                "time": "2026-05-03T09:00:00Z",
                "visible": true
              },
              {
                "azimuth": 181.1,
                "elevation": 61.82,
                "time": "2026-05-03T09:20:00Z",
                "visible": true
              },
              {
                "azimuth": 191.2,
                "elevation": 61.43,
                "time": "2026-05-03T09:40:00Z",
                "visible": true
              },
              {
                "azimuth": 200.9,
                "elevation": 60.43,
                "time": "2026-05-03T10:00:00Z",
                "visible": true
              },
              {
                "azimuth": 210.0,
                "elevation": 58.88,
                "time": "2026-05-03T10:20:00Z",
                "visible": true
              },
              {
                "azimuth": 218.2,
                "elevation": 56.85,
                "time": "2026-05-03T10:40:00Z",
                "visible": true
              },
              {
                "azimuth": 225.6,
                "elevation": 54.43,
                "time": "2026-05-03T11:00:00Z",
                "visible": true
              },
              {
                "azimuth": 232.2,
                "elevation": 51.7,
                "time": "2026-05-03T11:20:00Z",
                "visible": true
              },
              {
                "azimuth": 238.2,
                "elevation": 48.73,
                "time": "2026-05-03T11:40:00Z",
                "visible": true
              },
              {
                "azimuth": 243.5,
                "elevation": 45.57,
                "time": "2026-05-03T12:00:00Z",
                "visible": true
              },
              {
                "azimuth": 248.4,
                "elevation": 42.27,
                "time": "2026-05-03T12:20:00Z",
                "visible": true
              },
              {
                "azimuth": 252.9,
                "elevation": 38.85,
                "time": "2026-05-03T12:40:00Z",
                "visible": true
              },
              {
                "azimuth": 257.0,
                "elevation": 35.36,
                "time": "2026-05-03T13:00:00Z",
                "visible": true
              },
              {
                "azimuth": 260.9,
                "elevation": 31.81,
                "time": "2026-05-03T13:20:00Z",
                "visible": true
              },
              {
                "azimuth": 264.7,
                "elevation": 28.22,
                "time": "2026-05-03T13:40:00Z",
                "visible": true
              },
              {
                "azimuth": 268.2,
                "elevation": 24.6,
                "time": "2026-05-03T14:00:00Z",
                "visible": true
              },
              {
                "azimuth": 271.7,
                "elevation": 20.98,
                "time": "2026-05-03T14:20:00Z",
                "visible": true
              },
              {
                "azimuth": 275.0,
                "elevation": 17.37,
                "time": "2026-05-03T14:40:00Z",
                "visible": true
              },
              {
                "azimuth": 278.4,
                "elevation": 13.78,
                "time": "2026-05-03T15:00:00Z",
                "visible": true
              },
              {
                "azimuth": 281.7,
                "elevation": 10.21,
                "time": "2026-05-03T15:20:00Z",
                "visible": true
              },
              {
                "azimuth": 285.0,
                "elevation": 6.69,
                "time": "2026-05-03T15:40:00Z",
                "visible": true
              },
              {
                "azimuth": 288.4,
                "elevation": 3.23,
                "time": "2026-05-03T16:00:00Z",
                "visible": true
              },
              {
                "azimuth": 291.8,
                "elevation": -0.17,
                "time": "2026-05-03T16:20:00Z",
                "visible": false
              }
            ],
            "sunset": {
              "azimuth": 292.5,
              "over": "sea",
              "time": "2026-05-03T16:24:00Z"
            }
          }
        ],
        "properties": {
          "date": {
            "format": "date",
            "title": "Date",
            "type": "string"
          },
          "points": {
            "items": {
              "$ref": "#/components/schemas/SunPoint"
            },
            "maxItems": 200,
            "title": "Points",
            "type": "array"
          },
          "sunset": {
            "$ref": "#/components/schemas/Sunset"
          }
        },
        "required": [
          "date",
          "points",
          "sunset"
        ],
        "title": "SunPath",
        "type": "object"
      },
      "SunPoint": {
        "additionalProperties": false,
        "examples": [
          {
            "azimuth": 170.9,
            "elevation": 61.56,
            "time": "2026-05-03T09:00:00Z",
            "visible": true
          }
        ],
        "properties": {
          "azimuth": {
            "description": "Азимут от севера по часовой, градусы",
            "maximum": 360.0,
            "minimum": 0.0,
            "title": "Azimuth",
            "type": "number"
          },
          "elevation": {
            "maximum": 90.0,
            "minimum": -90.0,
            "title": "Elevation",
            "type": "number"
          },
          "time": {
            "format": "date-time",
            "title": "Time",
            "type": "string"
          },
          "visible": {
            "description": "Солнце выше горизонта точки обзора",
            "title": "Visible",
            "type": "boolean"
          }
        },
        "required": [
          "time",
          "azimuth",
          "elevation",
          "visible"
        ],
        "title": "SunPoint",
        "type": "object"
      },
      "Sunset": {
        "additionalProperties": false,
        "description": "Последний момент, когда солнце видно, и над чем оно садится.",
        "examples": [
          {
            "azimuth": 292.5,
            "over": "sea",
            "time": "2026-05-03T16:24:00Z"
          }
        ],
        "properties": {
          "azimuth": {
            "description": "Азимут от севера по часовой, градусы",
            "maximum": 360.0,
            "minimum": 0.0,
            "title": "Azimuth",
            "type": "number"
          },
          "over": {
            "$ref": "#/components/schemas/SunsetOver"
          },
          "time": {
            "format": "date-time",
            "title": "Time",
            "type": "string"
          }
        },
        "required": [
          "time",
          "azimuth",
          "over"
        ],
        "title": "Sunset",
        "type": "object"
      },
      "SunsetOver": {
        "description": "Куда садится солнце.",
        "enum": [
          "sea",
          "mountains",
          "buildings",
          "none"
        ],
        "examples": [
          "sea"
        ],
        "title": "SunsetOver",
        "type": "string"
      },
      "User": {
        "additionalProperties": false,
        "examples": [
          {
            "consents": [
              "memory"
            ],
            "display_name": "Гость (демо)",
            "hotel_ids": [],
            "id": "3e6d6986-ea53-573c-8997-a97c060ce78f",
            "is_anonymous": false,
            "is_demo": true,
            "role": "guest"
          }
        ],
        "properties": {
          "consents": {
            "items": {
              "$ref": "#/components/schemas/ConsentKind"
            },
            "maxItems": 2,
            "title": "Consents",
            "type": "array"
          },
          "display_name": {
            "maxLength": 80,
            "minLength": 1,
            "title": "Display Name",
            "type": "string"
          },
          "hotel_ids": {
            "description": "Отели отельера, у остальных пусто",
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 10,
            "title": "Hotel Ids",
            "type": "array"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "is_anonymous": {
            "title": "Is Anonymous",
            "type": "boolean"
          },
          "is_demo": {
            "title": "Is Demo",
            "type": "boolean"
          },
          "role": {
            "$ref": "#/components/schemas/Role"
          }
        },
        "required": [
          "id",
          "role",
          "display_name",
          "is_demo",
          "is_anonymous",
          "hotel_ids",
          "consents"
        ],
        "title": "User",
        "type": "object"
      },
      "ValueChange": {
        "additionalProperties": false,
        "description": "Как изменилось значение после запуска.",
        "examples": [
          {
            "after": "Закат над морем, апрель - август",
            "before": "Закат за здания",
            "characteristic_id": "CH-SUNSET",
            "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "scope_key": "floor:ЮЗ:5"
          }
        ],
        "properties": {
          "after": {
            "anyOf": [
              {
                "maxLength": 200,
                "minLength": 1,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "After"
          },
          "before": {
            "anyOf": [
              {
                "maxLength": 200,
                "minLength": 1,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Before"
          },
          "characteristic_id": {
            "description": "ID характеристики, CH-*",
            "maxLength": 43,
            "minLength": 5,
            "pattern": "^CH-[A-Z0-9-]{2,40}$",
            "title": "Characteristic Id",
            "type": "string"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "scope_key": {
            "description": "Ключ уровня значения: hotel, side:ЮЗ, floor:ЮЗ:5, room:401 или room:<uuid>, district:adler; с датами - плюс |2026-06-01..2026-06-14",
            "maxLength": 80,
            "minLength": 5,
            "pattern": "^(hotel|side:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ)|floor:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ):([1-9]|[1-3][0-9]|40)|room:([0-9A-Za-z-]{1,20}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|district:(center|matsesta|khosta|kudepsta|adler|imeretinka))(\\|[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\\.\\.[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]))?$",
            "title": "Scope Key",
            "type": "string"
          }
        },
        "required": [
          "hotel_id",
          "characteristic_id",
          "scope_key",
          "before",
          "after"
        ],
        "title": "ValueChange",
        "type": "object"
      },
      "ValueState": {
        "description": "Состояние итогового значения (resolved_values.state).",
        "enum": [
          "ok",
          "conflict",
          "unknown",
          "stale"
        ],
        "examples": [
          "ok"
        ],
        "title": "ValueState",
        "type": "string"
      },
      "Variant": {
        "additionalProperties": false,
        "description": "Вариант «отель + сторона + этаж».",
        "examples": [
          {
            "explanation": {
              "claims": [
                {
                  "observation_ids": [
                    "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
                  ],
                  "text": "Закат над морем с 3-го этажа, сторона ЮЗ"
                },
                {
                  "observation_ids": [
                    "39a55f35-0329-5c71-b687-483a2ed6c8a0"
                  ],
                  "text": "Ночью тихо: трасса за домом"
                },
                {
                  "observation_ids": [
                    "d8d773fc-576c-5524-8894-06520f25b5cd"
                  ],
                  "text": "Ночью рядом: есть"
                }
              ],
              "generated_by": "template"
            },
            "floor_from": 3,
            "hotel": {
              "address": "Курортный проспект, 18",
              "district": "center",
              "id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
              "kind": "hotel",
              "location": {
                "lat": 43.579,
                "lon": 39.723
              },
              "name": "Гостиница «Бриз»",
              "photo": {
                "author": "Участник Викисклада",
                "license": "CC BY-SA 4.0",
                "license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
                "page_url": "https://commons.wikimedia.org/wiki/File:Sochi_briz.jpg",
                "url": "/media/photos/briz.jpg"
              },
              "stars": 4,
              "walk_back_min": 11,
              "walk_to_sea_min": 7
            },
            "limits": [],
            "matches": [
              {
                "confidence": 0.88,
                "need_index": 0,
                "observation_ids": [
                  "1bc28e5c-a481-58d2-9505-2de53fa58a4f"
                ],
                "reason": "Закат над морем с 3-го этажа, сторона ЮЗ",
                "status": "yes"
              },
              {
                "confidence": 0.83,
                "need_index": 1,
                "observation_ids": [
                  "39a55f35-0329-5c71-b687-483a2ed6c8a0"
                ],
                "reason": "Ночью тихо: трасса за домом",
                "status": "yes"
              },
              {
                "confidence": 0.78,
                "need_index": 2,
                "observation_ids": [
                  "d8d773fc-576c-5524-8894-06520f25b5cd"
                ],
                "reason": "Ночью рядом: есть",
                "status": "yes"
              }
            ],
            "score": 0.94,
            "side": "ЮЗ",
            "variant_id": "v1"
          }
        ],
        "properties": {
          "explanation": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Explanation"
              },
              {
                "type": "null"
              }
            ]
          },
          "floor_from": {
            "anyOf": [
              {
                "description": "Этаж, 1-40",
                "maximum": 40.0,
                "minimum": 1.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Floor From"
          },
          "hotel": {
            "$ref": "#/components/schemas/HotelSummary"
          },
          "limits": {
            "description": "Ограничения варианта",
            "items": {
              "maxLength": 200,
              "minLength": 1,
              "type": "string"
            },
            "maxItems": 6,
            "title": "Limits",
            "type": "array"
          },
          "matches": {
            "items": {
              "$ref": "#/components/schemas/Match"
            },
            "maxItems": 12,
            "title": "Matches",
            "type": "array"
          },
          "score": {
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Score",
            "type": "number"
          },
          "side": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/Rumb"
              },
              {
                "type": "null"
              }
            ]
          },
          "variant_id": {
            "maxLength": 5,
            "minLength": 2,
            "pattern": "^v[0-9]{1,4}$",
            "title": "Variant Id",
            "type": "string"
          }
        },
        "required": [
          "variant_id",
          "hotel",
          "side",
          "floor_from",
          "score",
          "matches",
          "limits",
          "explanation"
        ],
        "title": "Variant",
        "type": "object"
      },
      "ViewResponse": {
        "additionalProperties": false,
        "description": "Панорама стороны и этажа на даты поездки.",
        "examples": [
          {
            "azimuth_from": 145.0,
            "azimuth_to": 305.0,
            "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.0,
                "sunset_over": "buildings"
              },
              {
                "floor": 2,
                "sea_sector_deg": 86.0,
                "sunset_over": "buildings"
              },
              {
                "floor": 3,
                "sea_sector_deg": 86.0,
                "sunset_over": "buildings"
              },
              {
                "floor": 4,
                "sea_sector_deg": 86.0,
                "sunset_over": "buildings"
              },
              {
                "floor": 5,
                "sea_sector_deg": 98.0,
                "sunset_over": "sea"
              },
              {
                "floor": 6,
                "sea_sector_deg": 98.0,
                "sunset_over": "sea"
              },
              {
                "floor": 7,
                "sea_sector_deg": 98.0,
                "sunset_over": "sea"
              },
              {
                "floor": 8,
                "sea_sector_deg": 98.0,
                "sunset_over": "sea"
              },
              {
                "floor": 9,
                "sea_sector_deg": 98.0,
                "sunset_over": "sea"
              }
            ],
            "horizon_deg": [
              0.32,
              0.32,
              0.33,
              0.33,
              0.34,
              0.34,
              0.35,
              0.35,
              0.36,
              0.36,
              0.37,
              0.38,
              0.38,
              0.39,
              0.4,
              0.41,
              0.42,
              0.42,
              0.43,
              0.44,
              0.45,
              0.46,
              0.47,
              0.48,
              0.49,
              0.5,
              0.51,
              0.52,
              0.53,
              0.54,
              0.55,
              0.56,
              0.57,
              0.58,
              0.59,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              6.42,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              -0.11,
              0.36,
              0.35,
              0.35,
              0.34,
              0.34,
              0.33,
              0.33,
              0.32,
              0.32
            ],
            "hotel_id": "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6",
            "mountain_mask": [
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false
            ],
            "obstacles": [
              {
                "azimuth_from": 285.0,
                "azimuth_to": 300.0,
                "building_id": "way/245601999",
                "distance_m": 85.0,
                "height_m": 12.0,
                "kind": "building"
              },
              {
                "azimuth_from": 228.0,
                "azimuth_to": 246.0,
                "building_id": "way/245602004",
                "distance_m": 120.0,
                "height_m": 27.0,
                "kind": "building"
              },
              {
                "azimuth_from": 20.0,
                "azimuth_to": 140.0,
                "building_id": null,
                "distance_m": 32000.0,
                "height_m": 2300.0,
                "kind": "terrain"
              }
            ],
            "sea_mask": [
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              true,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false,
              false
            ],
            "side": "ЮЗ",
            "sources": [
              {
                "date": null,
                "label": "Рельеф Copernicus GLO-30",
                "source_id": "SRC-02"
              },
              {
                "date": "2026-10-22",
                "label": "Здания OSM",
                "source_id": "SRC-01"
              }
            ],
            "step_deg": 1.0,
            "sun_paths": [
              {
                "date": "2026-05-03",
                "points": [
                  {
                    "azimuth": 170.9,
                    "elevation": 61.56,
                    "time": "2026-05-03T09:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 181.1,
                    "elevation": 61.82,
                    "time": "2026-05-03T09:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 191.2,
                    "elevation": 61.43,
                    "time": "2026-05-03T09:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 200.9,
                    "elevation": 60.43,
                    "time": "2026-05-03T10:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 210.0,
                    "elevation": 58.88,
                    "time": "2026-05-03T10:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 218.2,
                    "elevation": 56.85,
                    "time": "2026-05-03T10:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 225.6,
                    "elevation": 54.43,
                    "time": "2026-05-03T11:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 232.2,
                    "elevation": 51.7,
                    "time": "2026-05-03T11:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 238.2,
                    "elevation": 48.73,
                    "time": "2026-05-03T11:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 243.5,
                    "elevation": 45.57,
                    "time": "2026-05-03T12:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 248.4,
                    "elevation": 42.27,
                    "time": "2026-05-03T12:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 252.9,
                    "elevation": 38.85,
                    "time": "2026-05-03T12:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 257.0,
                    "elevation": 35.36,
                    "time": "2026-05-03T13:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 260.9,
                    "elevation": 31.81,
                    "time": "2026-05-03T13:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 264.7,
                    "elevation": 28.22,
                    "time": "2026-05-03T13:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 268.2,
                    "elevation": 24.6,
                    "time": "2026-05-03T14:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 271.7,
                    "elevation": 20.98,
                    "time": "2026-05-03T14:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 275.0,
                    "elevation": 17.37,
                    "time": "2026-05-03T14:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 278.4,
                    "elevation": 13.78,
                    "time": "2026-05-03T15:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 281.7,
                    "elevation": 10.21,
                    "time": "2026-05-03T15:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 285.0,
                    "elevation": 6.69,
                    "time": "2026-05-03T15:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 288.4,
                    "elevation": 3.23,
                    "time": "2026-05-03T16:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 291.8,
                    "elevation": -0.17,
                    "time": "2026-05-03T16:20:00Z",
                    "visible": false
                  }
                ],
                "sunset": {
                  "azimuth": 292.5,
                  "over": "sea",
                  "time": "2026-05-03T16:24:00Z"
                }
              },
              {
                "date": "2026-05-05",
                "points": [
                  {
                    "azimuth": 170.9,
                    "elevation": 62.15,
                    "time": "2026-05-05T09:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 181.2,
                    "elevation": 62.4,
                    "time": "2026-05-05T09:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 191.5,
                    "elevation": 62.01,
                    "time": "2026-05-05T09:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 201.4,
                    "elevation": 60.98,
                    "time": "2026-05-05T10:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 210.5,
                    "elevation": 59.4,
                    "time": "2026-05-05T10:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 218.8,
                    "elevation": 57.34,
                    "time": "2026-05-05T10:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 226.2,
                    "elevation": 54.9,
                    "time": "2026-05-05T11:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 232.9,
                    "elevation": 52.14,
                    "time": "2026-05-05T11:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 238.8,
                    "elevation": 49.15,
                    "time": "2026-05-05T11:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 244.1,
                    "elevation": 45.97,
                    "time": "2026-05-05T12:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 249.0,
                    "elevation": 42.65,
                    "time": "2026-05-05T12:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 253.5,
                    "elevation": 39.22,
                    "time": "2026-05-05T12:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 257.6,
                    "elevation": 35.72,
                    "time": "2026-05-05T13:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 261.5,
                    "elevation": 32.16,
                    "time": "2026-05-05T13:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 265.2,
                    "elevation": 28.57,
                    "time": "2026-05-05T13:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 268.7,
                    "elevation": 24.95,
                    "time": "2026-05-05T14:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 272.2,
                    "elevation": 21.33,
                    "time": "2026-05-05T14:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 275.5,
                    "elevation": 17.72,
                    "time": "2026-05-05T14:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 278.9,
                    "elevation": 14.13,
                    "time": "2026-05-05T15:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 282.2,
                    "elevation": 10.57,
                    "time": "2026-05-05T15:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 285.5,
                    "elevation": 7.06,
                    "time": "2026-05-05T15:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 288.8,
                    "elevation": 3.6,
                    "time": "2026-05-05T16:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 292.2,
                    "elevation": 0.21,
                    "time": "2026-05-05T16:20:00Z",
                    "visible": true
                  }
                ],
                "sunset": {
                  "azimuth": 293.6,
                  "over": "sea",
                  "time": "2026-05-05T16:28:00Z"
                }
              },
              {
                "date": "2026-05-07",
                "points": [
                  {
                    "azimuth": 170.8,
                    "elevation": 62.72,
                    "time": "2026-05-07T09:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 181.3,
                    "elevation": 62.97,
                    "time": "2026-05-07T09:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 191.8,
                    "elevation": 62.56,
                    "time": "2026-05-07T09:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 201.8,
                    "elevation": 61.52,
                    "time": "2026-05-07T10:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 211.0,
                    "elevation": 59.91,
                    "time": "2026-05-07T10:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 219.4,
                    "elevation": 57.82,
                    "time": "2026-05-07T10:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 226.8,
                    "elevation": 55.35,
                    "time": "2026-05-07T11:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 233.5,
                    "elevation": 52.57,
                    "time": "2026-05-07T11:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 239.4,
                    "elevation": 49.56,
                    "time": "2026-05-07T11:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 244.7,
                    "elevation": 46.36,
                    "time": "2026-05-07T12:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 249.6,
                    "elevation": 43.02,
                    "time": "2026-05-07T12:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 254.0,
                    "elevation": 39.59,
                    "time": "2026-05-07T12:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 258.2,
                    "elevation": 36.07,
                    "time": "2026-05-07T13:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 262.0,
                    "elevation": 32.51,
                    "time": "2026-05-07T13:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 265.7,
                    "elevation": 28.91,
                    "time": "2026-05-07T13:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 269.2,
                    "elevation": 25.29,
                    "time": "2026-05-07T14:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 272.7,
                    "elevation": 21.68,
                    "time": "2026-05-07T14:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 276.0,
                    "elevation": 18.07,
                    "time": "2026-05-07T14:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 279.3,
                    "elevation": 14.48,
                    "time": "2026-05-07T15:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 282.6,
                    "elevation": 10.93,
                    "time": "2026-05-07T15:20:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 285.9,
                    "elevation": 7.42,
                    "time": "2026-05-07T15:40:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 289.3,
                    "elevation": 3.97,
                    "time": "2026-05-07T16:00:00Z",
                    "visible": true
                  },
                  {
                    "azimuth": 292.7,
                    "elevation": 0.59,
                    "time": "2026-05-07T16:20:00Z",
                    "visible": true
                  }
                ],
                "sunset": {
                  "azimuth": 294.4,
                  "over": "sea",
                  "time": "2026-05-07T16:30:00Z"
                }
              }
            ],
            "verdict": "Сторона ЮЗ (юго-западная), 5-й этаж, 3 мая. Море видно в секторе 180°-296°. Закат в море около 19:24. На 4-м этаже и ниже часть моря закрывает соседний дом."
          }
        ],
        "properties": {
          "azimuth_from": {
            "description": "Азимут от севера по часовой, градусы",
            "maximum": 360.0,
            "minimum": 0.0,
            "title": "Azimuth From",
            "type": "number"
          },
          "azimuth_to": {
            "description": "Больше 360 при переходе через север",
            "maximum": 720.0,
            "minimum": 0.0,
            "title": "Azimuth To",
            "type": "number"
          },
          "confidence": {
            "description": "Уверенность от 0 до 1",
            "maximum": 1.0,
            "minimum": 0.0,
            "title": "Confidence",
            "type": "number"
          },
          "confidence_label": {
            "$ref": "#/components/schemas/ConfidenceLabel"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "dates": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/DateRange"
              },
              {
                "type": "null"
              }
            ]
          },
          "floor": {
            "description": "Этаж, 1-40",
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floor",
            "type": "integer"
          },
          "floors_count": {
            "maximum": 40.0,
            "minimum": 1.0,
            "title": "Floors Count",
            "type": "integer"
          },
          "floors_summary": {
            "items": {
              "$ref": "#/components/schemas/FloorSummary"
            },
            "maxItems": 40,
            "title": "Floors Summary",
            "type": "array"
          },
          "horizon_deg": {
            "description": "Угол горизонта, градусы",
            "items": {
              "type": "number"
            },
            "maxItems": 321,
            "minItems": 2,
            "title": "Horizon Deg",
            "type": "array"
          },
          "hotel_id": {
            "format": "uuid",
            "title": "Hotel Id",
            "type": "string"
          },
          "mountain_mask": {
            "items": {
              "type": "boolean"
            },
            "maxItems": 321,
            "minItems": 2,
            "title": "Mountain Mask",
            "type": "array"
          },
          "obstacles": {
            "items": {
              "$ref": "#/components/schemas/Obstacle"
            },
            "maxItems": 64,
            "title": "Obstacles",
            "type": "array"
          },
          "sea_mask": {
            "items": {
              "type": "boolean"
            },
            "maxItems": 321,
            "minItems": 2,
            "title": "Sea Mask",
            "type": "array"
          },
          "side": {
            "$ref": "#/components/schemas/Rumb"
          },
          "sources": {
            "items": {
              "$ref": "#/components/schemas/SourceChip"
            },
            "maxItems": 6,
            "title": "Sources",
            "type": "array"
          },
          "step_deg": {
            "description": "1° из снимка или 0,5° после пересчёта",
            "maximum": 1.0,
            "minimum": 0.5,
            "title": "Step Deg",
            "type": "number"
          },
          "sun_paths": {
            "items": {
              "$ref": "#/components/schemas/SunPath"
            },
            "maxItems": 3,
            "title": "Sun Paths",
            "type": "array"
          },
          "verdict": {
            "description": "Вывод 1-3 предложения строго по данным",
            "maxLength": 600,
            "minLength": 1,
            "title": "Verdict",
            "type": "string"
          }
        },
        "required": [
          "data_mode",
          "hotel_id",
          "side",
          "floor",
          "floors_count",
          "dates",
          "azimuth_from",
          "azimuth_to",
          "step_deg",
          "horizon_deg",
          "sea_mask",
          "mountain_mask",
          "sun_paths",
          "obstacles",
          "floors_summary",
          "verdict",
          "confidence",
          "confidence_label",
          "sources"
        ],
        "title": "ViewResponse",
        "type": "object"
      },
      "ViewpointState": {
        "additionalProperties": false,
        "examples": [
          {
            "sea_sector_deg": 62.0,
            "sunset_over": "sea"
          }
        ],
        "properties": {
          "sea_sector_deg": {
            "maximum": 160.0,
            "minimum": 0.0,
            "title": "Sea Sector Deg",
            "type": "number"
          },
          "sunset_over": {
            "$ref": "#/components/schemas/SunsetOver"
          }
        },
        "required": [
          "sea_sector_deg",
          "sunset_over"
        ],
        "title": "ViewpointState",
        "type": "object"
      },
      "VoiceLatency": {
        "additionalProperties": false,
        "examples": [
          {
            "p50_ms": 1150,
            "p90_ms": 1900
          }
        ],
        "properties": {
          "p50_ms": {
            "maximum": 60000.0,
            "minimum": 0.0,
            "title": "P50 Ms",
            "type": "integer"
          },
          "p90_ms": {
            "maximum": 60000.0,
            "minimum": 0.0,
            "title": "P90 Ms",
            "type": "integer"
          }
        },
        "required": [
          "p50_ms",
          "p90_ms"
        ],
        "title": "VoiceLatency",
        "type": "object"
      },
      "VoiceLevel": {
        "description": "Уровень лестницы отказов голоса.",
        "enum": [
          "realtime",
          "cascade",
          "text"
        ],
        "examples": [
          "realtime"
        ],
        "title": "VoiceLevel",
        "type": "string"
      },
      "VoiceMode": {
        "enum": [
          "search",
          "dictation"
        ],
        "examples": [
          "search"
        ],
        "title": "VoiceMode",
        "type": "string"
      },
      "VoiceSession": {
        "additionalProperties": false,
        "description": "Голосовая сессия: одноразовый токен шлётся первым сообщением в WS, не в адресе.",
        "examples": [
          {
            "audio": {
              "channels": 1,
              "encoding": "pcm16",
              "frame_ms": 20,
              "sample_rate_hz": 16000
            },
            "data_mode": "real",
            "level": "text",
            "level_reason": "Голосовой мост ещё не подключён: заглушка волны 1",
            "mode": "search",
            "session_id": "2f1932f7-d6f1-5b7f-a9d2-6015c7884636",
            "token": "example-voice-token-not-a-secret-000000",
            "token_expires_at": "2026-10-23T09:01:00Z",
            "ws_path": "/api/v1/voice/ws"
          }
        ],
        "properties": {
          "audio": {
            "$ref": "#/components/schemas/AudioFormat"
          },
          "data_mode": {
            "$ref": "#/components/schemas/DataMode"
          },
          "level": {
            "$ref": "#/components/schemas/VoiceLevel"
          },
          "level_reason": {
            "anyOf": [
              {
                "maxLength": 200,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Level Reason"
          },
          "mode": {
            "$ref": "#/components/schemas/VoiceMode"
          },
          "session_id": {
            "format": "uuid",
            "title": "Session Id",
            "type": "string"
          },
          "token": {
            "maxLength": 128,
            "minLength": 32,
            "title": "Token",
            "type": "string"
          },
          "token_expires_at": {
            "format": "date-time",
            "title": "Token Expires At",
            "type": "string"
          },
          "ws_path": {
            "const": "/api/v1/voice/ws",
            "title": "Ws Path",
            "type": "string"
          }
        },
        "required": [
          "data_mode",
          "session_id",
          "token",
          "token_expires_at",
          "ws_path",
          "mode",
          "audio",
          "level",
          "level_reason"
        ],
        "title": "VoiceSession",
        "type": "object"
      },
      "VoiceSessionIn": {
        "additionalProperties": false,
        "examples": [
          {
            "mode": "search",
            "question_id": null
          }
        ],
        "properties": {
          "mode": {
            "$ref": "#/components/schemas/VoiceMode"
          },
          "question_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Вопрос H3 для режима диктовки",
            "title": "Question Id"
          }
        },
        "required": [
          "mode"
        ],
        "title": "VoiceSessionIn",
        "type": "object"
      }
    }
  },
  "info": {
    "description": "HTTP API О!кна: поиск отелей по запросу своими словами, факты об отеле с источником и уверенностью, вид из окна, кабинеты отельера и админа. Все ответы JSON в UTF-8, время в UTC. Ошибки в формате application/problem+json (RFC 9457). Вход: POST /api/v1/auth/demo-login ставит cookie сессии; методы с x-access guest, hotelier или admin требуют её, x-access public открыты без входа. Поле data_mode в каждом ответе: real значит расчёт по данным.",
    "title": "О!кно API",
    "version": "0.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/v1/admin/audit": {
      "get": {
        "operationId": "listAdminAudit",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "-ts",
              "enum": [
                "-ts",
                "ts"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "actor_kind",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/ActorKind"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Actor Kind"
            }
          },
          {
            "in": "query",
            "name": "action",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "maxLength": 60,
                  "minLength": 1,
                  "pattern": "^[a-z_.]{1,60}$",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Action"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Журнал аудита",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/characteristics": {
      "get": {
        "operationId": "listAdminCharacteristics",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "id",
              "enum": [
                "id",
                "-id",
                "priority",
                "-priority",
                "coverage",
                "-coverage"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/SpecStatus"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Status"
            }
          },
          {
            "in": "query",
            "name": "priority",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/Priority"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Priority"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CharacteristicPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Реестр характеристик и версий",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      },
      "post": {
        "description": "AG-PLAN собирает спецификацию. Готовая версия - в журнале запуска (result).",
        "operationId": "draftAdminCharacteristic",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CharacteristicDraftIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunAccepted"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Черновик спецификации из текста",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/characteristics/{ch_id}/versions/{version}": {
      "get": {
        "operationId": "getAdminCharacteristicVersion",
        "parameters": [
          {
            "description": "ID характеристики",
            "in": "path",
            "name": "ch_id",
            "required": true,
            "schema": {
              "description": "ID характеристики",
              "examples": [
                "CH-SUNSET"
              ],
              "maxLength": 43,
              "minLength": 5,
              "pattern": "^CH-[A-Z0-9-]{2,40}$",
              "title": "Ch Id",
              "type": "string"
            }
          },
          {
            "description": "Версия спецификации",
            "in": "path",
            "name": "version",
            "required": true,
            "schema": {
              "description": "Версия спецификации",
              "examples": [
                1
              ],
              "maximum": 999,
              "minimum": 1,
              "title": "Version",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SpecVersion"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Спецификация, ошибки проверки и объяснение выбора",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/characteristics/{ch_id}/versions/{version}/activate": {
      "post": {
        "description": "Версия с ошибками проверки не включается: 409.",
        "operationId": "activateAdminCharacteristicVersion",
        "parameters": [
          {
            "description": "ID характеристики",
            "in": "path",
            "name": "ch_id",
            "required": true,
            "schema": {
              "description": "ID характеристики",
              "examples": [
                "CH-SUNSET"
              ],
              "maxLength": 43,
              "minLength": 5,
              "pattern": "^CH-[A-Z0-9-]{2,40}$",
              "title": "Ch Id",
              "type": "string"
            }
          },
          {
            "description": "Версия спецификации",
            "in": "path",
            "name": "version",
            "required": true,
            "schema": {
              "description": "Версия спецификации",
              "examples": [
                1
              ],
              "maximum": 999,
              "minimum": 1,
              "title": "Version",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SpecActivated"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Включить версию",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/characteristics/{ch_id}/versions/{version}/dry-run": {
      "post": {
        "description": "Наблюдения пишутся в набор dry_run:<ch>@<версия> и не попадают в поиск.",
        "operationId": "dryRunAdminCharacteristicVersion",
        "parameters": [
          {
            "description": "ID характеристики",
            "in": "path",
            "name": "ch_id",
            "required": true,
            "schema": {
              "description": "ID характеристики",
              "examples": [
                "CH-SUNSET"
              ],
              "maxLength": 43,
              "minLength": 5,
              "pattern": "^CH-[A-Z0-9-]{2,40}$",
              "title": "Ch Id",
              "type": "string"
            }
          },
          {
            "description": "Версия спецификации",
            "in": "path",
            "name": "version",
            "required": true,
            "schema": {
              "description": "Версия спецификации",
              "examples": [
                1
              ],
              "maximum": 999,
              "minimum": 1,
              "title": "Version",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunAccepted"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Пробный прогон на 5 отелях",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/conflicts": {
      "get": {
        "operationId": "listAdminConflicts",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "-created_at",
              "enum": [
                "-created_at",
                "created_at",
                "characteristic_id",
                "-characteristic_id"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/ConflictState"
                },
                {
                  "type": "null"
                }
              ],
              "title": "State"
            }
          },
          {
            "in": "query",
            "name": "characteristic_id",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "description": "ID характеристики, CH-*",
                  "maxLength": 43,
                  "minLength": 5,
                  "pattern": "^CH-[A-Z0-9-]{2,40}$",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Characteristic Id"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Очередь конфликтов",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/conflicts/{conflict_id}/resolve": {
      "post": {
        "description": "Принять значение, спросить отельера или отметить неизвестным. Решение - в аудит.",
        "operationId": "resolveAdminConflict",
        "parameters": [
          {
            "description": "ID конфликта",
            "in": "path",
            "name": "conflict_id",
            "required": true,
            "schema": {
              "description": "ID конфликта",
              "examples": [
                "531809e2-a4c0-503c-9c1e-c6898ed7c409"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Conflict Id",
              "type": "string"
            }
          },
          {
            "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
              "examples": [
                "4f1c2a9e-booking-0001"
              ],
              "maxLength": 64,
              "minLength": 8,
              "pattern": "^[A-Za-z0-9_-]{8,64}$",
              "title": "Idempotency-Key",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "$ref": "#/components/schemas/AcceptObservationIn"
                  },
                  {
                    "$ref": "#/components/schemas/AskHotelierIn"
                  },
                  {
                    "$ref": "#/components/schemas/MarkUnknownIn"
                  }
                ],
                "title": "Body"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConflictResolved"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Разрешить конфликт",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/hotels": {
      "get": {
        "operationId": "listAdminHotels",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "name",
              "enum": [
                "name",
                "-name",
                "coverage",
                "-coverage",
                "conflicts",
                "-conflicts",
                "updated_at",
                "-updated_at"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "segment",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/AdminHotelSegment"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Segment"
            }
          },
          {
            "in": "query",
            "name": "district",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/District"
                },
                {
                  "type": "null"
                }
              ],
              "title": "District"
            }
          },
          {
            "description": "Поиск по названию",
            "in": "query",
            "name": "q",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "maxLength": 100,
                  "minLength": 1,
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Поиск по названию",
              "title": "Q"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminHotelPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Таблица отелей: покрытие, конфликты, устаревшее",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/hotels/{hotel_id}/recompute": {
      "post": {
        "description": "202 и ID запуска. Ход работы - SSE /admin/runs/{run_id}/events.",
        "operationId": "recomputeAdminHotel",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunAccepted"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Пересобрать отель",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/metrics": {
      "get": {
        "operationId": "getAdminMetrics",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Metrics"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Метрики качества",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/overview": {
      "get": {
        "operationId": "getAdminOverview",
        "parameters": [
          {
            "in": "query",
            "name": "period",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/OverviewPeriod",
              "default": "24h"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Overview"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Пульт: KPI, внимание, агенты, события",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/runs": {
      "get": {
        "operationId": "listAdminRuns",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "-started_at",
              "enum": [
                "-started_at",
                "started_at",
                "kind",
                "-kind",
                "state",
                "-state"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "kind",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/RunKind"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Kind"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/RunState"
                },
                {
                  "type": "null"
                }
              ],
              "title": "State"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Запуски агентов",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      },
      "post": {
        "description": "Сбор, пересчёт, «Перепроверить устаревшие» (stale_only) или обновление источника.",
        "operationId": "createAdminRun",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunCreateIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunAccepted"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Новый запуск",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/runs/{run_id}": {
      "get": {
        "operationId": "getAdminRun",
        "parameters": [
          {
            "description": "ID запуска агента",
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "description": "ID запуска агента",
              "examples": [
                "11176bcc-8d14-58b7-9c4f-6a655b8b9578"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Run Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunDetail"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Журнал запуска",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/runs/{run_id}/events": {
      "get": {
        "operationId": "streamAdminRunEvents",
        "parameters": [
          {
            "description": "ID запуска агента",
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "description": "ID запуска агента",
              "examples": [
                "11176bcc-8d14-58b7-9c4f-6a655b8b9578"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Run Id",
              "type": "string"
            }
          },
          {
            "description": "Продолжить поток после этого события",
            "in": "header",
            "name": "Last-Event-ID",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "maximum": 10000000,
                  "minimum": 0,
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Продолжить поток после этого события",
              "title": "Last-Event-Id"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/event-stream": {
                "example": "event: run_event\nid: 1\ndata: {\"data\":{\"step\":1},\"data_mode\":\"mock\",\"id\":1,\"level\":\"info\",\"message\":\"Шаг 1 из 3: профиль горизонта для 4 сторон\",\"run_id\":\"d473b344-6128-5adc-8f72-36c89bc4e483\",\"ts\":\"2026-10-23T08:30:00Z\",\"type\":\"step\"}\n\n",
                "schema": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "oneOf": [
                        {
                          "properties": {
                            "data": {
                              "contentMediaType": "application/json",
                              "contentSchema": {
                                "$ref": "#/components/schemas/RunEventMessage"
                              },
                              "type": "string"
                            },
                            "event": {
                              "const": "run_event",
                              "type": "string"
                            },
                            "id": {
                              "maxLength": 20,
                              "type": "string"
                            },
                            "retry": {
                              "minimum": 0,
                              "type": "integer"
                            }
                          },
                          "required": [
                            "event",
                            "data"
                          ],
                          "type": "object"
                        }
                      ]
                    }
                  ],
                  "description": "Поток Server-Sent Events. Событие - объект event, id, data. События - в x-sse-events"
                },
                "x-sse-events": {
                  "run_event": {
                    "$ref": "#/components/schemas/RunEventMessage"
                  }
                }
              }
            },
            "description": "События run_event, повторное подключение по Last-Event-ID"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Ход работы запуска (SSE)",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/sandbox/scenarios": {
      "get": {
        "operationId": "listSandboxScenarios",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "-created_at",
              "enum": [
                "-created_at",
                "created_at"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/SandboxState"
                },
                {
                  "type": "null"
                }
              ],
              "title": "State"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Сценарии песочницы",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      },
      "post": {
        "description": "Пересчёт идёт в набор sandbox:<сценарий>, боевые данные не меняются.",
        "operationId": "createSandboxScenario",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SandboxScenarioIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SandboxAccepted"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Поставить дом и пересчитать соседей",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/sandbox/scenarios/{scenario_id}": {
      "delete": {
        "operationId": "deleteSandboxScenario",
        "parameters": [
          {
            "description": "ID сценария песочницы",
            "in": "path",
            "name": "scenario_id",
            "required": true,
            "schema": {
              "description": "ID сценария песочницы",
              "examples": [
                "4cad8456-2fa8-5cff-9579-5ab0100aced3"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Scenario Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Сценарий удалён"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Удалить сценарий",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/sandbox/scenarios/{scenario_id}/apply": {
      "post": {
        "description": "Боевые значения меняются только после подтверждения, отельеры получают уведомление.",
        "operationId": "applySandboxScenario",
        "parameters": [
          {
            "description": "ID сценария песочницы",
            "in": "path",
            "name": "scenario_id",
            "required": true,
            "schema": {
              "description": "ID сценария песочницы",
              "examples": [
                "4cad8456-2fa8-5cff-9579-5ab0100aced3"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Scenario Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunAccepted"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Принять сценарий как реальное изменение",
        "tags": [
          "admin-knowledge"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/simulator/questions/{question_id}/answer": {
      "post": {
        "description": "AG-SIM отвечает в отдельном контексте. Черновик ответа появится в H3.",
        "operationId": "simulateHotelierAnswer",
        "parameters": [
          {
            "description": "ID вопроса агента",
            "in": "path",
            "name": "question_id",
            "required": true,
            "schema": {
              "description": "ID вопроса агента",
              "examples": [
                "41c93f91-182f-546d-ae1d-ea750b9ab5d9"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Question Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunAccepted"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Ответ за отельера через симулятор",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/admin/sources": {
      "get": {
        "operationId": "listAdminSources",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "id",
              "enum": [
                "id",
                "-id",
                "name",
                "-name",
                "data_date",
                "-data_date"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/SourceStatus"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Status"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataSourcePage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Источники данных",
        "tags": [
          "admin-ops"
        ],
        "x-access": "admin"
      }
    },
    "/api/v1/auth/demo-login": {
      "post": {
        "description": "Только при DEMO_MODE=1, иначе 404. Создаёт пользователя на сессию браузера и ставит __Host-sid.",
        "operationId": "demoLogin",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DemoLoginIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionInfo"
                }
              }
            },
            "description": "Successful Response"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Вход по роли одной кнопкой",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/auth/logout": {
      "post": {
        "operationId": "logout",
        "responses": {
          "204": {
            "description": "Сессия удалена",
            "headers": {
              "Clear-Site-Data": {
                "description": "Очистка данных сайта",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Выход",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/auth/me": {
      "get": {
        "description": "user: null, если сессии нет. Заглушка отвечает демо-гостем.",
        "operationId": "getCurrentUser",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionInfo"
                }
              }
            },
            "description": "Successful Response"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Текущий пользователь",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/bookings": {
      "post": {
        "description": "Без оплаты и цен. Гость получает анонимную сессию при первой брони.",
        "operationId": "createBooking",
        "parameters": [
          {
            "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
              "examples": [
                "4f1c2a9e-booking-0001"
              ],
              "maxLength": 64,
              "minLength": 8,
              "pattern": "^[A-Za-z0-9_-]{8,64}$",
              "title": "Idempotency-Key",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Booking"
                }
              }
            },
            "description": "Мок-бронь создана",
            "headers": {
              "Set-Cookie": {
                "description": "__Host-sid: анонимная сессия гостя, если её ещё не было",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Мок-бронь варианта",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/compare": {
      "post": {
        "operationId": "compareVariants",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompareRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompareResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Сравнение вариантов",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/health": {
      "get": {
        "description": "Процесс жив. База не проверяется.",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Живость",
        "tags": [
          "service"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/hotelier/answers/{answer_id}/confirm": {
      "post": {
        "description": "Только после подтверждения значения попадают в поиск.",
        "operationId": "confirmHotelierAnswer",
        "parameters": [
          {
            "description": "ID черновика ответа",
            "in": "path",
            "name": "answer_id",
            "required": true,
            "schema": {
              "description": "ID черновика ответа",
              "examples": [
                "5c34db89-564b-5888-b1c7-22b9e65cbe40"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Answer Id",
              "type": "string"
            }
          },
          {
            "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
              "examples": [
                "4f1c2a9e-booking-0001"
              ],
              "maxLength": 64,
              "minLength": 8,
              "pattern": "^[A-Za-z0-9_-]{8,64}$",
              "title": "Idempotency-Key",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnswerConfirmIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnswerConfirmed"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Подтвердить черновик ответа",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      }
    },
    "/api/v1/hotelier/hotels": {
      "get": {
        "operationId": "listHotelierHotels",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "name",
              "enum": [
                "name",
                "-name",
                "open_questions",
                "-open_questions"
              ],
              "title": "Sort",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HotelierHotelPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Свои отели со сводкой",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      }
    },
    "/api/v1/hotelier/hotels/{hotel_id}/demand": {
      "get": {
        "operationId": "getHotelierDemand",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "period_days",
            "required": false,
            "schema": {
              "default": 7,
              "enum": [
                7,
                30
              ],
              "title": "Period Days",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Demand"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Что о вас ищут",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      }
    },
    "/api/v1/hotelier/hotels/{hotel_id}/facts": {
      "get": {
        "operationId": "listHotelierFacts",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          },
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "characteristic_id",
              "enum": [
                "characteristic_id",
                "-characteristic_id",
                "confidence",
                "-confidence",
                "valid_until",
                "-valid_until",
                "observed_at",
                "-observed_at"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "description": "Сегменты H2: ждут, конфликты, устарели",
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/FactFilter"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Сегменты H2: ждут, конфликты, устарели",
              "title": "State"
            }
          },
          {
            "in": "query",
            "name": "characteristic_id",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "description": "ID характеристики, CH-*",
                  "maxLength": 43,
                  "minLength": 5,
                  "pattern": "^CH-[A-Z0-9-]{2,40}$",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Characteristic Id"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HotelierFactPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Что система знает",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      }
    },
    "/api/v1/hotelier/hotels/{hotel_id}/facts/{fact_id}/confirm": {
      "post": {
        "operationId": "confirmHotelierFact",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          },
          {
            "description": "ID значения из H2",
            "in": "path",
            "name": "fact_id",
            "required": true,
            "schema": {
              "description": "ID значения из H2",
              "examples": [
                "2478e67b-904f-56d3-91ac-136b4382bb2b"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Fact Id",
              "type": "string"
            }
          },
          {
            "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
              "examples": [
                "4f1c2a9e-booking-0001"
              ],
              "maxLength": 64,
              "minLength": 8,
              "pattern": "^[A-Za-z0-9_-]{8,64}$",
              "title": "Idempotency-Key",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FactActionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Подтвердить значение",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      }
    },
    "/api/v1/hotelier/hotels/{hotel_id}/facts/{fact_id}/correct": {
      "post": {
        "description": "Создаёт наблюдение hotelier. При расхождении с расчётом - конфликт.",
        "operationId": "correctHotelierFact",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          },
          {
            "description": "ID значения из H2",
            "in": "path",
            "name": "fact_id",
            "required": true,
            "schema": {
              "description": "ID значения из H2",
              "examples": [
                "2478e67b-904f-56d3-91ac-136b4382bb2b"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Fact Id",
              "type": "string"
            }
          },
          {
            "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
            "in": "header",
            "name": "Idempotency-Key",
            "required": true,
            "schema": {
              "description": "Ключ идемпотентности: повтор с тем же ключом не создаёт второй объект",
              "examples": [
                "4f1c2a9e-booking-0001"
              ],
              "maxLength": 64,
              "minLength": 8,
              "pattern": "^[A-Za-z0-9_-]{8,64}$",
              "title": "Idempotency-Key",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FactCorrectionIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FactActionResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Исправить значение",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      }
    },
    "/api/v1/hotelier/hotels/{hotel_id}/rooms": {
      "get": {
        "operationId": "getHotelierRooms",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rooms"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Номера и стороны",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      },
      "put": {
        "operationId": "putHotelierRooms",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RoomsIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Rooms"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Привязать номера к сторонам",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      }
    },
    "/api/v1/hotelier/questions": {
      "get": {
        "operationId": "listHotelierQuestions",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "-priority",
              "enum": [
                "-priority",
                "priority",
                "-created_at",
                "created_at"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/QuestionState"
                },
                {
                  "type": "null"
                }
              ],
              "title": "State"
            }
          },
          {
            "description": "Только вопросы этого отеля",
            "in": "query",
            "name": "hotel_id",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "format": "uuid",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Только вопросы этого отеля",
              "title": "Hotel Id"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QuestionPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Лента вопросов агента",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      }
    },
    "/api/v1/hotelier/questions/{question_id}/answers": {
      "post": {
        "description": "Текст до 4000 символов или диктовка с from_audio. Результат - черновик «Вот что я понял».",
        "operationId": "createHotelierAnswer",
        "parameters": [
          {
            "description": "ID вопроса агента",
            "in": "path",
            "name": "question_id",
            "required": true,
            "schema": {
              "description": "ID вопроса агента",
              "examples": [
                "41c93f91-182f-546d-ae1d-ea750b9ab5d9"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Question Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnswerIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnswerDraft"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Конфликт состояния или повтор с другим телом"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Ответить на вопрос",
        "tags": [
          "hotelier"
        ],
        "x-access": "hotelier"
      }
    },
    "/api/v1/hotels": {
      "get": {
        "description": "Рамка карты (west, south, east, north), район и пагинация.",
        "operationId": "listHotels",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "name",
              "enum": [
                "name",
                "-name",
                "district",
                "-district",
                "stars",
                "-stars"
              ],
              "title": "Sort",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "district",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/District"
                },
                {
                  "type": "null"
                }
              ],
              "title": "District"
            }
          },
          {
            "description": "Рамка карты: запад",
            "in": "query",
            "name": "west",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "maximum": 40.1,
                  "minimum": 39.55,
                  "type": "number"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Рамка карты: запад",
              "title": "West"
            }
          },
          {
            "description": "Рамка карты: юг",
            "in": "query",
            "name": "south",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "maximum": 43.72,
                  "minimum": 43.35,
                  "type": "number"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Рамка карты: юг",
              "title": "South"
            }
          },
          {
            "description": "Рамка карты: восток",
            "in": "query",
            "name": "east",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "maximum": 40.1,
                  "minimum": 39.55,
                  "type": "number"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Рамка карты: восток",
              "title": "East"
            }
          },
          {
            "description": "Рамка карты: север",
            "in": "query",
            "name": "north",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "maximum": 43.72,
                  "minimum": 43.35,
                  "type": "number"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Рамка карты: север",
              "title": "North"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HotelPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Отели зоны",
        "tags": [
          "guest-search"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/hotels/{hotel_id}": {
      "get": {
        "operationId": "getHotel",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HotelDetail"
                }
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Карточка отеля",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/hotels/{hotel_id}/characteristics/{ch_id}": {
      "get": {
        "operationId": "getHotelCharacteristic",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          },
          {
            "description": "ID характеристики",
            "in": "path",
            "name": "ch_id",
            "required": true,
            "schema": {
              "description": "ID характеристики",
              "examples": [
                "CH-SUNSET"
              ],
              "maxLength": 43,
              "minLength": 5,
              "pattern": "^CH-[A-Z0-9-]{2,40}$",
              "title": "Ch Id",
              "type": "string"
            }
          },
          {
            "description": "Только этот уровень, например floor:ЮЗ:5",
            "in": "query",
            "name": "scope",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "description": "Ключ уровня значения: hotel, side:ЮЗ, floor:ЮЗ:5, room:401 или room:<uuid>, district:adler; с датами - плюс |2026-06-01..2026-06-14",
                  "maxLength": 80,
                  "minLength": 5,
                  "pattern": "^(hotel|side:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ)|floor:(С|СВ|В|ЮВ|Ю|ЮЗ|З|СЗ):([1-9]|[1-3][0-9]|40)|room:([0-9A-Za-z-]{1,20}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})|district:(center|matsesta|khosta|kudepsta|adler|imeretinka))(\\|[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])\\.\\.[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]))?$",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Только этот уровень, например floor:ЮЗ:5",
              "title": "Scope"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Provenance"
                }
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Как мы это узнали: провенанс значения",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/hotels/{hotel_id}/view": {
      "get": {
        "description": "Профиль горизонта, маски моря и гор, путь солнца на даты, препятствия и вывод.",
        "operationId": "getHotelView",
        "parameters": [
          {
            "description": "ID отеля",
            "in": "path",
            "name": "hotel_id",
            "required": true,
            "schema": {
              "description": "ID отеля",
              "examples": [
                "24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Hotel Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "side",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/Rumb"
            }
          },
          {
            "description": "Этаж точки обзора",
            "in": "query",
            "name": "floor",
            "required": true,
            "schema": {
              "description": "Этаж точки обзора",
              "examples": [
                5
              ],
              "maximum": 40,
              "minimum": 1,
              "title": "Floor",
              "type": "integer"
            }
          },
          {
            "description": "Первый день поездки",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "format": "date",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Первый день поездки",
              "title": "From"
            }
          },
          {
            "description": "Последний день поездки",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "format": "date",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Последний день поездки",
              "title": "To"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ViewResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Вид из окна",
        "tags": [
          "view"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/me/consents": {
      "post": {
        "operationId": "grantConsent",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConsentIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Consent"
                }
              }
            },
            "description": "Согласие записано",
            "headers": {
              "Set-Cookie": {
                "description": "__Host-sid: анонимная сессия гостя, если её ещё не было",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Дать согласие на память или голос",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "guest"
      }
    },
    "/api/v1/me/consents/{kind}": {
      "delete": {
        "operationId": "revokeConsent",
        "parameters": [
          {
            "description": "Вид согласия",
            "in": "path",
            "name": "kind",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/ConsentKind",
              "description": "Вид согласия",
              "examples": [
                "voice"
              ]
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Согласие отозвано. Отзыв voice закрывает голосовую сессию"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Отозвать согласие",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "guest"
      }
    },
    "/api/v1/me/preferences": {
      "delete": {
        "operationId": "deleteAllPreferences",
        "responses": {
          "204": {
            "description": "Удалено всё"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Удалить всё, что система помнит",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "guest"
      },
      "get": {
        "operationId": "listPreferences",
        "parameters": [
          {
            "description": "Номер страницы с 1",
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "description": "Номер страницы с 1",
              "maximum": 10000,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "description": "Размер страницы: 10, 25, 50 или 100",
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 25,
              "description": "Размер страницы: 10, 25, 50 или 100",
              "enum": [
                10,
                25,
                50,
                100
              ],
              "title": "Page Size",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "sort",
            "required": false,
            "schema": {
              "default": "-updated_at",
              "enum": [
                "key",
                "-key",
                "updated_at",
                "-updated_at"
              ],
              "title": "Sort",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreferencePage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Что система помнит",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "guest"
      }
    },
    "/api/v1/me/preferences/{key}": {
      "delete": {
        "operationId": "deletePreference",
        "parameters": [
          {
            "description": "Ключ предпочтения из словаря",
            "in": "path",
            "name": "key",
            "required": true,
            "schema": {
              "description": "Ключ предпочтения из словаря",
              "examples": [
                "quiet_night_quiet"
              ],
              "maxLength": 41,
              "minLength": 3,
              "pattern": "^[a-z][a-z0-9_]{2,40}$",
              "title": "Key",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Удалено"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Удалить предпочтение",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "guest"
      },
      "put": {
        "description": "Только структурное предпочтение, без свободного текста. Без согласия на память - 403.",
        "operationId": "putPreference",
        "parameters": [
          {
            "description": "Ключ предпочтения из словаря",
            "in": "path",
            "name": "key",
            "required": true,
            "schema": {
              "description": "Ключ предпочтения из словаря",
              "examples": [
                "quiet_night_quiet"
              ],
              "maxLength": 41,
              "minLength": 3,
              "pattern": "^[a-z][a-z0-9_]{2,40}$",
              "title": "Key",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PreferenceIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreferenceSaved"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Изменить предпочтение",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "guest"
      }
    },
    "/api/v1/ready": {
      "get": {
        "description": "Готовность: база доступна. Миграции -, снимок -.",
        "operationId": "getReady",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ready"
                }
              }
            },
            "description": "База доступна"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ready"
                }
              }
            },
            "description": "База недоступна"
          }
        },
        "summary": "Готовность",
        "tags": [
          "service"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/search": {
      "post": {
        "description": "Потребности, топ, «не подтверждено», «не подходят» и шаблонные объяснения.",
        "operationId": "searchHotels",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            },
            "description": "Выдача. Без сессии ставится анонимная, поток объяснений идёт в ней",
            "headers": {
              "Set-Cookie": {
                "description": "__Host-sid: анонимная сессия гостя, если её ещё не было",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Разбор запроса и выдача",
        "tags": [
          "guest-search"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/search/suggestions": {
      "get": {
        "operationId": "getSearchSuggestions",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuggestionsResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Умные чипы главного экрана",
        "tags": [
          "guest-search"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/search/{search_id}/explanations": {
      "get": {
        "description": "Та же сессия, что и поиск: чужой search_id - 404.",
        "operationId": "streamSearchExplanations",
        "parameters": [
          {
            "description": "ID поиска из ответа POST /search",
            "in": "path",
            "name": "search_id",
            "required": true,
            "schema": {
              "description": "ID поиска из ответа POST /search",
              "examples": [
                "1e349b6c-6a30-5d6e-98bc-040269a86280"
              ],
              "format": "uuid",
              "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
              "title": "Search Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/event-stream": {
                "example": "event: explanation.ready\nid: 1\ndata: {\"data_mode\":\"mock\",\"explanation\":{\"claims\":[{\"observation_ids\":[\"1bc28e5c-a481-58d2-9505-2de53fa58a4f\",\"de0db691-db80-59df-badb-64035219ad81\"],\"text\":\"Из окон стороны ЮЗ с 3-го этажа закат садится в море с апреля по август. До пляжа 7 минут пешком.\"}],\"generated_by\":\"llm\"},\"hotel_id\":\"24450bf0-94ee-5c2a-ba35-0fdfc7fa1ec6\",\"search_id\":\"1e349b6c-6a30-5d6e-98bc-040269a86280\",\"variant_id\":\"v1\"}\n\nevent: explanations.done\nid: 2\ndata: {\"data_mode\":\"mock\",\"ready\":3,\"search_id\":\"1e349b6c-6a30-5d6e-98bc-040269a86280\",\"timed_out\":false}\n\n",
                "schema": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "oneOf": [
                        {
                          "properties": {
                            "data": {
                              "contentMediaType": "application/json",
                              "contentSchema": {
                                "$ref": "#/components/schemas/ExplanationReadyEvent"
                              },
                              "type": "string"
                            },
                            "event": {
                              "const": "explanation.ready",
                              "type": "string"
                            },
                            "id": {
                              "maxLength": 20,
                              "type": "string"
                            },
                            "retry": {
                              "minimum": 0,
                              "type": "integer"
                            }
                          },
                          "required": [
                            "event",
                            "data"
                          ],
                          "type": "object"
                        },
                        {
                          "properties": {
                            "data": {
                              "contentMediaType": "application/json",
                              "contentSchema": {
                                "$ref": "#/components/schemas/ExplanationsDoneEvent"
                              },
                              "type": "string"
                            },
                            "event": {
                              "const": "explanations.done",
                              "type": "string"
                            },
                            "id": {
                              "maxLength": 20,
                              "type": "string"
                            },
                            "retry": {
                              "minimum": 0,
                              "type": "integer"
                            }
                          },
                          "required": [
                            "event",
                            "data"
                          ],
                          "type": "object"
                        }
                      ]
                    }
                  ],
                  "description": "Поток Server-Sent Events. Событие - объект event, id, data. События - в x-sse-events"
                },
                "x-sse-events": {
                  "explanation.ready": {
                    "$ref": "#/components/schemas/ExplanationReadyEvent"
                  },
                  "explanations.done": {
                    "$ref": "#/components/schemas/ExplanationsDoneEvent"
                  }
                }
              }
            },
            "description": "События explanation.ready на вариант и explanations.done в конце, не дольше 8 секунд"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Не найдено, в том числе чужой объект"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Объяснения от LLM по мере готовности (SSE)",
        "tags": [
          "guest-search"
        ],
        "x-access": "guest"
      }
    },
    "/api/v1/sources/attribution": {
      "get": {
        "operationId": "getSourceAttribution",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Attribution"
                }
              }
            },
            "description": "Successful Response"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Источники и лицензии",
        "tags": [
          "guest-hotel"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/status": {
      "get": {
        "description": "Подключён ли ИИ на этом стенде. Без ключа запросы разбираются правилами.",
        "operationId": "getServiceStatus",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServiceStatus"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Статус ИИ",
        "tags": [
          "service"
        ],
        "x-access": "public"
      }
    },
    "/api/v1/voice/sessions": {
      "post": {
        "description": "Нужно согласие voice. Токен одноразовый, клиент шлёт его первым сообщением WebSocket.",
        "operationId": "createVoiceSession",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VoiceSessionIn"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VoiceSession"
                }
              }
            },
            "description": "Сессия голоса открыта",
            "headers": {
              "Set-Cookie": {
                "description": "__Host-sid: анонимная сессия гостя, если её ещё не было",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нужен вход"
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Нет доступа или запрос с чужого сайта"
          },
          "413": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Тело больше лимита"
          },
          "415": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Неподдерживаемый тип тела"
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Ошибка в данных запроса"
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "Слишком много запросов"
          }
        },
        "summary": "Открыть голосовую сессию",
        "tags": [
          "guest-search"
        ],
        "x-access": "guest"
      }
    }
  },
  "tags": [
    {
      "description": "G1, G2, G8: поиск, чипы, отели зоны, объяснения, голос",
      "name": "guest-search"
    },
    {
      "description": "G3, G5-G7, G9 и вход: карточка, провенанс, сравнение, бронь, профиль",
      "name": "guest-hotel"
    },
    {
      "description": "G4: «Вид из окна»",
      "name": "view"
    },
    {
      "description": "H1-H5: кабинет отельера, только свои отели",
      "name": "hotelier"
    },
    {
      "description": "A1, A2, A5, A6, A9: пульт, отели, запуски, источники, аудит",
      "name": "admin-ops"
    },
    {
      "description": "A3, A4, A7, A8: характеристики, конфликты, метрики, песочница",
      "name": "admin-knowledge"
    },
    {
      "description": "Пробы живости и готовности",
      "name": "service"
    }
  ]
}
