Журнал разработки Николая Ланца

13 июл. 2026 г.

Вот тут, кстати, надо продумать какую-то систему метрик и классификации объектов, не только по качеству, но и по направленности. К примеру, если человек не хочет пещеры, то ему точно не надо гроты и т.п. Если он выбирает вечернее платье, вряд ли ему надо в бухту Ха Лонга или дайвинг. Если ре ищет в городе ресторан или казино, ему не нужен водопад. В свою очередт если он ещет к горе, то почему не заскочить на базу, где есть и ручей, и пещера, и ресторан и еще что-либо. Кстати, вот эти связки мест точно нужны. Есть вот базы отдыха, где и кемпинг, и поесть, и пещера, и покупаться. Там можно на пару дней зависнуть. А жемчужный остров в Ня Чанге можно 3 дня не покидать

Вот мысли чатгпт:

Да — здесь уже нужна не просто таксономия мест, а система совместимости, намерений и “ёмкости” объекта.

Проблема в том, что тип объекта сам по себе почти ничего не говорит о том, зачем человек туда едет и сколько потребностей он может закрыть.

Например:

  • «пещера» — это тип;
  • «активный отдых», «природа», «на полдня», «не подходит при клаустрофобии» — это уже направленность;
  • «туристический комплекс с пещерой, рестораном, купанием и ночёвкой» — это вообще составной объект, который может заменить несколько отдельных точек маршрута.

Нужны три разных слоя классификации

1. Что это

Это ваша текущая типология:

  • пещера;
  • водопад;
  • ресторан;
  • пляж;
  • казино;
  • дайвинг;
  • пагода;
  • парк;
  • кемпинг;
  • отель.

Она нужна, но её недостаточно.

2. Зачем туда едут

Это пользовательское намерение:

  • природа;
  • активный отдых;
  • романтика;
  • вечерний выход;
  • еда;
  • шопинг;
  • развлечения;
  • отдых с детьми;
  • ночная жизнь;
  • спокойный отдых;
  • приключение;
  • фотогеничное место;
  • оздоровление;
  • культурная программа.

Вот здесь появляются ваши примеры:

  • человек ищет вечернее платье → ему нужны шопинг, центр города, ресторан, вечерняя программа;
  • ищет казино → ему нужны nightlife, рестораны, отели, транспорт вечером;
  • едет к горе → ему можно предложить пещеру, ручей, смотровую, кафе, кемпинг;
  • не любит пещеры → исключаются пещеры, гроты, подземные храмы и похожие объекты.

3. Как объект ведёт себя в маршруте

Это уже операционная классификация:

  • на 30 минут;
  • на 2 часа;
  • на полдня;
  • на день;
  • на 2–3 дня;
  • можно совместить;
  • лучше отдельно;
  • транзитная точка;
  • базовая точка;
  • конечная точка;
  • самодостаточный комплекс;
  • требует ночёвки;
  • зависит от погоды;
  • требует бронирования;
  • подходит только в определённое время суток.

Вот это критически важно для маршрута.


Нужна сущность «комплекс» или «кластер»

То, что вы описываете с базами отдыха и Жемчужным островом, нельзя моделировать просто как обычное место.

Нужно ввести понятие:

Составной объект / туристический комплекс

Он может включать:

  • рестораны;
  • отели;
  • пляжи;
  • аттракционы;
  • пещеры;
  • кемпинг;
  • бассейн;
  • спа;
  • маршруты;
  • шоу;
  • торговлю;
  • транспорт внутри территории.

Примеры:

  • курорт;
  • островной комплекс;
  • национальный парк;
  • база отдыха;
  • тематический парк;
  • большой храмовый комплекс;
  • горный кластер;
  • пляжный кластер;
  • экопарк.

У него должны быть свои свойства:

  • минимальное рекомендуемое время;
  • максимальное разумное время;
  • можно ли ночевать;
  • можно ли есть на месте;
  • есть ли внутренний транспорт;
  • сколько активностей внутри;
  • подходит ли как самостоятельная часть поездки.

Например:

Жемчужный остров Тип: туристический комплекс Рекомендуемая длительность: 1–3 дня Закрывает потребности: пляж, развлечения, еда, дети, шоу, проживание

Тогда система понимает: это не просто одна точка на два часа, а контейнер маршрута.


Полезная модель объекта

Для каждого объекта я бы добавил набор признаков.

Тематические признаки

  • природа;
  • еда;
  • шопинг;
  • история;
  • религия;
  • приключения;
  • вода;
  • горы;
  • ночная жизнь;
  • развлечения;
  • wellness;
  • дети;
  • романтика;
  • фотография.

Нежелательные признаки

Это важно не меньше.

  • замкнутые пространства;
  • высота;
  • физическая нагрузка;
  • толпы;
  • шум;
  • жара;
  • вода;
  • животные;
  • религиозные ограничения;
  • долгий трансфер;
  • экстремальная активность.

Если человек говорит «не люблю пещеры», система должна исключать не только тип пещера, но и признаки:

  • подземный объект;
  • грот;
  • тоннель;
  • тесное пространство;
  • спелеология.

То есть нужны не только положительные интересы, но и антипредпочтения.


Нужны “потребности”, которые закрывает объект

Это отдельная сильная идея.

Каждое место может закрывать одну или несколько потребностей:

  • поесть;
  • переночевать;
  • искупаться;
  • развлечь детей;
  • отдохнуть;
  • погулять;
  • посмотреть природу;
  • купить вещи;
  • получить эмоции;
  • провести вечер;
  • укрыться от дождя;
  • провести целый день без переездов.

Например:

Водопад

Закрывает:

  • природа;
  • фото;
  • прогулка;
  • купание — иногда;
  • активность.

Казино

Закрывает:

  • вечер;
  • развлечения;
  • nightlife;
  • ресторан — иногда.

Туристическая база

Закрывает:

  • еда;
  • природа;
  • купание;
  • ночёвка;
  • активность;
  • отдых на 1–2 дня.

Тогда маршрут можно строить не вокруг количества точек, а вокруг баланса закрытых потребностей.


Нужны метрики объекта

Я бы разделил их на несколько групп.

Привлекательность

  • уникальность;
  • визуальная ценность;
  • популярность;
  • качество опыта;
  • репутация;
  • актуальность.

Логистика

  • удалённость;
  • доступность;
  • время в пути;
  • сложность подъезда;
  • наличие парковки;
  • зависимость от транспорта.

Временная ёмкость

  • минимум времени;
  • рекомендуемое время;
  • максимум разумного времени;
  • можно ли занять полдня;
  • можно ли занять целый день;
  • можно ли остаться на ночь.

Составность

  • количество активностей;
  • количество сервисов;
  • наличие еды;
  • наличие проживания;
  • наличие развлечений;
  • наличие природных объектов;
  • самостоятельность.

Контекст

  • подходит утром;
  • подходит вечером;
  • подходит в дождь;
  • подходит в жару;
  • подходит с детьми;
  • подходит без машины;
  • подходит для пары;
  • подходит для соло.

Очень важна “плотность пользы”

Можно ввести внутреннюю метрику:

Сколько разных потребностей закрывается в одной точке

Например:

Обычный водопад

  • природа;
  • фото;
  • прогулка.

Плотность пользы: средняя.

База отдыха

  • природа;
  • купание;
  • еда;
  • ночёвка;
  • пещера;
  • кемпинг;
  • активность.

Плотность пользы: высокая.

Большой островной комплекс

  • пляж;
  • аттракционы;
  • рестораны;
  • шоу;
  • отели;
  • дети;
  • вечерняя программа.

Плотность пользы: очень высокая.

Такие объекты надо иначе показывать в маршруте:

«Эта точка может занять 2–3 дня и заменить несколько отдельных поездок».


Нужны связи не только между местами, но и внутри комплексов

Связи могут быть такими:

  • входит в комплекс;
  • находится на территории;
  • доступно только через комплекс;
  • можно посетить отдельно;
  • входит в общий билет;
  • требует отдельного билета;
  • работает в другие часы;
  • лучше посещать в определённой последовательности.

Например:

Жемчужный остров Включает:

  • аквапарк;
  • пляж;
  • рестораны;
  • шоу;
  • канатную дорогу;
  • отели.

Пользователь может:

  • добавить весь комплекс;
  • добавить только часть;
  • добавить комплекс на 1 день;
  • добавить комплекс на 3 дня.

Как это применять к рекомендациям

Система должна сначала определять намерение пользователя, а потом уже выбирать типы объектов.

Пример:

Пользователь ищет вечернее платье.

Намерения:

  • шопинг;
  • город;
  • вечер;
  • возможно ресторан;
  • возможно фотогеничное место;
  • минимальные переезды.

Неуместные категории:

  • дайвинг;
  • водопады;
  • пещеры;
  • удалённые бухты.

Уместная связка:

торговый центр → салон/бутик → ресторан → бар/шоу

Другой пример:

Пользователь едет к горе.

Намерения:

  • природа;
  • активность;
  • панорамы;
  • возможно полный день.

Дополнительные связи:

  • ручей;
  • пещера;
  • кафе;
  • кемпинг;
  • смотровая;
  • база отдыха;
  • ночёвка.

И тогда система предлагает не «ещё одну гору», а готовый кластер впечатлений.


Классификация маршрутов

Маршрут тоже должен иметь направленность:

  • городской;
  • природный;
  • гастрономический;
  • вечерний;
  • семейный;
  • приключенческий;
  • романтический;
  • пляжный;
  • смешанный;
  • без переездов;
  • с высокой плотностью активностей;
  • расслабленный.

И баланс:

  • 70% природа / 30% город;
  • 50% отдых / 50% активности;
  • 20% шопинг / 30% еда / 50% развлечения.

Это позволит проверять, не “сломался” ли маршрут.

Например:

Пользователь хочет спокойный пляжный отдых, а маршрут на 60% состоит из переездов и активностей.

Агент должен сказать:

Маршрут не соответствует вашим предпочтениям.


Самая полезная новая сущность

Я бы добавил не только комплекс, но ещё и сценарий.

Примеры сценариев:

  • день в дождь;
  • вечер в городе;
  • два дня без выезда из комплекса;
  • поездка с ребёнком;
  • романтический вечер;
  • активный день в горах;
  • день без автомобиля;
  • остановка по пути между городами.

Сценарий связывает:

  • намерения;
  • ограничения;
  • типы мест;
  • комплексы;
  • время;
  • порядок действий.

Минимальная модель

К вашим сущностям:

  • провинция;
  • город;
  • тип места;
  • место;

я бы добавил:

  • комплекс;
  • сценарий;
  • потребность;
  • интерес;
  • антипредпочтение;
  • активность;
  • сервис;
  • маршрутный профиль.

И связи:

  • место входит в комплекс;
  • место закрывает потребность;
  • место подходит сценарию;
  • место конфликтует с предпочтением;
  • место дополняет другое место;
  • место заменяет другое место;
  • место требует отдельного дня;
  • место можно посетить по пути;
  • комплекс рассчитан на N дней.

Главный вывод

Систему лучше строить не вокруг вопроса:

«Что это за место?»

А вокруг трёх вопросов:

Зачем туда ехать? Сколько времени оно может занять? Какие потребности оно закрывает?

И отдельно выделить составные объекты, где человек реально может провести день, два или три без необходимости ездить по десяткам разрозненных точек.

Это все релевантно к вот этому ворклогу.

Переосмыслить структуру сайта, доработать ее и пересобрать контент

13 июл. 2026 г.

Кстати, вот что чатгпт говорит по поводу нашей структуры базы знаний:

Самое сильное здесь — не отдельные модели, а то, как они складываются вместе.

Что здесь реально мощного

KBConcept фактически может быть универсальной сущностью сайта:

  • страна;
  • регион;
  • город;
  • район;
  • пляж;
  • храм;
  • аэропорт;
  • отель;
  • маршрут;
  • событие;
  • транспортный узел.

У концепта уже есть:

  • иерархия Parent / Children;
  • корень дерева;
  • координаты lat / lng / alt;
  • мультиязычные названия через KBLabel;
  • файлы и изображения;
  • факты;
  • привязка к URL через SiteRoute;
  • произвольные структурированные данные через Json.

То есть город, достопримечательность и маршрут — не просто статьи в CMS, а узлы единого графа знаний.

И это идеально совпадает с VietnamGuru.

Например:

Дананг → район Сон Тра → пагода Линь Унг → координаты → часы работы → стоимость → сезонность → транспорт → объекты рядом → статья → карта → маршрут.

Все это можно хранить не в одном огромном тексте, а как связанные знания.

Самая перспективная часть — факты не привязаны жестко к одной сущности

KBFact вместе с KBFactParticipation поддерживает n-арные связи: один факт может связывать сразу несколько концептов с разными ролями.

Для туристического проекта это очень сильная модель.

Например, утверждение:

Автобус №5 идет из Дананга в Хойан, останавливается возле Marble Mountains, билет стоит 30 000 донгов.

Можно представить одним фактом, где участвуют:

  • Дананг — origin;
  • Хойан — destination;
  • Marble Mountains — stop;
  • автобус №5 — transport;
  • 30 000 VND — значение;
  • период актуальности;
  • источник;
  • confidence;
  • статус проверки.

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

Тут заложена полноценная эпистемическая модель

Особенно нравится, что база не притворяется абсолютной истиной.

У фактов есть:

  • confidence;
  • verified / tentative / disputed / deprecated;
  • источник;
  • период действия;
  • дата, когда факт стал известен;
  • raw или derived;
  • происхождение из других фактов.

Плюс противоречия являются самостоятельными объектами через KBConflict, а не просто перезаписываются.

Для туристического ИИ это огромный плюс.

Типичный реальный случай:

  • Google Maps говорит, что место открыто до 18:00;
  • официальный сайт говорит до 17:00;
  • свежий отзыв говорит, что касса закрывается в 16:30.

Обычный RAG просто выдаст один из вариантов. Твоя модель позволяет агенту сказать:

«Источники расходятся. Официально указано 17:00, но посетители сообщают о более раннем закрытии кассы. Лучше приехать до 16:00».

Вот это уже не чат-бот над статьями, а нормальный интеллектуальный туристический помощник.

KBKnowledgeSpace открывает еще один уровень

Knowledge Spaces позволяют одному и тому же факту иметь разную:

  • видимость;
  • важность;
  • степень доверия;
  • интерпретацию.

Это можно использовать не только для приватности, но и для разных режимов продукта:

  • база редакции;
  • публичная база;
  • персональное пространство пользователя;
  • пространство конкретной поездки;
  • знания агента;
  • партнерский контент;
  • непроверенные пользовательские данные.

Например, турист создает поездку «Вьетнам, август, семья с ребенком». Агент может построить отдельное пространство знаний, где выше приоритет у:

  • коротких переездов;
  • семейных пляжей;
  • объектов с кондиционером;
  • безопасной еды;
  • мест без сложных подъемов.

Те же факты существуют глобально, но их важность меняется в контексте поездки.

SiteRoute делает структуру сайта продолжением knowledge graph

Связь SiteRoute ↔ KBConcept — очень правильная идея.

Это означает, что URL — не сама сущность, а представление сущности.

В результате можно:

  • менять маршрут без потери концепта;
  • строить хлебные крошки из дерева;
  • генерировать страницы автоматически;
  • создавать региональные разделы;
  • поддерживать старые URL через redirects;
  • находить сущности без публичной страницы;
  • давать одному объекту разные представления.

Для SEO это особенно ценно. Контентная архитектура может рождаться из графа, а не поддерживаться вручную в нескольких несвязанных таблицах.

Карта здесь вообще может стать интерфейсом к базе знаний

Теперь твое пояснение про карту выглядит еще интереснее.

Поскольку координаты есть и у KBConcept, и у File, карта может показывать не просто точки из статей, а целую пространственную модель:

  • города;
  • достопримечательности;
  • фотографии, привязанные к месту;
  • маршруты;
  • объекты рядом;
  • факты с географическим контекстом;
  • пользовательские сохранения;
  • зоны интереса.

А агент может выполнять запросы уровня:

«Покажи интересные места в радиусе 15 км от Нячанга, куда можно доехать без байка и где есть смысл быть после 17:00».

Для такого запроса обычной статьи недостаточно. А эта схема потенциально позволяет собрать ответ из координат, фактов, связей, ограничений и предпочтений пользователя.

Проработать раздел Транспорт

13 июл. 2026 г.

Вот такая вот статистика у нас с начала года. Там провал был почти месяц до сегодня - это я сайт до этого переносил промежуточную версию и забыл счетчик подключить. Сейчас опять статистика пойдет.

Думаю, я сейчас очень быстро прокачаю его.

Прокачать SEO

13 июл. 2026 г.

Было много битых ссылок, в том числе те, что должны быть внутренними, вели на левые уже мертные сайты. Что поделать, сайт был куплен б/у с рук :-) Пришлось вот в испортер дописать чистку контента и переимпортировать весь контент.

export function cleanupImportingContent(
  content: string | undefined,
): string | undefined {
  if (!content) {
    return content
  }

  content = content.replaceAll('http://vietnamguru.ru/', '/')

  content = content.replaceAll('/city/hochiminh"', '/city/ho-chi-minh"')
  // /city/phan-thiet
  content = content.replaceAll(
    '/place/ho-chi-minh-museum"',
    '/place/ho-chi-minh-museum-phan-thiet"',
  )
  // /city/nha-trang
  content = content.replaceAll(
    '<a href="http://thaibase.ru/city/phatthaya">Паттайя</a>',
    'Паттайя',
  )

  // /city/phu-quoc
  content = content.replaceAll(
    '<a href="http://thaibase.ru/city/phuket">Пхукет</a>',
    'Пхукет',
  )

  // /city/cam-ranh
  content = content.replaceAll('/beach/bai-dai"', '/place/bai-dai"')

  // /city/hai-phong
  content = content.replaceAll(
    '<a href="http://vietnow.ru/city/ha-noi">столицы</a>',
    '<a href="/city/ha-noi">Ханоя</a>',
  )
  // /city/hai-phong
  content = content.replaceAll(
    '<a href="http://vietnow.ru/city/ha-long">Халонг</a>',
    '<a href="/city/ha-long">Халонг</a>',
  )

  // /city/bien-hoa
  content = content.replaceAll(
    '<a href="http://vietnow.ru/city/ho-chi-minh">Хошимин</a>',
    '<a href="/city/ho-chi-minh">Хошимин</a>',
  )
  // /city/bien-hoa
  content = content.replaceAll(
    '<a href="http://airportguru.ru/airport/SGN">Таншоннят</a>',
    'Таншоннят',
  )

  /**
   * Заменяем все ссылки, их видимо много
   */
  content = content.replaceAll('http://vietnow.ru/', '/')

  content = content.replaceAll(
    '<iframe width="560" height="315" src="https://www.youtube.com/embed/TTTsp1_5-b4" frameborder="0" allowfullscreen></iframe>',
    '',
  )

  content = content.replaceAll(
    'Фото и видео произошедшего вы можете посмотреть пройдя по этой <a href="https://goo.gl/G5pk5P"target="_blank">ССЫЛКЕ</a>.',
    '',
  )

  content = content.replaceAll(
    '<iframe width="560" height="315" src="https://www.youtube.com/embed/ze0apK_iDnI" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>',
    '',
  )

  content = content.replaceAll(
    'Все подробности о проведении фестиваля можно узнать на <a href="http://www.huefestival.com/" target="_blank">официальном сайте мероприятия</a>.',
    '',
  )

  content = content.replaceAll(
    'Кафе находится по адресу <a href="https://goo.gl/maps/jrAuqWtUzrK2" target="_blank">23 Ngo Van So</a>.',
    '',
  )

  content = content.replaceAll(
    `Например, такой вариант:
http://www.ebay.com/itm/Bien-Hoa-Ceramics-Perfect-GuanYin-Statue-/382015625988`,
    '',
  )

  content = content.replaceAll(
    `Подробнее об апартаментах вы можете прочитать на странице 
<a href="http://vietnamguru.ru/blog/questions-apartments-villas-mui-ne" target="_blank">Часто задаваемые вопросы по апартаментам (1, 2 или 3 спальни) и виллам (3 спальни) в Муйне</a>`,
    '',
  )

  /**
   * В некоторых местах в ссылках отсутствовали пробелы между атрибутами,
   * из-за чего они бились. Поэтому делаем две замены
   */
  content = content.replaceAll(' target="_blank"', '')
  content = content.replaceAll('target="_blank"', '')

  // /place/vinipearl-land-hon-tre-island

  return content
}

Прописать метатеги

11 июл. 2026 г.

Данные со старого сайта все импортировал.

Теперь надо базовые интерфейсы добавить, повторить всю старую систему УРЛов и выкатить сайта. После этого его можно будет уже дорабатывать.

Обновить сайт

11 июл. 2026 г.

Кстати, в некоторых маршрунах еще и данные битые.

"Can not get city2: \"cat-ba-island\"",
"Can not get city2: \"cat-ba-island\"",
"Can not get city2: \"cat-ba-island\"",
"Can not get city: \"cat-ba-island\"",
"Can not get city: \"cat-ba-island\"",
"Can not get city: \"cat-ba-island\"",
"Can not get city2: \"phong-nha-ke-bang\"",
"Can not get city2: \"nam-du-island\"",
"Can not get city2: \"nam-du-island\"",
"Can not get city2: \"nam-du-island\"",
"Can not get city: \"nam-du-island\"",
"Can not get city: \"nam-du-island\"",

Недо попутно завести.

Проработать раздел Транспорт

10 июл. 2026 г.

Система кастомных URL-маршрутов (SiteRoute)

Задача

Реализовать гибкую систему маршрутизации, позволяющую назначать произвольные URL для контента. Вместо технических путей вида /concepts/clxyz123abc — человекочитаемые /статьи/мой-первый-пост.

Архитектурное решение

Выбран подход с fallback-роутингом на уровне Next.js. Все запросы к несуществующим страницам перехватываются единой точкой входа, которая резолвит путь через базу данных.

Почему так:

  • Не требует перегенерации статики при добавлении новых маршрутов
  • Иерархическая структура через parentId — можно строить вложенные пути любой глубины
  • Связь 1:1 с контентом исключает дубликаты и проблемы с каноникализацией

Реализация

База данных — новая модель SiteRoute:

  • path — полный путь (уникальный индекс)
  • slug — сегмент пути
  • parentId — ссылка на родителя для иерархии
  • kBConceptId — привязка к концепту базы знаний
  • Составной индекс parentId + slug гарантирует уникальность slug в рамках родителя

Next.js — fallback rewrite в next.config.js перенаправляет /:path* на /_fallback/[...path].tsx.

Обработчик — получает текущий путь, запрашивает SiteRoute через GraphQL, рендерит соответствующую страницу или отдаёт 404.

Виртуальное поле uri — добавлено в KBConcept на уровне GraphQL-схемы. Возвращает кастомный путь если есть SiteRoute, иначе стандартный /concepts/{id}. Все компоненты ссылок автоматически используют это поле.

Технические детали

  • GraphQL резолверы: siteRoute, siteRoutes, createSiteRoute
  • Компоненты: ConceptLink, PostLink, TaskLink, WorkLogLink
  • SEO-заголовки: canonical URL формируется из uri
  • Preload данных на сервере для SSR

Результат

Движок haih-agent получил полноценную систему ЧПУ с поддержкой иерархии, без ограничений на структуру URL и с автоматической интеграцией во все существующие компоненты.

Добавить механику веб-страниц

9 июл. 2026 г.

Сделано

Сбор ошибок:

  • Фиксация всех HTTP-ошибок (404, 500 и т.д.)
  • Определение поисковых роботов (Google, Yandex, Bing и др.)
  • Сохранение контекста: URL, referer, user-agent

Редиректы:

  • Создание правил редиректов с тремя типами паттернов (точный, префикс, regex)
  • Автоматическое применение редиректов при ошибках
  • Поддержка 301/302 кодов

Группировка инцидентов:

  • Автоматическое объединение похожих ошибок в один инцидент
  • Статусы: открыт, решён, игнорируется

Документация:

  • Техническое описание системы
  • Статья для бизнеса о проблеме битых ссылок

Осталось

  • Интерфейс просмотра логов
  • Интерфейс управления редиректами
Добавить статистику заходов на страницы

9 июл. 2026 г.

Проблема ясна. Буржуйская контора openrouter отключила Россию от себя. То есть просто не проходят запросы на него. Сейчас подниму прокси и исправлю это.

Поправить работу агента

6 июл. 2026 г.

В общих чертах сделал.

Надо будет еще иконки добавить в зависимости от типа объекта, но сейчас это не критично, потому что все равно в основном поиск будет через ИИ-агента.

Вывести гео-объекты на карте

Написать мне