Data API (gwptd-collector)
HTTP/JSON граница, через которую mp.hyp.ru (PHP) читает собранные данные вместо прямого доступа к БД (вводная Михаила). Контракт: ../docs/DESIGN-2026-06-08.md §3 + ../docs/DATA-API-REPORTS.md.
Статус: автономный read-only сервис S3 NEXT. Он читает локальные
gwptd_intake/gwptd_kernel/gwptd_monitoring; сервисный Bearer token ограничиваетdata:read,status:readиcontrol:read.
Запуск (dev)
cd data_api
python3 -m venv .venv && . .venv/bin/activate
pip install -r requirements.txt
# env: V3_MYSQL_HOST/PORT/USER/PASSWORD (как у коллектора, :3406). Локально auth выключен, если DATA_API_TOKENS пуст.
uvicorn main:app --reload --port 8080
# Swagger: http://localhost:8080/docs ; health: /health
Что реализовано (живые срезы, по одному из каждого семейства)
| Endpoint | Источник |
|---|---|
GET /api/v1/status/freshness | gwptd_intake.collection_run (последний run на endpoint + last_success) |
GET /api/v1/status/collection-runs (+ /{run_id}) | gwptd_intake.collection_run (строгие фильтры, bounded limit) |
GET /api/v1/status/parser-errors | gwptd_intake.parser_error × collection_run; raw payload не выдаётся |
GET /api/v1/status/reconciliation (+ /{check_id}) | локальный gwptd_monitoring.cutover_reconciliation_log |
GET /api/v1/facts/stocks-daily | gwptd_kernel.fact_stocks_daily (keyset-пагинация по snapshot_date,id) |
GET /api/v1/control/endpoints (+ /{code}) | gwptd_monitoring.api_endpoint_registry (SELECT *, устойчив к pre/post-ALTER) |
GET /api/v1/control/field-maps | DB-проекция api_field_map; при её отсутствии — валидированный declared-каталог из 79 ProductSpec с явным provenance |
GET /health | SELECT 1 |
Оставшийся каркас (stub → 501)
/facts/{orders,sales,prices-daily,finance,storage-daily,promotions-daily,analytics-daily},
/dims/*, неизвестные /reports/*, /compat/mp-*-daily и control-plane
мутации. Служебные status/field-map чтения больше не являются заглушками.
Классификация ошибок источника (errors.py)
db.fetch_* классифицирует ошибки MySQL по errno (план spec-driven §4.13 R2, quick-win):
| Класс | Когда | Наружу |
|---|---|---|
OptionalSourceMissing | нет таблицы/колонки/гранта на optional-зеркале s2_ref/s20_ref или dark gwptd_core_compat | ловится except MIRROR_ERRORS → прежний fallback/degraded; allowlisted core-reader повышает ошибку обратно до mandatory |
MandatorySourceError | те же errno на обязательной схеме (kernel и др.); 1045 креды | 500 SOURCE_ERROR + лог |
QuerySourceError | прочие ошибки SQL (1064 syntax, …) | 500 QUERY_ERROR + лог |
SourceUnavailable | соединение/timeout (CR 2xxx, 1040, 1205, 3024) | 503 SOURCE_UNAVAILABLE, data_status=unavailable |
Плюс глобальный handler на неожиданные исключения → 500 INTERNAL (envelope, не голый traceback).
Итог: сломанный SQL и упавшее соединение больше НЕ маскируются под пустой деградированный 200
(DNR-007). data_status расширен значениями degraded/unavailable (зеркально в
app/Support/DataStatus.php).
Согласованное чтение одного ответа (P1B-5, dark)
DATA_API_READ_COHERENCE_MODE управляет request-scoped чтением:
off(default) сохраняет прежнее отдельное соединение на каждыйdb.fetch_*;observeлениво открывает один read-onlyREPEATABLE READ ... WITH CONSISTENT SNAPSHOT, но до первого business-query может откатиться на прежние чтения, если MySQL не дал запустить snapshot; после начала чтения ошибка cleanup уже фатальна и успешный ответ не выпускается;enforceтребует snapshot и завершает запрос ошибкой, если его невозможно открыть или корректно закрыть.
Snapshot применяется ко всем синхронным /api/v1/*-роутам через
SnapshotAPIRoute. Auth/validation выполняются раньше и не открывают БД.
Соединение открывается, используется, откатывается и закрывается в одном AnyIO
worker thread; async/generator/streaming/background endpoints этим route-class
запрещены. Режим остаётся off до отдельного P1B activation gate.
Перед активацией enforce нужен отдельный runtime-attestation: каждая реально
читаемая таблица во всех kernel/intake/monitoring и временных mirror-схемах
должна быть InnoDB. Этот patch такой live-инвентарь не подменяет и не считает
P1B-5 активированным.
Переход product reader на canonical core
DATA_API_PRODUCT_READ_SOURCE управляет только обогащением карточкой товара;
исторические факты продолжают использовать свой неизменный kernel.model_id:
kernel— дефолт, поведение и форма ответа не меняются;shadow_observe— ответ остаётся kernel, а в лог попадают только агрегатные counts покрытия/расхождения сgwptd_core_compat, без артикулов и названий;core_allowlist— core-view используется только маршрутами, перечисленными вDATA_API_CORE_PRODUCT_ROUTE_ALLOWLIST. Пустой allowlist оставляет все маршруты на kernel.
Первый поддержанный маршрут — reports.onec-stock: переключается только join
бренда, тогда как остаток, склад и дата среза остаются в 1С intake. Отсутствие
dark-схемы в observe-режиме логируется и не меняет ответ; в allowlist-режиме
это честная обязательная ошибка без молчаливого fallback.
Файлы
main.py— app, роутеры, error-envelope, health, глобальные хендлеры таксономии.config.py— БД (env V3_MYSQL_*), схемы, лимиты, токены/скоупы.db.py— read-only pymysql (DictCursor), в стиле collector/db/connection.py; классифицирует ошибки через errors.py и переиспользует request snapshot.read_coherence.py— synchronousSnapshotAPIRoute, lazy RR snapshot и same-thread cleanup.errors.py— таксономия ошибок источника (см. выше).envelope.py— data/error-обёртки + cursor (base64 JSON).auth.py— Bearer + scope (data:read/status:read/control:read/control:write/run:write).routers/— status, facts, dims, control, reports, compat.
Прод-заметки
- БД-юзер для data/status-линий должен быть read-only grant (отдельно от
collector-writer). На S3 Next это
gwptd_next_reader; phase 80 добавляет ему один точныйSELECTдля dark core compatibility seam и fail-closed проверяет, что у него нет других прямых raw/stage/core/core-compat прав. - Кэш/ETag/
kernel_snapshot_atдля report-эндпоинтов — см. DATA-API-REPORTS.md (Phase 3 продолжение). - Auth обязателен в проде: задать
DATA_API_TOKENS.