Skip to content

Раздел 5: База данных и REST API

Проект: ADOLF — AI-Driven Operations Layer Framework
Модуль: Watcher / Database & API
Версия: 4.0
Дата: Февраль 2026


5.1 Обзор

Collector использует SQLite (через better-sqlite3) как единственное хранилище структурированных данных. REST API (api.js) предоставляет HTTP-доступ к данным для Analyzer и внешних потребителей.

КомпонентФайлПроцессПорт
Databasedb.jsвнутри bot.js и api.js
REST APIapi.jswatcher-api.service3002

Файл БД: /opt/watcher/watcher.db (WAL-режим для производительности).


5.2 Схема базы данных

ER-диаграмма

Таблица sellers

Реестр отслеживаемых продавцов. Уникальность по паре (marketplace, seller_id).

ПолеТипПо умолчаниюОписание
idINTEGER PKautoВнутренний ID
marketplaceTEXT'wildberries'wildberries, ozon, yandex_market
seller_idTEXTID продавца на маркетплейсе
nameTEXTОтображаемое имя
priorityTEXT'normal'low, normal, high, critical (CHECK)
statusTEXT'active'active, paused, removed (CHECK)
seller_slugTEXT''Slug для YM URL (миграция)
schedule_hoursINTEGER72Интервал сканирования (часы)
last_scan_atTEXTNULLВремя последнего скана
next_scan_atTEXTNULLВремя следующего скана
enrich_schedule_hoursINTEGER0Интервал обогащения (0 = выключено, миграция)
last_enriched_atTEXTNULLВремя последнего обогащения (миграция)
next_enrich_atTEXTNULLВремя следующего обогащения (миграция)
created_atTEXTdatetime('now')Время создания

Автоматическое обновление расписаний при завершении задач:

  • next_scan_at = datetime('now', '+' || schedule_hours || ' hours') — после скана
  • next_enrich_at = datetime('now', '+' || enrich_schedule_hours || ' hours') — после обогащения (если enrich_schedule_hours > 0)

Таблица scans

Журнал всех задач (сканирование и обогащение).

ПолеТипПо умолчаниюОписание
idINTEGER PKautoID задачи
seller_rowidINTEGER FKСсылка на sellers.id
statusTEXT'queued'queued, running, completed, failed (CHECK)
task_typeTEXT'scan'scan или enrich (миграция)
sourceTEXT'auto'auto, manual, api (миграция)
started_atTEXTNULLВремя начала выполнения
completed_atTEXTNULLВремя завершения
duration_minutesREALNULLДлительность (минуты)
total_productsINTEGERNULLКоличество товаров / обогащённых SKU
price_minREALNULLМинимальная цена (только scan)
price_maxREALNULLМаксимальная цена (только scan)
avg_ratingREALNULLСредний рейтинг (только scan)
error_messageTEXTNULLТекст ошибки (при failed)
retry_countINTEGER0Счётчик повторов (инкрементируется при updateScanFailed)
result_fileTEXTNULLПуть к JSON-файлу результата
created_atTEXTdatetime('now')Время постановки в очередь

Жизненный цикл задачи:

Таблица products

Товары из каталога продавца (данные сканов). Каждый скан создаёт новый набор записей.

ПолеТипОписание
idINTEGER PKAuto
scan_idINTEGER FKСсылка на scans.id
seller_rowidINTEGER FKСсылка на sellers.id
skuTEXTАртикул товара на маркетплейсе
nameTEXTНазвание товара
priceREALТекущая цена
old_priceREALСтарая цена (до скидки)
discountREALПроцент скидки
ratingREALРейтинг товара
reviews_countINTEGERКоличество отзывов/оценок
badgesTEXTJSON-массив бейджей
urlTEXTURL карточки товара

Вставка через транзакцию db.transaction в runner.js для атомарности (все товары скана за один коммит).

Таблица product_details

Обогащённые данные товаров. UPSERT по уникальному ключу (seller_rowid, sku) — при повторном обогащении данные перезаписываются.

ПолеТипОписание
idINTEGER PKAuto
seller_rowidINTEGER FKСсылка на sellers.id
skuTEXTАртикул товара
scan_idINTEGER FKСсылка на scans.id (enrich-задача)
nameTEXTПолное название (миграция)
sale_priceREALАктуальная цена продажи
feedbacksINTEGERКоличество отзывов
sale_countINTEGERКоличество продаж
sizesTEXTJSON: массив размеров с остатками по складам
total_stockINTEGERСуммарный остаток
descriptionTEXTОписание товара
characteristicsTEXTJSON: характеристики/опции
compositionsTEXTJSON: состав
imagesTEXTJSON: массив URL изображений
seller_nameTEXTЮридическое имя продавца
seller_ogrnTEXTОГРН/ОГРНИП продавца
data_jsonTEXTПолный JSON обогащённых данных (миграция)
enriched_atTEXTВремя обогащения

Таблица price_history

История изменения цен. Запись создаётся при каждом скане для каждого товара с ненулевой ценой.

ПолеТипОписание
idINTEGER PKAuto
seller_rowidINTEGER FKСсылка на sellers.id
skuTEXTАртикул товара
scan_idINTEGER FKСсылка на scans.id
priceREALТекущая цена
old_priceREALСтарая цена
discountREALПроцент скидки
recorded_atTEXTВремя записи

Индекс: idx_ph_seller_sku ON price_history(seller_rowid, sku, recorded_at).

Таблица assignments

История назначений задач на ПК. Используется оркестратором для скоринга (см. Раздел 3).

ПолеТипОписание
idINTEGER PKAuto
scan_idINTEGER FKСсылка на scans.id
pc_portINTEGERПорт CDP на VPS
pc_nameTEXTИмя ПК
marketplaceTEXTМаркетплейс задачи
seller_idTEXTID продавца
started_atTEXTВремя начала
completed_atTEXTВремя завершения

Таблица events

Лог системных событий (мониторинг, завершение задач, ошибки).

ПолеТипОписание
idINTEGER PKAuto
typeTEXTТип: scan_start, scan_complete, scan_failed, enrich_start, enrich_complete, enrich_failed, pc_connected, pc_disconnected
messageTEXTТекст события
created_atTEXTВремя события

5.3 Миграции

БД использует inline-миграции через try { ALTER TABLE ... } catch {}. Каждая миграция добавляет поле, которое может не существовать в ранних версиях БД.

МиграцияТаблицаПолеПо умолчанию
1sellersseller_slug''
2scanstask_type'scan'
3sellerslast_enriched_atNULL
4sellersenrich_schedule_hours0
5sellersnext_enrich_atNULL
6scanssource'auto'
7product_detailsdata_jsonNULL
8product_detailsnameNULL

Миграции идемпотентны: повторный запуск ничего не меняет (ALTER TABLE выбрасывает ошибку «duplicate column», которая перехватывается).


5.4 Prepared Statements

Модуль db.js экспортирует 30+ prepared statements. Группы:

Продавцы (9 statements)

StatementМетодОписание
addSeller.run()INSERT OR IGNORE нового продавца
getSellers.all()Все продавцы (кроме removed), сортировка: priority → next_scan_at
getSeller.get()Поиск по (seller_id, marketplace)
getSellerById.get()Поиск по id
updateSellerStatus.run()Изменение статуса
updateSellerPriority.run()Изменение приоритета
updateSellerSchedule.run()Изменение schedule_hours
updateSellerLastScan.run()Обновление last_scan_at + next_scan_at
updateSellerLastEnrich.run()Обновление last_enriched_at + next_enrich_at

Дополнительно: updateSellerEnrichSchedule, updateSellerEnrichScheduleById, getSellersNeedingEnrich.

Задачи (12 statements)

StatementМетодОписание
createScan.run()Создать scan-задачу (queued)
createEnrichScan.run()Создать enrich-задачу (queued)
getScan.get()Получить задачу по ID
getQueuedScans.all()Очередь scan-задач (приоритет → время)
getNextQueued.get()Следующая задача из очереди (LIMIT 1)
getRunningScans.all()Выполняемые scan-задачи
getQueuedEnrichScans.all()Очередь enrich-задач
getRunningEnrichScans.all()Выполняемые enrich-задачи
updateScanRunning.run()queued → running + started_at
updateScanCompleted.run()running → completed + метрики
updateScanFailed.run()running → failed + error + retry_count++
getRecentScans.all()Последние N задач (для /history)

Дополнительно: getLastScanForSeller, getFailedScans.

Товары и обогащение (8 statements)

StatementМетодОписание
insertProduct.run()Вставка товара из скана
getProductsByScan.all()Все товары скана
getLastTwoScansProducts.all()Товары двух последних сканов (для diff)
insertProductDetail.run()UPSERT обогащённых данных
getProductDetailsBySeller.all()Все обогащённые товары продавца
getProductDetailBySku.get()Обогащённые данные по SKU
getProductDetailBySkuAndMp.get()Обогащённые данные по SKU + маркетплейс
insertPriceHistory.run()Запись в историю цен

Дополнительно: getPriceHistory, getPriceHistoryBySeller, getPriceHistoryBySku.

Инфраструктура (6 statements)

StatementМетодОписание
addEvent.run()Запись события
getStats.get()Агрегированная статистика системы
insertAssignment.run()Запись назначения ПК
completeAssignment.run()Завершение назначения
getLastAssignmentForPC.get()Последнее назначение ПК (для скоринга)
getAssignmentCountForPC.get()Количество задач ПК за 24ч

Дополнительно: getRecentAssignmentsForPC.


5.5 REST API (api.js)

HTTP-сервер на порту 3002 (localhost). Запускается отдельным процессом (watcher-api.service).

Общие характеристики

ПараметрЗначение
Фреймворкhttp (stdlib Node.js)
ФорматJSON
РоутингШаблоны с :param
CORSAccess-Control-Allow-Origin: *
Базовый путь/api/v1
АутентификацияНет (localhost)

Эндпоинты

Продавцы

МетодПутьQuery paramsОписание
GET/api/v1/sellersmarketplace, statusСписок продавцов (фильтрация)
GET/api/v1/sellers/:idДетали продавца + последний скан
POST/api/v1/sellersСоздание продавца
PUT/api/v1/sellers/:idОбновление (name, priority, status, schedule_hours, enrich_schedule_hours)
DELETE/api/v1/sellers/:idМягкое удаление (status → removed)

POST /api/v1/sellers body:

json
{
  "marketplace": "wildberries",
  "seller_id": "1025130",
  "name": "Конкурент А",
  "priority": "high"
}

Товары продавца

МетодПутьОписание
GET/api/v1/sellers/:id/productsТовары последнего скана
GET/api/v1/sellers/:id/enrichedОбогащённые товары
GET/api/v1/sellers/:id/diffСравнение двух последних сканов (computeDiff)

Задачи

МетодПутьQuery paramsОписание
GET/api/v1/scansseller_id, status, limitСписок задач (фильтрация)
GET/api/v1/scans/:idДетали задачи + товары
POST/api/v1/scansСоздание scan-задачи
POST/api/v1/enrichmentsСоздание enrich-задачи

POST body для обоих:

json
{
  "seller_id": "1025130",
  "marketplace": "wildberries"
}

Товары по SKU

МетодПутьQuery paramsОписание
GET/api/v1/products/:skumarketplaceОбогащённые данные товара
GET/api/v1/products/:sku/enrichedmarketplaceАналог (совместимость)
GET/api/v1/products/:sku/historylimitИстория цен (по умолчанию 100 записей)

Файлы

МетодПутьОписание
GET/api/v1/files/catalogСписок файлов каталогов
GET/api/v1/files/enrichedСписок файлов обогащения
GET/api/v1/files/:type/:filenameСкачивание JSON-файла

Защита от path traversal: проверка на .., /, \ в имени файла. Допустимые типы: catalog, enriched.

Инфраструктура

МетодПутьОписание
GET/api/v1/pool/statusПроксирование CDP Pool /status
GET/api/v1/pool/summaryПроксирование CDP Pool /summary
GET/api/v1/tasksТекущие задачи: running, queued, enrich_running, enrich_queued
GET/api/v1/statsСистемная статистика: продавцы, задачи, товары, обогащения, история цен

Формат ответа /stats

json
{
  "active_sellers": 12,
  "queued": 3,
  "running": 1,
  "completed": 287,
  "failed": 2,
  "total_products": 15420,
  "enriched_products": 8930,
  "price_history_records": 47820
}

Коды ответов

КодСитуация
200Успех
201Создано (POST)
204CORS preflight
400Ошибка валидации
404Ресурс не найден
500Внутренняя ошибка
502CDP Pool недоступен

5.6 Файловое хранилище

Помимо SQLite, Collector хранит JSON-файлы результатов на диске.

Структура директорий

/opt/watcher/results/
├── results_seller_1025130.json      # Текущий скан (перезаписывается)
├── enriched_seller_1025130.json     # Текущее обогащение (перезаписывается)
├── catalog/                         # Архив сканов (версионированные копии)
│   ├── wildberries_seller_1025130_2026-02-12.json
│   ├── wildberries_seller_1025130_2026-02-14.json
│   └── ozon_seller_465656_2026-02-13.json
└── enriched/                        # Архив обогащений
    ├── wildberries_seller_1025130_2026-02-12.json
    └── ozon_seller_465656_2026-02-13.json

Жизненный цикл файлов

ФайлСоздаётсяПерезаписываетсяРотация
results_seller_<id>.jsonСканером (промежуточно и финально)При каждом сканеНет (текущий)
enriched_seller_<id>.jsonОбогатителемПри каждом обогащенииНет (текущий)
catalog/<mp>_seller_<id>_<date>.jsonRunner (копия)Нет (уникальное имя)catalogKeepPerSeller (10)
enriched/<mp>_seller_<id>_<date>.jsonRunner (копия)Нет (уникальное имя)enrichedKeepPerSeller (30)

Ротация выполняется планировщиком каждые 24 часа (см. Раздел 2, цикл 5).


5.7 Потоки данных

Запись данных при скане

Запись данных при обогащении

Чтение данных через API


Документ подготовлен: Февраль 2026
Версия: 4.0
Статус: Черновик

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