Перейти к основному содержимому

Разбор

Замер от: 2026-08-14 · чем проверено: ZAMER-KARTY-BLOKOV-2026-08-14.md, ZAMER-BAZY-2026-08-14.md, прогон проверок разбора — 25 passed, 0 skipped Для кого: кто добавляет целевую таблицу приёма, меняет развёртку (explode) или чинит «ответ получен, а строк нет». Читать до первой правки в spec_runtime/.

Что делает

Превращает ответ площадки в строки целевых таблиц gwptd_intake. В действующем контуре это часть СБОРА: внутри цикла после fetch() сырой ответ сохраняется через save_raw_payload, затем тот же процесс вызывает _buffer_row и _write_legacy (marketplace-collector-v3/collectors/base.py, строки 789–813). Отдельная пара next_raw_collector.py (неизменяемый захват в gwptd_raw) + next_parser.py (новое поколение разбора из запечатанного захвата) написана, но в расписании отсутствует: if grep -qE 'next_raw_collector|next_parser' marketplace-collector-v3/scripts/crontab.s3-next.txt; then echo 'найдено'; else echo 0; fi → 0. Поэтому правка только next_parser.py боевой разбор не изменит: сейчас нужно проверять parse_and_store из spec_runtime/parsed_runner.py и описания, которые компилирует spec_runtime/product_spec.py. В двухпроходном контуре разбор можно повторить без нового обращения к площадке; действующий контур этого отдельного шага не запускает.

Точка входа в код

marketplace-collector-v3/collectors/base.py, метод run_collection() — цикл действующего однопроходного сбора; из него вызывается parse_and_store текущего разборщика.

Чем задаётся

Секция outputs: каждого описания specs/products/*.yaml. Две формы:

  • outputs.intakeByKind — маршрутизация по значению поля field (например _kind): у каждого вида свои цели targets[kind]. Если она есть, писатель ходит только по targets[kind] (parsed_runner.py, строки 62–81), а явный outputs.intake для записи не используется.
  • outputs.intake — единственная цель для всех строк описания.

Ловушка 11.08.2026, якорь — комментарий в specs/products/goldapple.snapshot.yaml, строки 39–44: при наличии intakeByKind явный intake молчит, и ga_raw не получила ни строки, хотя дочерние таблицы наполнялись исправно. При компиляции явный intake сохраняет своё explode; без него — синтезируется из первой цели первого вида с отбросом explode (product_spec.py, строки 1250–1260: intake.pop("explode", None) на 1259).

Какие правила обязан соблюдать

  • Статусы — по канону STATUS-SEMANTICS.md; формулы не трогать без DO-NOT-REGRESS.md.
  • У BaseCollector два пути записи. Действующий SpecCollector наследует supports_batch_write = False, поэтому _buffer_row сразу вызывает parse_and_store (base.py, строки 1025–1046). Отдельные коллекторы с supports_batch_write = True собирают строки в буфер (base.py, строки 1048–1085), а затем _flush_buffer передаёт их в executemany (base.py, строки 1087–1140).
  • rows_parsed считается по записанному, а не по положенному в буфер — честность счётчика под тестом (test_rows_parsed_honesty.py).
  • _buffer_row пишет разобранную строку в целевую таблицу gwptd_intake: пакетно через буфер либо сразу старым построчным путём. _write_legacy — отдельная необязательная запись по legacy_mapping_path в старую lamoda_reports; она включается только при V3_LEGACY_MYSQL_HOST, а ошибка там лишь журналируется и не валит приём. Штатная обёртка s3 удаляет эту переменную, поэтому живой Next пишет только в приём (run_collector.sh).
  • Схему цели менять нельзя в обход миграций ядра — связь описаний и таблиц держит check_intake_schema_contract.py.
  • Раздельный контур сбора и разбора пока является следующим этапом, а не действующим путём. Он написан, но миграции и учётные записи на сервере не применены и в расписание он не включён. Включение выделено в этап IV-1 дорожной карты: сначала одна площадка и сверка с действующим контуром, затем расширение. Его цель — повторный разбор сохранённого сырья без нового обращения к площадке (ROADMAP.md:254-268).

Чем проверяется

  • python3 -m pytest marketplace-collector-v3/tests/test_buffered_write.py marketplace-collector-v3/tests/test_rows_parsed_honesty.py marketplace-collector-v3/tests/test_spec_goldapple.py::test_product_spec_maps_snapshot_to_existing_ga_raw_shape marketplace-collector-v3/tests/test_spec_goldapple.py::test_ga_raw_leaf_fields_render_values_and_die_on_broken_path -q — 25 passed, 0 skipped (прогнал 14.08).
  • if grep -qE 'next_raw_collector|next_parser' marketplace-collector-v3/scripts/crontab.s3-next.txt; then echo 'найдено'; else echo 0; fi — 0: раздельная пара не в расписании, разбор идёт в одном проходе.

Чего делать нельзя

  • Ставить явный outputs.intake рядом с intakeByKind и считать, что он пишет: при наличии intakeByKind для записи используется только она (см. якорь в goldapple.snapshot.yaml).
  • Забывать explode-семантику: без явного intake развёртка цели теряется (product_spec.py, строки 1250–1260).
  • Править разборщик так, чтобы его rows_parsed рос от строк в буфере, а не от записанных — тест честности не пропустит.
  • Трогать отгрузку и миграции ядра без решения владельца (общее для системы, RULES.md).

Что НЕ установлено

  • Покрытие парсера тестами целиком — не установлено. Запуск всего набора сборщика не является замером покрытия каждого модуля; поимённое соответствие «модуль парсера → выполняющий его тест» не считалось, а часть тестов требует отдельной базы и пропускается без неё.

Куда за подробностью

  • СБОР — как ответ доходит до разбора.
  • ХРАНЕНИЕ — куда попадают разобранные строки.
  • INTAKE-RAZBOR-STATUS.md — семантика статусов приёма и разбора.
  • ENDPOINTS-CATALOG.md — описания адресов, откуда приходят ответы.