Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Benz AI

Минимальный веб-агрегатор доступности бензина на АЗС. Пользователь вводит город, район или регион России, а приложение показывает сводку по территории и список станций. Сервер запрашивает источники сам, поэтому браузер не упирается в CORS.

Интерфейс содержит сводку, интерактивную карту АЗС с кластеризацией, таблицу и мобильные карточки, строку поиска и мультивыбор видов топлива и статусов через выпадающие списки с галочками. Карта при разрешении браузера открывается около местоположения пользователя и после остановки перемещения загружает станции для видимой области с запасом. Малые сдвиги внутри загруженной рамки не создают новых запросов; при дальнейшем перемещении догружаются только открывшиеся полосы, а прежние маркеры сохраняются до выхода за более широкую буферную область. При далёком масштабе нужно приблизить карту. Цвет маркера показывает вероятностный статус, а карточка на карте — объединённые источники, согласованность сигналов, топливо, цены и свежесть. Карта и список используют одни фильтры, но таблица остаётся результатом текстового поиска. Viewport-запрос потоково отдаёт накопленные АЗС после ответа каждого источника, поэтому маркеры появляются постепенно до завершения всей догрузки. Ожидание каждого источника ограничено 12 секундами; последовательное обогащение ценами Яндекса не запускается. Полный поиск территории ожидает каждый основной источник не более 8 секунд, медленный браузерный Sber — не более 2 секунд, а ценовую проверку Яндекса — не более 4 секунд. Успешная частичная сводка кратковременно кэшируется вместе с предупреждениями, поэтому недоступный источник не заставляет повторный поиск снова ждать полный тайм-аут. Браузер прекращает ожидание карты через 18 секунд и показывает повторяемую ошибку вместо бесконечного индикатора. Таблица поддерживает сортировку по каждой колонке, пагинацию по 10/25/50/100 строк, липкую шапку и вертикальную прокрутку; колонки помещаются по ширине и перетаскиваются мышью. По умолчанию показываются 25 строк. Фильтры и поиск возвращают пользователя на первую страницу. Размер окна таблицы изменяется за правый нижний угол. Размер, прокрутка, порядок колонок, город, поиск, фильтры, сортировка, страница и количество строк сохраняются в браузере и восстанавливаются после F5; данные повторно берутся из серверного кэша. При переходе в таблицу загрузка скрытой карты приостанавливается. Возврат на карту восстанавливает найденную территорию и заново загружает только актуальную видимую область, не используя запрос со старыми границами скрытой панели. Если в найденной территории нет АЗС, карта всё равно переходит к её bbox, а не оставляет на экране предыдущий район. Кнопка «Обновить весь кэш» сбрасывает данные источников и заново собирает текущую территорию.

Запуск

npm start

Откройте http://localhost:3000.

Telegram

Скопируйте .env.example в .env, задайте TELEGRAM_BOT_TOKEN и включите polling:

TELEGRAM_POLLING_ENABLED=true
TELEGRAM_BOT_TOKEN=123456789:real-token-goes-here

После npm start бот принимает /start, /help и название города или региона. Поиск проходит через ту же серверную функцию и те же источники, что и веб-версия; бот возвращает компактную сводку, а веб — полную таблицу и фильтры. .env не попадает в git. Статус polling доступен в GET /api/health без раскрытия токена. При старте бот обновляет описание профиля и меню команд. В /help приведены примеры поиска города, района города, района области и населённого пункта с уточнением региона. Сайт и ответы бота показывают короткий хеш и дату текущего коммита. При локальном запуске метаданные читаются из Git. Docker-образ не копирует историю репозитория: передайте GIT_COMMIT_SHA и GIT_COMMIT_DATE как build args (Compose читает их из одноимённых переменных окружения). Если они не заданы, интерфейс корректно скрывает неизвестные Git-метаданные.

Каждый новый коммит автоматически увеличивает patch-версию приложения через отслеживаемый .githooks/pre-commit и scripts/bump-version.js. Один раз после клонирования включите проектные hooks командой git config core.hooksPath .githooks. Повторный запуск неудавшегося коммита не увеличивает уже подготовленную версию ещё раз. Текущая версия синхронно хранится в package.json и package-lock.json и показывается вместе с хешем.

Статистика посещений

Сайт и Telegram-бот записывают обезличенные события в PostgreSQL, если заданы DATABASE_URL и ANALYTICS_HASH_SALT. Сырые IP-адреса, Telegram ID, имена и тексты поиска не сохраняются. Недоступность базы статистики не блокирует поиск АЗС или ответы бота.

DATABASE_URL=postgresql://user:password@host:5432/database
DATABASE_SSL=0
# Только для сервера с заведомо самоподписанным сертификатом:
DATABASE_SSL_INSECURE=0
ANALYTICS_HASH_SALT=replace-with-a-long-random-secret
STATS_ADMIN_TOKEN=replace-with-a-long-random-admin-token

После запуска откройте /admin.html и введите STATS_ADMIN_TOKEN. Сводка за 30 дней показывает просмотры и уникальных посетителей сайта, поиски, события веб-роботов, сообщения и уникальных пользователей Telegram-бота. Таблица и индексы с префиксом benz_analytics_ создаются автоматически при подключении.

Docker

Образ включает Node.js 22 и Chromium для Sber browser-worker. Секреты в образ не копируются. Compose читает локальный .env, но передаёт контейнеру только явно разрешённые переменные Benz AI; посторонние значения из файла не попадают в runtime.

$env:GIT_COMMIT_SHA = (git rev-parse HEAD)
$env:GIT_COMMIT_DATE = (git show -s --format=%cI HEAD)
docker compose build
docker compose up -d
docker compose ps
Invoke-RestMethod -Uri http://127.0.0.1:3000/api/health

Если внешний порт 3000 занят, задайте BENZ_AI_HOST_PORT только для Compose-команды или в локальном .env; внутри контейнера приложение всегда слушает 3000. Пока используется Telegram long polling, запускайте только одну реплику приложения: несколько реплик с одним bot token будут конкурировать за updates. Остановка:

docker compose down

Compose не создаёт тома и не удаляет внешние Docker-ресурсы. Временный профиль Chromium находится в container tmpfs и исчезает при остановке контейнера.

Источники

  • T-Bank Fuel — активный источник: GET /api/v1/stations с границами карты.
  • Alfa AZS — активный источник вероятностного наличия и опубликованных цен. Endpoint возвращает общероссийский снимок, поэтому приложение кэширует его на 60 секунд и самостоятельно оставляет точки внутри найденной территории. Защитная HTTP-проверка завершается лёгкой серверной cookie-сессией без запуска дополнительного Chromium. Установите ENABLE_ALFA_AZS=0, чтобы полностью отключить запросы к источнику.
  • BenzUp — лицензируемый источник каталога АЗС и цен. Включается через BENZUP_API_TOKEN; адрес API по умолчанию — https://api.omt-consult.ru/v2/stations.
  • Multigo — активный источник ближайших объектов категории АЗС: POST https://multigo.ru/api/9/near/list с JSON-полями lat, lng и limit. Он дополняет каталог, но его общий статус не трактуется как наличие топлива. В выдачу попадают только точки внутри найденной территории; чистые ЭЗС отбрасываются. Поскольку передаваемый API идентификатор может меняться между запросами, приложение использует устойчивый отпечаток координат, адреса и названия.
  • Яндекс Карты — проверка карточек всех АЗС со статусом «Вероятно есть», для которых T‑Bank передал yandexOrgId. Яндекс остаётся отдельным источником цен: успешная проверка отображается среди источников АЗС, но не подтверждает фактическое наличие топлива. Установите ENABLE_YANDEX_PRICES=0, чтобы отключить запросы, либо положительное YANDEX_PRICE_LIMIT, чтобы ограничить число проверок за поиск.
  • OpenStreetMap Nominatim — определяет координаты, bbox и административный GeoJSON-контур введённого города или региона. Итоговая сводка отсекает точки за пределами контура; если контур не предоставлен, безопасно используется bbox. Запросы кэшируются на 24 часа и ограничены одним в секунду.
  • Sber AZS — API станций проверен через настоящий браузерный сеанс: /api/stations?bbox=minLon,minLat,maxLon,maxLat возвращает JSON после выполнения JavaScript-проверки. Приложение запускает один скрытый Chromium-worker в headless-режиме без окна GUI, хранит cookies только во временном профиле и обновляет активные поисковые области каждые 60 секунд.
  • ГдеБЕНЗ — активный краудсорсинговый источник наличия, очередей и лимитов: GET https://gdebenz.ru/api/nearby?lat=...&lon=...&radius_km=.... Запрашивается только при пользовательском поиске и кэшируется на 60 секунд; фонового массового обхода регионов нет. Из радиусного ответа сохраняются только точки внутри найденной территории. Статусы остаются пользовательскими мнениями и показываются со свежестью и подтверждениями.

Конфигурация источников

$env:BENZUP_API_TOKEN = "<полученный Bearer token>"
$env:DEEPSEEK_API_KEY = "<серверный API-ключ>" # только для функций, которые явно используют providers/deepseek.js
$env:ENABLE_ALFA_AZS = "0" # необязательно: полностью отключает Alfa AZS
$env:ENABLE_YANDEX_PRICES = "0" # необязательно: отключает Яндекс Карты
$env:YANDEX_PRICE_LIMIT = "30" # необязательно: ограничивает число проверок
$env:SUMMARY_PROVIDER_TIMEOUT_MS = "8000" # бюджет ожидания каждого основного источника
$env:SBER_SUMMARY_TIMEOUT_MS = "2000" # отдельный бюджет медленного браузерного источника
$env:YANDEX_SUMMARY_TIMEOUT_MS = "4000" # общий бюджет ценового обогащения сводки
$env:CHROME_PATH = "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
npm start

Подготовленный адаптер DeepSeek использует DEEPSEEK_BASE_URL (по умолчанию https://api.deepseek.com), DEEPSEEK_MODEL (по умолчанию deepseek-chat) и DEEPSEEK_TIMEOUT_MS (по умолчанию 60000). Геокодер сначала проверяет исходный запрос и обращается к нормализатору только при неоднозначном результате; этот вызов ограничен LOCATION_NORMALIZER_TIMEOUT_MS (по умолчанию 2000). Адаптер приводит свободный запрос к однозначному названию населённого пункта или административной территории; городской запрос вида «Воронеж Советский» преобразуется в название района с контекстом города и региона. Лимит ответа задаёт DEEPSEEK_NORMALIZER_MAX_TOKENS (по умолчанию 2000). Результат модели обязательно подтверждается Nominatim, а при недоступности DeepSeek используется исходная формулировка. Ключ остаётся только на сервере и не должен попадать в браузер, исходный код или логи.

BENZUP_API_URL позволяет заменить базовый endpoint, если BenzUp выдаст другой адрес в договоре. Токены не должны попадать в исходный код, браузер или логи. CHROME_PATH необязателен на Windows, если Chrome или Edge установлен в стандартную папку. Для browser-worker требуется Node.js 22 или новее. Worker создаётся по первому поиску, обслуживает до 10 активных областей, удаляет неактивные через 15 минут и очищает временный профиль при штатной остановке. CHROME_NO_SANDBOX=1 является ослаблением защиты и не используется при обычном локальном запуске. Штатный Compose включает его явно, потому что Chromium уже работает непривилегированно в read-only контейнере с no-new-privileges, которое несовместимо с setuid sandbox. Публичные HTTP-запросы ограничиваются параметрами REQUEST_RATE_*; для /api/cache/refresh действует отдельный более строгий лимит. Очередь Nominatim дополнительно ограничена GEOCODER_QUEUE_MAX, чтобы входящие запросы не могли создать неограниченный backlog при обязательной паузе между геокодированиями.

Для публичного размещения задайте идентифицирующий User-Agent с контактами:

$env:GEOCODER_USER_AGENT = "BenzAI/1.0 (contact: admin@example.ru)"

API приложения

  • GET /api/location?q=Воронеж%20Советский — быстро определяет территорию и её bbox без ожидания источников АЗС. Интерфейс сразу фокусирует карту и запускает потоковую загрузку видимой области параллельно с полной сводкой.
  • GET /api/summary?q=Воронежская%20область — территория, полная сводка и АЗС.
  • GET /api/stations?minLat=...&maxLat=...&minLon=...&maxLon=... — нормализованные АЗС и предупреждения по источникам для текущей области карты. Для карты добавляется mode=viewport; одна сторона bbox не должна превышать 12 градусов.
  • GET /api/stations/stream?...&mode=viewport — NDJSON-поток кумулятивных снимков АЗС; клиент карты обновляет маркеры после каждой строки, не ожидая завершения всех источников.

При текстовом поиске /api/location и /api/summary запускаются параллельно. После ответа геокодера карта начинает получать /api/stations/stream, поэтому первые маркеры появляются до готовности полной сводки. Финальная сводка объединяется с уже показанными станциями без очистки карты.

Ответ содержит sources с состоянием каждого провайдера. У станции поля fuelStatus и overallStatus относятся к вероятностному наличию, а prices и priceUpdatedAt — к цене. Эти сигналы намеренно не подменяют друг друга.

GET /api/health показывает состояние сервера и Chromium-worker Sber.

POST /api/cache/refresh?q=Воронеж очищает общую сводку, геокодирование, Multigo, ГдеБЕНЗ, Sber и цены Яндекса, затем возвращает полностью пересобранный ответ с метаданными cacheRefresh.

Если ответ T-Bank достигает предполагаемого лимита в 300 записей, сервер автоматически делит территорию на меньшие части и удаляет дубликаты. Поэтому поиск подходит не только для городов, но и для областей.

Ответы T-Bank не являются официально документированным контрактом. Интерфейс поэтому показывает вероятностные формулировки статусов, а не гарантию наличия топлива.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages