name: metravel-quest
description: >-
Создание/обновление городских квест-маршрутов на metravel.by: данные квеста,
скрипт-миграция, заливка на бэкенд. Триггеры: «сделай квест по городу »,
«добавь квест», «обнови квест».
metravel-quest
Движок и регламент для публикации городских квестов на metravel.by. Квест — это пеший маршрут по точкам города: на каждой точке игрок читает живую историю/легенду и выполняет задание (что-то посчитать, найти, прочитать).
Принципы (ОБЯЗАТЕЛЬНО)
- Не дублировать. Перед созданием проверь существующие квесты и не делай
повтор по
quest_idи по городу:GET https://metravel.by/api/quests/(список) иGET https://metravel.by/api/quests/by-quest-id/<quest_id>/(деталь). Если квест по этому городу уже есть — обновляй его (тот жеquest_id, миграция идемпотентна), а не плоди дубль. - 8–12 точек must-see в логичном пешем маршруте. Бери реально главные достопримечательности города (рейтинги Google Maps top sights, TripAdvisor), а не случайные. Упорядочь по координатам в естественный пеший путь — с севера на юг или по районам, чтобы человек шёл связно, а не метался по карте.
- Квест — это СВЯЗНАЯ ИСТОРИЯ, а не список точек. Погружение. У квеста
сквозная нить:
introзадаёт завязку и героя/тему, каждая точка двигает рассказ дальше, финал закрывает арку. Человек должен чувствовать себя ВНУТРИ истории, идти по сюжету, а не отмечаться на достопримечательностях. Между точками — мостики («отсюда переулок выведет тебя к…»), чтобы маршрут читался как одно повествование. Тема/мотив (легенда, личность, эпоха, ремесло) проходит через весь квест. 3a. На каждой точке — живой слой деталей. Не сухая справка, а то, что делает место интересным: ЛЕГЕНДА (подавай как легенду), реальная ИСТОРИЯ и событие, ЛИЧНОСТИ (кто здесь жил/творил/бывал, человеческая деталь), АРХИТЕКТУРА (что видно глазами и почему так — стиль, материал, деталь), КУЛЬТУРНЫЕ ОСОБЕННОСТИ и быт, происхождение названия (топонимика). Всё ДОСТОВЕРНОЕ, сверенное веб-поиском: факт не подтверждён — не пиши его, новую «наблюдаемую» деталь не выдумывай. Факты — как факты, легенды — как легенды. - Игровое задание, выполнимое на месте. На каждой точке — задание, которое
реально сделать стоя перед объектом: найти деталь, прочитать табличку/дату,
назвать предмет в руках статуи, узнать блюдо/материал/символ. Не абстрактные
вопросы из интернета, а наблюдение здесь и сейчас. У каждого задания —
hintи корректныйanswer_pattern. Счёт объектов — ТОЛЬКО при 100% уверенности в числе (устойчивый, не зависит от ракурса/сезона/реставрации). Число плавающее/спорное → не делай счёт «с запасным range», а замени на более ИНТЕРЕСНЫЙ вопрос с бесспорным однозначным ответом (эталон: Plac Nowy — вместо счёта окошек «что за блюдо жарят?» = запеканка). Где счёт оставлен — давай мягкийrange±1 вокруг верного числа (n≥3, где реально сбиться: 8 лягушек → 7–9, 12 кресел → 11–13);exact— только для бесспорных 1–2 (2 башни, 1 проезд), где ±1 пропустил бы явно неверное. 4b. Планка вопроса (4 критерия, ВСЕ обязательны): (1) ответ 100% однозначен и проверяем (источник/видно на месте); (2) виден стоя на точке — наблюдение здесь-и-сейчас, не эрудиция; (3) НЕ выдаётся вstory/hint; (4) объект стабилен (не снимут, не зависит от сезона/реставрации/ракурса). 4c. Одобренные ТИПЫ вопросов (опирайся на них): (A) узнай культовую вещь места — фирменный символ/блюдо/явление (запеканка на Plac Nowy); (B) прочитай конкретную надпись/дату/имя на табличке/памятнике/надгробии (сверь по фото/Mapillary, что читаемо); (C) найди спрятанную стабильную деталь и назови (бронзовый дракончик у моста); (D) что символизирует / кто-что изображён — смысл памятника (пустые стулья → депортированные) или узнаваемая фигура/зверь/предмет на рельефе/гербе/статуе. ИЗБЕГАЙ: счёт нестабильного (окна/гномы/утки/деревья/ярусы); архитектурные термины-загадки (аркада, аттик, пинакли); субъективное («сколько ярусов», «какая форма»); невидимое-эрудиция. Где НИ ОДИН тип не даёт 100% — делай свободный «впечатленческий»any_text(живо, погружает, всегда засчитывается), а НЕ шаткий вопрос. 4a. Подсказка НЕ содержит ответа.hint— это наводка «куда/как смотреть» («подойди ближе к стене», «подними взгляд на вершину колонны», «пересчитай колонны на фасаде»), а НЕ сам ответ и не его половина. Запрещено: называть ответ или его синоним («…— от СССР», «это гусеница танкетки Голиаф»), давать точное число/диапазон ответа («их от 3 до 5»), писать «Крест и …». Подсказка открывается только после 2 неверных попыток — она должна помочь додумать, а не выдать. Проверка перед заливкой: для каждого шага убедись, что ответ (и ключевые словаanswer_pattern.value) не встречается в текстеhint. - Вступление и финал.
intro(is_intro,answer_pattern.type='any') — завязка, тон, правила прохождения, направление к первой точке. Финал (quest-finales) — тёплый человеческий вывод о городе и маршруте. Это ТЕКСТ финала; финальное ВИДЕО+постер — отдельный пост-шаг, см. скиллmetravel-quest-finale(не забудь его для нового квеста). - Тон живой, человеческий. Без AI-обвязки, без канцелярита, без эмодзи и шаблонных заголовков. Пиши как увлечённый гид, а не как генератор.
- Интересные опциональные места С РАСПИСАНИЕМ. По ходу маршрута добавляй
ОПЦИОНАЛЬНЫЕ точки (
answer_pattern.type='any', пометка «по желанию») — то, что обогащает прогулку: музей (напр. Czartoryski с «Дамой с горностаем»), тематический/кошерный ресторан, знаковая кофейня, смотровая. Для каждого такого места УКАЗЫВАЙ ЧАСЫ РАБОТЫ и (если есть) сайт — чтобы человек знал, открыто ли сейчас; музеи и кафе с расписанием особенно. Источник кандидатов и ихopening_hours/сайта —node scripts/quest-poi-suggest.js --quest-id=<id>(--kinds=museum,gallery,kosher,cafe,attraction); часы из OSM сверяй с офиц. сайтом (могут устареть). Опциональная точка не блокирует прохождение.
Технический раздел
Формат файла данных
По образцу scripts/warsaw-quest-data.js — module.exports = [ { quest } ].
Структура одного квеста: quest_id (строковый, kebab-case), title,
city: { name, lat, lng, country }, meta, storage_key, intro, steps[],
finale. У каждого шага — step_id, title, location, story, task,
hint, answer_pattern: { type, value }, lat, lng, mapsUrl.
- Координаты — decimal degrees (
lat,lng), напр.52.25123, 21.00862. - mapsUrl — ссылка на Google Maps:
https://maps.google.com/?q=<место>. - meta:
duration_min(минуты на прохождение),difficulty(easy/medium/hard),tags(['legend','history','citywalk',...]), опц.pet_friendly. - country — id страны квеста (НЕ travel-страна!): Польша=160, Беларусь=3, Армения=6.
Типы answer_pattern и когда какой
any—{ type: 'any', value: '' }— любой ответ принимается. Дляintroи шагов «просто дойди / нажми далее».exact—{ type: 'exact', value: '<строка>' }— точное совпадение после нормализации. Для одного известного числа/слова без вариантов.exact_any—{ type: 'exact_any', value: JSON.stringify([...]) }— массив допустимых вариантов (lowercase, без пунктуации). Перечисли падежи и синонимы:['меч и щит','щит и меч','меч, щит',...]. Для слова/названия/предмета.range—{ type: 'range', value: JSON.stringify({ min, max }) }— число в диапазоне. Для заданий «посчитай» (счёт может слегка разойтись).any_text—{ type: 'any_text', value: JSON.stringify({ min_length }) }— любой осмысленный текст не короче N символов. Для «опиши в нескольких словах».any_number— любое число. Когда важен сам факт числа, не его значение.approx—{ type: 'approx', value: JSON.stringify({ target, tolerance }) }— число около цели ± допуск.
Нормализация ввода (buildAnswerChecker): lowercase, схлоп пробелов, удаление
пунктуации, ё→е. Учитывай это, заполняя варианты exact_any в lowercase.
Подсказки по выбору: счёт объектов → range; известное точное число →
exact; слово/название → exact_any (перечисли падежи и синонимы); описание
своими словами → any_text с min_length; «дойди/нажми» → any.
Процесс
- Ресёрч города — собери 8–12 must-see и их легенды/историю/топонимику через веб-поиск. Проверь достоверность фактов, упорядочь в пеший маршрут.
- Напиши данные —
scripts/<city>-quest-data.jsпо образцуwarsaw-quest-data.js, с явнымanswer_patternна каждом шаге. - Напиши/переиспользуй миграцию —
scripts/migrate-<city>-quest.js, идемпотентную (город→квест→шаги→финал), по образцуscripts/migrate-warsaw-quest.js. Квест создаётся соstatus=1. - Dry-run — прогони миграцию в режиме проверки, убедись что данные читаются
и маппятся, нет ошибок.
4a. Гео-сверка точек (до заливки). Прогони
node scripts/quest-geocheck.js --source-file=scripts/<city>-quest-data.js --quest-id=<quest_id>и разбери WARN/FAIL: каждая точка должна стоять на объекте изstory/task, а не на парковке/остановке/дороге (см. скиллmetravel-quest-geocheck). Исправьlat/lng/mapsUrlв data-файле и перепрогони до 0 реальных проблем. - Залей на прод:
NODE_TLS_REJECT_UNAUTHORIZED=0 node scripts/migrate-<city>-quest.js --api-url=https://metravel.by(токен из~/.metravel_tokenили--token=; если протух (HTTP 401) — свежий логином e2e:METRAVEL_TOKEN=$(node scripts/get-quest-token.js) node scripts/migrate-<city>-quest.js …). - Проверь GET-ом
GET /api/quests/by-quest-id/<quest_id>/: на местеintro, всеsteps,finale, корректные типыanswer_pattern, координаты,mapsUrl. - Обложку загрузи через Django admin →
/admin/quests/quest/. - Пройди квест залогиненным на мобильном вьюпорте: понятны ли задания, выполнимы ли на месте, работает ли проверка ответов; проверь печатную версию (QuestPrintable).
Верификация
- Дев Expo Web проксирует
/apiна препрод-бэкенд — новый прод-квест там не виден. Для локального теста можно временно залить квест и на препрод (--api-url=<preprod>), затем удалить/перезалить на прод. - Prod Static не имеет SPA-fallback для динамических роутов квестов
(
/quests/{cityId}/{quest_id}) — прямой заход по URL может не открыться, проверяй переходом из списка. - Прохождение квеста требует логина (AuthGate). Для QA логинься тестовым e2e-аккаунтом (см. CLAUDE.md), без ручного ввода пароля в поля.
API-справка
GET /api/quests/— список квестов (проверка дублей).GET /api/quests/by-quest-id/<quest_id>/— деталь по строковому quest_id (intro + steps + finale).- Запись (через миграцию, Token):
/api/quest-cities/,/api/quests/,/api/quest-steps/,/api/quest-finales/. - Фронт-роут детали:
/quests/{cityId}/{quest_id}— тянет по строковомуquest_id. Проверка ответов —utils/questAdapters.ts(buildAnswerChecker).