Симптом
Живой прогон на cc5ce70 (3 фразы, Россия, --keep-raw). Для фразы «подарки» сохранённый top_popular.csv содержит related-заголовок:
| файл |
размер |
заголовок |
CLI top_popular.csv |
191 Б |
Запросы, похожие на "подарки"… ← related |
CLI top_related.csv |
191 Б |
Запросы, похожие на "подарки"… |
ручной экспорт wordstat_top_queries.csv |
198 Б |
Топ частотных запросов "подарки"… ← popular |
Для фразы «новогодние подарки детям» оба файла — 223 Б с related-заголовком.
Для «новогодние подарки» заголовки правильные (219 Б popular / 212 Б related).
То есть <view>.parquet и <view>.csv могут описывать не тот отчёт, имя которого носят, и происходит это не всегда — зависит от фразы.
Почему это не «пустые топы»
Отдельно проверено и не является багом: топовые виды на живом Wordstat экспортируются header-only даже при непустом UI (200 строк в таблице «подарки»). Ручной экспорт даёт ровно то же самое. Это поведение Wordstat, оно уже задокументировано в collector.py (_is_untrustworthy_empty_export, «TOP_POPULAR … is always empty on live Wordstat»). Данная issue не про пустоту, а про подмену вида.
Механизм (гипотеза)
VIEW_SELECTORS (collector.py:57):
WordstatView.TOP_POPULAR: "label[for='table']",
WordstatView.TOP_RELATED: "label:has(#associations)",
Оба топовых вида живут в одной таблице. _select_view подтверждает переключение тремя признаками: input.checked, наличие кнопки скачивания и TABLE_ROW_SELECTOR.length > 0. Ни один из них не описывает содержимое будущего экспорта — только состояние контролов и наличие строк.
Проверка «текст таблицы изменился» существует, но она required=False и явно помечена в комментарии как corroborating evidence, а не гейт — потому что разные виды могут иметь одинаковую первую строку. В результате: radio уже переключён, строки в DOM есть, а blob экспорта всё ещё принадлежит предыдущему виду — скачивается related под именем popular.
Это тот же класс дефекта, что #3, #13, #21, #22, #26, #31 и три ревью в PR #39: проверяется соседний сигнал вместо искомого. Здесь искомое — принадлежность экспорта виду, а проверяется состояние переключателя.
Почему тесты молчат
row_count=0 у обоих топов одинаков независимо от того, какой заголовок попал в файл. Заголовки Wordstat локализованы и намеренно не валидируются по именам (csv_io.py) — так что подмена вида не отражается ни в одной проверке. Манифест показывает top_popular row_count 0 и выглядит нормально.
Что нужно
- Воспроизвести: несколько фраз подряд,
--keep-raw, сверить заголовок каждого <view>.csv с ожидаемым видом. Зафиксировать, от чего зависит (порядок видов, скорость перерисовки, конкретная фраза).
- Найти признак, описывающий сам экспорт, а не контролы: заголовок скачанного CSV — первый кандидат, он приходит от Wordstat и прямо называет отчёт.
- Проверка принадлежности вида после скачивания, fail-closed: если заголовок не соответствует запрошенному виду — не записывать parquet под этим именем.
- Осторожно с п. 3: заголовки локализованы и Wordstat их меняет без предупреждения. Жёсткая привязка к строке — новый источник тихих поломок. Нужен признак, отличающий виды, но не ломающийся от переформулировки; варианты обсудить до реализации.
Данные прогона
Каталог: /tmp/wordstat-issue-38-one-1788155996, коммит cc5ce70, monthly, дефолтное окно UI, timeout 45.
UI на момент проверки: «подарки» → top_popular 200 строк (подарок 4 583 689), top_related 20 строк (подарить 3 586 247).
Симптом
Живой прогон на
cc5ce70(3 фразы, Россия,--keep-raw). Для фразы «подарки» сохранённыйtop_popular.csvсодержит related-заголовок:top_popular.csvЗапросы, похожие на "подарки"…← relatedtop_related.csvЗапросы, похожие на "подарки"…wordstat_top_queries.csvТоп частотных запросов "подарки"…← popularДля фразы «новогодние подарки детям» оба файла — 223 Б с related-заголовком.
Для «новогодние подарки» заголовки правильные (219 Б popular / 212 Б related).
То есть
<view>.parquetи<view>.csvмогут описывать не тот отчёт, имя которого носят, и происходит это не всегда — зависит от фразы.Почему это не «пустые топы»
Отдельно проверено и не является багом: топовые виды на живом Wordstat экспортируются header-only даже при непустом UI (200 строк в таблице «подарки»). Ручной экспорт даёт ровно то же самое. Это поведение Wordstat, оно уже задокументировано в
collector.py(_is_untrustworthy_empty_export, «TOP_POPULAR … is always empty on live Wordstat»). Данная issue не про пустоту, а про подмену вида.Механизм (гипотеза)
VIEW_SELECTORS(collector.py:57):Оба топовых вида живут в одной таблице.
_select_viewподтверждает переключение тремя признаками:input.checked, наличие кнопки скачивания иTABLE_ROW_SELECTOR.length > 0. Ни один из них не описывает содержимое будущего экспорта — только состояние контролов и наличие строк.Проверка «текст таблицы изменился» существует, но она
required=Falseи явно помечена в комментарии как corroborating evidence, а не гейт — потому что разные виды могут иметь одинаковую первую строку. В результате: radio уже переключён, строки в DOM есть, а blob экспорта всё ещё принадлежит предыдущему виду — скачивается related под именем popular.Это тот же класс дефекта, что #3, #13, #21, #22, #26, #31 и три ревью в PR #39: проверяется соседний сигнал вместо искомого. Здесь искомое — принадлежность экспорта виду, а проверяется состояние переключателя.
Почему тесты молчат
row_count=0у обоих топов одинаков независимо от того, какой заголовок попал в файл. Заголовки Wordstat локализованы и намеренно не валидируются по именам (csv_io.py) — так что подмена вида не отражается ни в одной проверке. Манифест показываетtop_popular row_count 0и выглядит нормально.Что нужно
--keep-raw, сверить заголовок каждого<view>.csvс ожидаемым видом. Зафиксировать, от чего зависит (порядок видов, скорость перерисовки, конкретная фраза).Данные прогона
Каталог:
/tmp/wordstat-issue-38-one-1788155996, коммитcc5ce70, monthly, дефолтное окно UI, timeout 45.UI на момент проверки: «подарки» → top_popular 200 строк (
подарок4 583 689), top_related 20 строк (подарить3 586 247).