From fe285a8379ff3b539b918b5b376e9cc2d244d0c2 Mon Sep 17 00:00:00 2001 From: axisrow Date: Fri, 21 Aug 2026 12:39:07 +0700 Subject: [PATCH 1/5] docs: record issue 6 live granularity measurements --- docs/ISSUE6_PHASE1_RESEARCH.md | 175 +++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 docs/ISSUE6_PHASE1_RESEARCH.md diff --git a/docs/ISSUE6_PHASE1_RESEARCH.md b/docs/ISSUE6_PHASE1_RESEARCH.md new file mode 100644 index 0000000..1d80161 --- /dev/null +++ b/docs/ISSUE6_PHASE1_RESEARCH.md @@ -0,0 +1,175 @@ +# Issue #6 — фаза 1: живые замеры + +Дата замеров: **2026-08-21**. Интерфейс: `https://wordstat.yandex.ru/`, +авторизованный Chrome через CDP `http://127.0.0.1:9223`, фраза +`курсы английского языка`, регион `Россия`. Chrome не закрывался. Это только +исследование: `collector.py`, `wordstat.toml` и код приложения не менялись. + +## Краткий результат + +Блокирующая для потребителя дневная детализация доступна. В текущем +интерфейсе она автоматически выставила окно **22.06.2026—20.08.2026** и +показала сообщение, что дневная статистика доступна только за 60 дней. +Окно в 56 дней целиком входит в это окно. Недельная детализация выдаёт +полные календарные недели понедельник—воскресенье. + +## Подтверждено руками + +### Формат `Период` + +После переключения `Динамика` → `По дням` я скачал именно пункт +`Скачать` → `Таблицу (CSV)`. Файл имеет следующие байтовые свойства: + +* UTF-8; +* BOM `EF BB BF` в начале; +* разделитель полей `;`; +* терминатор строк — одиночный `CR` (`0D`), без `LF`; +* 59 строк всего (заголовок + 58 данных) и 177 разделителей `;`. + +Первые байты после BOM, декодированные UTF-8: + +```text +Дата;Число запросов;Доля от всех запросов, %;Динамика частотности запросов «курсы английского языка», по дням, 22.06.2026 — 19.08.2026, Россия, все устройства\r +22.06.2026;1 680;0,00047;\r +23.06.2026;1 626;0,00047; +``` + +Главное расхождение с DOM: в **файле** поле называется `Дата`, а значение +имеет формат `DD.MM.YYYY`, например `22.06.2026`. Год в выгрузке есть. +DOM показывал локализованное короткое представление `22 июня`, поэтому DOM +нельзя использовать как описание CSV. + +После переключения на `По неделям` я скачал второй CSV тем же способом. Его +байтовые свойства: UTF-8, BOM `EF BB BF`, разделитель `;`, одиночный `CR` +(`0D`) без `LF`, 9 строк всего (заголовок + 8 данных). Файл начинается так: + +```text +Неделя с;Число запросов;Доля от всех запросов, %;Динамика частотности запросов «курсы английского языка», по неделям, 22.06.2026 — 16.08.2026, Россия, все устройства\r +22.06.2026;9 799;0,00042;\r +29.06.2026;8 839;0,00039; +``` + +В **файле** недельное поле называется `Неделя с` и содержит только дату +начала недели в формате `DD.MM.YYYY`; диапазона и номера недели нет. По DOM +та же строка отображалась как диапазон `22 июня 2026 – 28 июня 2026`. + +### Реакция окна на детализацию + +При смене `По месяцам` на `По дням` интерфейс сам изменил окно с месячного на +дневное и показал дословно: + +> Диапазон дат был изменён: статистика «по дням» доступна только для 60 дней. + +Результат: `22.06.2026 — 20.08.2026` (60 календарных дней включительно). +Следовательно, окно из 56 дней доступно целиком. При смене на недели окно +стало `22.06.2026 — 16.08.2026` и появилось сообщение: + +> Диапазон дат был изменён: статистика «по неделям» доступна только для полных календарных недель. + +### Типизация по фактическим значениям + +Фактические значения прогнаны через `src/wordstat/dtypes.py:parse_number`: + +| Значение | Результат | +|---|---:| +| `22 июня` | `NotImplemented` | +| `22 июня 2026 – 28 июня 2026` | `NotImplemented` | +| `1 680` | `1680` (`int`) | +| `0,00047` | `0.00047` (`float`) | +| `9 799` | `9799` (`int`) | +| `0,00042` | `0.00042` (`float`) | + +Дополнительно проверены значения **из скачанных файлов**: `parse_number` +возвращает `NotImplemented` для `22.06.2026` и для обоих заголовков +(`Дата`, `Неделя с`). Это подтверждает, что фактические даты не станут +числами. + +`infer_column(["22 июня", "23 июня"])` и +`infer_column(["22 июня 2026 – 28 июня 2026", "29 июня 2026 – 5 июля 2026"])` +вернули `None`: оба периода остаются строковой колонкой. + +### Селекторы и уникальность + +Проверка выполнялась в консоли страницы через +`document.querySelectorAll(SEL).length`: + +| Контрол | CSS-селектор | count | +|---|---|---:| +| Выпадающий список детализации | `.wordstat__content-type_select > button` | 1 | +| Диапазон дат | `.range-datepicker__selected-dates > button` | 1 | +| Кнопка скачивания | `.save-button` | 1 | + +После раскрытия списка детализации реально видны ровно три опции: `По дням`, +`По неделям`, `По месяцам`. Для строгого гейта интерфейса пригодны первые два +селекторы; селектор кнопки скачивания проверен в том же состоянии. + +### Нижняя граница + +В месячном календаре виден год 2018, а при выборе `Январь 2018` интерфейс +автоматически установил `Январь 2018 — Март 2018` и показал дословно: + +> Диапазон дат был изменён: минимальный период для построения графика с выбранной детализацией — три календарных месяца. + +## Взято из справки, не проверено руками + +Официальная [справка Яндекса по новому интерфейсу Wordstat](https://yandex.ru/support2/wordstat/ru/interface/new) +утверждает: для месячной и недельной детализации диапазон — от трёх месяцев +или трёх недель до пяти лет; самая ранняя дата — январь 2018; для дней — до +60 дней от текущей даты. В ходе этого замера я подтвердил нижнюю границу +январь 2018 и месячный минимум три месяца, но **не подтвердил руками предел +пяти лет**. Поэтому пять лет не следует пока превращать в локальную +валидацию как проверенный факт. + +## Минимальные окна и эксперимент с пятью годами + +### День — подтверждено руками + +При выборе дневной детализации из более короткого недельного окна интерфейс +выставил полный доступный дневной диапазон `22.06.2026 — 20.08.2026` и +показал сообщение про доступность только 60 дней. Попытка выбрать отдельный +день в календаре не завершила второй клик: календарь оставался в состоянии +выбора диапазона. Поэтому это подтверждает **верхнюю границу 60 дней**, но не +минимум одного дня. Минимум дневного окна остаётся ниже непроверенного. + +### Неделя — подтверждено руками частично + +При переходе из дневной детализации в недельную интерфейс автоматически +перевёл окно в `27.07.2026 — 16.08.2026`, то есть ровно три полные недели. +Попытка выбрать одну неделю отдельным вторым кликом не завершилась: выбор +даты в уже выделенном диапазоне не менял окно. Это живое подтверждение +автоматического минимума в наблюдённом переходе, но не самостоятельный +эксперимент с одной неделей. Официальная справка ниже говорит о трёх +неделях; в отчёте это оставлено как справочное подтверждение, а не выдано +за полностью завершённый UI-тест. + +### Пять лет — не подтверждено; причина зафиксирована + +Я выбрал `Январь 2018`: интерфейс автоматически поставил минимум +`Январь 2018 — Март 2018`. Далее при переходе через селектор года popup +закрывался после выбора года, а последующий клик месяца менял начало +текущего диапазона вместо завершения выбора конца. Поэтому получить +однозначное состояние «январь 2018 → позже пяти лет» не удалось; факта о +пределе пяти лет из этого эксперимента нет. + +## Не проверено + +* Минимум дневного окна (отдельный день) и независимый выбор одной недели — + эксперименты не завершились из-за поведения календаря, описанного выше. +* Фактический отказ/усечение при попытке задать месячный диапазон длиннее + пяти лет — UI-эксперимент неоднозначен, доказательства нет. + +## Вывод для фазы 2 + +Дневной и недельный периоды нельзя валидировать как числа по аналогии с +гипотетическим `01.2024`: фактические даты `DD.MM.YYYY` отвергаются строгой +регуляркой чисел и остаются строками. Важно не перепутать DOM и выгрузку: +в DOM дневное значение теряет год, а CSV его содержит; недельный DOM рисует +диапазон, а CSV хранит дату начала. При реализации управляемого окна +нужно поддержать произвольные даты, учитывать автоматическое ограничение +дней 60 и для недель передавать/выбирать полные календарные недели. + +Отдельный подтверждённый факт из текущего дерева: `src/wordstat/dtypes.py` +описывает несуществующий продовый формат `01.2024`, а +`tests/test_dtypes.py` и `tests/test_dataset_io.py` закрепляют его. Реальный +месячный формат — `август 2024`; исправление docstring, фикстуры и регрессии +оставлено фазе 2 и в этот PR не включено. From 89bbb24512dd8e0e8f6ba16aa67a75f6052546ff Mon Sep 17 00:00:00 2001 From: axisrow Date: Fri, 21 Aug 2026 13:15:56 +0700 Subject: [PATCH 2/5] docs: record variable daily data tail --- docs/ISSUE6_PHASE1_RESEARCH.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/ISSUE6_PHASE1_RESEARCH.md b/docs/ISSUE6_PHASE1_RESEARCH.md index 1d80161..6e97e3b 100644 --- a/docs/ISSUE6_PHASE1_RESEARCH.md +++ b/docs/ISSUE6_PHASE1_RESEARCH.md @@ -158,6 +158,29 @@ DOM показывал локализованное короткое предс * Фактический отказ/усечение при попытке задать месячный диапазон длиннее пяти лет — UI-эксперимент неоднозначен, доказательства нет. +## Уточнение о хвосте дневного ряда + +Последний доступный день не является фиксированным лагом сервиса. По +сравнению запросов, переданному после первоначального замера, наблюдались +разные глубины хвоста: иногда в выгрузке есть вчерашний день, иногда +отсутствуют последние четыре дня. Поэтому нельзя зашивать в потребителе или +в collector константу «лаг N дней» и нельзя считать короткий хвост ошибкой. + +Для исходного замера 21.08 заголовок CSV заявлял период до 19.08, а реальные +строки заканчивались 18.08. При дополнительной проверке на том же аккаунте +были сопоставлены 10 общих дат с ранее сохранённой выгрузкой — значения не +изменились. Это не является проверкой задним числом: замеры сделаны в тот же +день и не дают временного среза, по которому можно установить, растут ли +значения уже выгруженных дат позже. Вопрос «отсутствуют последние дни или +присутствуют заниженными» остаётся неустановленным. + +Безопасный контракт для потребителя: хвост и его глубина определяются только +по фактическим строкам файла; последние даты нужно читать из ряда и отрезать +по факту, а не по константе. Пустой хвост делает ряд короче заявленного, но +не создаёт искусственного спада; заниженные значения в присутствующем хвосте +были бы отдельным риском тихой порчи данных и требуют отдельного временного +сравнения. + ## Вывод для фазы 2 Дневной и недельный периоды нельзя валидировать как числа по аналогии с From bc62b8b19bb8dcea4c488b99070519a49e258bdf Mon Sep 17 00:00:00 2001 From: axisrow Date: Fri, 21 Aug 2026 13:56:05 +0700 Subject: [PATCH 3/5] =?UTF-8?q?docs:=20record=20phase=202=20findings=20?= =?UTF-8?q?=E2=80=94=20weekly=20period=20rejected,=20backfill=20open,=20de?= =?UTF-8?q?ad=20code=20removed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds phase 2 findings to the phase 1 live-measurement document: - dtypes.py guards confirmed updated to real 'август 2024'/'22.06.2026' values; the _NUMERIC regex itself was not touched. - Weekly dynamics with an explicit period is not a partial UI glitch: Wordstat ignores the requested window entirely and returns its own default ~2-year range (107 full weeks) regardless of what was asked. validate_period() now rejects the combination outright. - Weekly dynamics without an explicit period was confirmed live to collect predictably (107 rows, factual period from CSV rows). - The now-unreachable weekly date-picker branch in collector.py was removed rather than left dead, collapsing a duplicated month_label computation into one shared function for month/day popups. - Backfill remains explicitly unestablished: the only comparison done was same-day, so it neither confirms nor rules out later revision of already-exported values. Flagged as an open question for a future time-separated remeasurement. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01JCyJr8vFFDW1eSMyCQyNNk --- docs/ISSUE6_PHASE1_RESEARCH.md | 116 +++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/docs/ISSUE6_PHASE1_RESEARCH.md b/docs/ISSUE6_PHASE1_RESEARCH.md index 6e97e3b..4148615 100644 --- a/docs/ISSUE6_PHASE1_RESEARCH.md +++ b/docs/ISSUE6_PHASE1_RESEARCH.md @@ -196,3 +196,119 @@ DOM показывал локализованное короткое предс `tests/test_dtypes.py` и `tests/test_dataset_io.py` закрепляют его. Реальный месячный формат — `август 2024`; исправление docstring, фикстуры и регрессии оставлено фазе 2 и в этот PR не включено. + +## Обновление по итогам фазы 2 (реализация и живая проверка) + +### `dtypes.py`: guards заменены на реальные значения + +Подтверждено по коду `src/wordstat/dtypes.py`, `tests/test_dtypes.py` и +`tests/test_dataset_io.py`: докстринг, комментарии и регрессионные стражи +теперь ссылаются на реально наблюдённые на живом Wordstat значения — +`"август 2024"` (месячный период) и `"22.06.2026"` (дневная/недельная дата) — +вместо гипотетического продового формата `01.2024`, который использовался в +фазе 1 только как пример аргумента для строгости регулярки. Сама регулярка +`_NUMERIC = re.compile(r"^-?[0-9]+(?:,[0-9]+)?$")` в `dtypes.py` не менялась: +правка затронула только docstring/комментарии и фикстуры тестов, логика +типизации осталась прежней. + +### Недельная детализация с явным периодом: не UI-баг, а дефолтное окно — запрещена + +Живой прогон через CDP (`--resume-dir`-трюк, минуя fail-closed на +`top_popular`; см. раздел про backfill ниже про сам метод проверки) +подтвердил: попытка передать `--granularity weekly` вместе с явным +`--date-from`/`--date-to` не частично не применяется, а **полностью +игнорируется**. Полученное окно — `29.07.2024 — 16.08.2026` (по заголовку +CSV) — это не сбой пикера и не искажение запрошенного диапазона, а ровно +**дефолтное окно Wordstat** длиной 107 полных календарных недель (~2.05 +года), не зависящее от того, что было запрошено. Попытка почитать этот +результат как «частично применённый» период была бы ошибкой: правильная +интерпретация — период не применился вообще. + +Причина на уровне DOM/пикера не установлена и намеренно не расследовалась +дальше — цена эксперимента с недельным calendar popup (см. раздел «Пять +лет — не подтверждено» выше) уже показала, что попытки завершить выбор +диапазона через недельный popup утыкаются в непредсказуемое поведение +календаря (клик по месяцу/году не завершает выбор диапазона предсказуемо). +Правдоподобная гипотеза — недельный режим требует выбора выровненных по +неделе дат через другой пикер-виджет, чем day/month — но это осталось +гипотезой, не проверенным фактом. + +**Решение фазы 2**: `validate_period()` в `src/wordstat/periods.py` теперь +безусловно отклоняет `Granularity.WEEKLY` с любым явным +`date_from`/`date_to` через `InvalidPeriodError`, независимо от длины +запрошенного окна — не существует такой длины периода, при которой Wordstat +станет его учитывать, так что валидация отклоняет комбинацию целиком, а не +пытается угадать безопасную границу. Это осознанно дороже для пользователя +(потеря функциональности), но дешевле, чем продолжать чинить недельный +пикер без диагностированной причины, и уж тем более дешевле, чем самое +плохое из возможных поведений — молча вернуть данные не за тот период, +пока манифест утверждает обратное. + +### Мёртвый код после запрета: убран, пикер остался одной общей функцией + +Запрет явного weekly-периода на уровне `validate_period()` сделал +недостижимым код `_set_period`/`_select_calendar_date`, написанный под +недельный calendar popup (`popup_type == "week"`): единственный вызывающий +путь для WEEKLY туда больше не доходит. Эта ветка убрана, а не оставлена +неиспользуемой — заодно устранено дублирование, которое было в исходном +`if popup_type == "month": ... else: ...`: вычисление `month_label` +повторялось в обеих ветках идентично. `_select_calendar_date` остаётся +именно той единой функцией, которую и просила issue #6 — «переключение +контролов грануляции и периода... по тем же правилам» — просто теперь она +обслуживает два реально достижимых случая (`month`, `day`), а не три, один +из которых никогда не мог быть вызван. + +### Недельная детализация без явного периода: работает предсказуемо + +Отдельно проверено, что `--granularity weekly` **без** `--date-from`/ +`--date-to` (принимающая дефолтное окно Wordstat как есть) собирается +предсказуемо и без описанной выше проблемы — предсказуемости здесь просто +неоткуда взяться иначе, раз период вообще не запрашивался. Живой прогон +(фраза «ремонт квартир», регион «Москва») дал: + +* 107 строк, `manifest.actual_period` (взят из первой/последней строки + CSV, не из заголовка) — `{"field": "Неделя с", "from": "29.07.2024", + "to": "10.08.2026"}`; +* заголовок CSV внутри данных заявляет более широкий диапазон + (`...16.08.2026`), чем фактические строки (до `10.08.2026`) — тот же + паттерн «заголовок ≠ факт», что и в дневном ряде (см. раздел про хвост + ниже); манифест берёт факт, не заголовок, поэтому это не расхождение с + контрактом; +* dtypes: `"Неделя с"` осталась строкой, числовые колонки типизировались + верно. + +Эта комбинация (`weekly` + без периода) остаётся поддерживаемой; запрет +из предыдущего раздела касается только явного периода. + +### Backfill (дозаполнение задним числом): по-прежнему неустановлено + +Вопрос «дособираются ли значения хвоста задним числом после более позднего +снятия» остаётся открытым, и вывод здесь намеренно не форсируется. Ранее +проведённое сравнение (10 общих дат между двумя выгрузками) было сделано +**в рамках одного дня** — обе выгрузки сняты 21.08 без временного разрыва +между ними, значения совпали, но это не доказывает ни то, что backfill +происходит, ни то, что его нет: у сравнения просто не было среза, +разделённого достаточным временем, чтобы увидеть изменение уже +выгруженного значения. Формулировка «сравнение в рамках одного дня, более +позднего среза нет» — точное и окончательное описание состояния этого +вопроса на конец фазы 2, а не промежуточный шаг к выводу. + +Это помечено как открытый вопрос, требующий отдельного повторного замера +через несколько дней (сравнить значения тех же дат/недель в двух выгрузках, +разделённых временем, а не в рамках одной сессии) — кто-то сделает это +позже, отдельно от этой фазы. + +### Метод живой проверки динамики в фазе 2 + +Полный CLI-прогон (`wordstat collect`) не мог использоваться для проверки +динамики: `top_popular` у Wordstat пуст всегда (задокументированное +свойство платформы, issue #11), и fail-closed на пустых top-экспортах +(влитый PR #19) корректно останавливает прогон на первом виде до того, как +он доходит до `dynamics`. Ослаблять fail-closed или обходить его флагом не +предполагалось и не делалось. Вместо этого динамика проверялась напрямую: +`--resume-dir` с вручную подготовленным `manifest.json`, где +`top_popular`/`top_related`/`regions` уже отмечены собранными (с +заглушечными `.parquet`-файлами), так что `collect_many` реально запрашивал +у живого Wordstat только вид `dynamics`. Это законная точечная проверка +конкретного пути коллектора, а не прогон полного CLI поверх настоящих +данных остальных видов. From 553d0e636b11eb2883d669ea9b967dc4c16e5a13 Mon Sep 17 00:00:00 2001 From: axisrow Date: Fri, 21 Aug 2026 14:07:07 +0700 Subject: [PATCH 4/5] =?UTF-8?q?Revert=20"docs:=20record=20phase=202=20find?= =?UTF-8?q?ings=20=E2=80=94=20weekly=20period=20rejected,=20backfill=20ope?= =?UTF-8?q?n,=20dead=20code=20removed"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit bc62b8b19bb8dcea4c488b99070519a49e258bdf. --- docs/ISSUE6_PHASE1_RESEARCH.md | 116 --------------------------------- 1 file changed, 116 deletions(-) diff --git a/docs/ISSUE6_PHASE1_RESEARCH.md b/docs/ISSUE6_PHASE1_RESEARCH.md index 4148615..6e97e3b 100644 --- a/docs/ISSUE6_PHASE1_RESEARCH.md +++ b/docs/ISSUE6_PHASE1_RESEARCH.md @@ -196,119 +196,3 @@ DOM показывал локализованное короткое предс `tests/test_dtypes.py` и `tests/test_dataset_io.py` закрепляют его. Реальный месячный формат — `август 2024`; исправление docstring, фикстуры и регрессии оставлено фазе 2 и в этот PR не включено. - -## Обновление по итогам фазы 2 (реализация и живая проверка) - -### `dtypes.py`: guards заменены на реальные значения - -Подтверждено по коду `src/wordstat/dtypes.py`, `tests/test_dtypes.py` и -`tests/test_dataset_io.py`: докстринг, комментарии и регрессионные стражи -теперь ссылаются на реально наблюдённые на живом Wordstat значения — -`"август 2024"` (месячный период) и `"22.06.2026"` (дневная/недельная дата) — -вместо гипотетического продового формата `01.2024`, который использовался в -фазе 1 только как пример аргумента для строгости регулярки. Сама регулярка -`_NUMERIC = re.compile(r"^-?[0-9]+(?:,[0-9]+)?$")` в `dtypes.py` не менялась: -правка затронула только docstring/комментарии и фикстуры тестов, логика -типизации осталась прежней. - -### Недельная детализация с явным периодом: не UI-баг, а дефолтное окно — запрещена - -Живой прогон через CDP (`--resume-dir`-трюк, минуя fail-closed на -`top_popular`; см. раздел про backfill ниже про сам метод проверки) -подтвердил: попытка передать `--granularity weekly` вместе с явным -`--date-from`/`--date-to` не частично не применяется, а **полностью -игнорируется**. Полученное окно — `29.07.2024 — 16.08.2026` (по заголовку -CSV) — это не сбой пикера и не искажение запрошенного диапазона, а ровно -**дефолтное окно Wordstat** длиной 107 полных календарных недель (~2.05 -года), не зависящее от того, что было запрошено. Попытка почитать этот -результат как «частично применённый» период была бы ошибкой: правильная -интерпретация — период не применился вообще. - -Причина на уровне DOM/пикера не установлена и намеренно не расследовалась -дальше — цена эксперимента с недельным calendar popup (см. раздел «Пять -лет — не подтверждено» выше) уже показала, что попытки завершить выбор -диапазона через недельный popup утыкаются в непредсказуемое поведение -календаря (клик по месяцу/году не завершает выбор диапазона предсказуемо). -Правдоподобная гипотеза — недельный режим требует выбора выровненных по -неделе дат через другой пикер-виджет, чем day/month — но это осталось -гипотезой, не проверенным фактом. - -**Решение фазы 2**: `validate_period()` в `src/wordstat/periods.py` теперь -безусловно отклоняет `Granularity.WEEKLY` с любым явным -`date_from`/`date_to` через `InvalidPeriodError`, независимо от длины -запрошенного окна — не существует такой длины периода, при которой Wordstat -станет его учитывать, так что валидация отклоняет комбинацию целиком, а не -пытается угадать безопасную границу. Это осознанно дороже для пользователя -(потеря функциональности), но дешевле, чем продолжать чинить недельный -пикер без диагностированной причины, и уж тем более дешевле, чем самое -плохое из возможных поведений — молча вернуть данные не за тот период, -пока манифест утверждает обратное. - -### Мёртвый код после запрета: убран, пикер остался одной общей функцией - -Запрет явного weekly-периода на уровне `validate_period()` сделал -недостижимым код `_set_period`/`_select_calendar_date`, написанный под -недельный calendar popup (`popup_type == "week"`): единственный вызывающий -путь для WEEKLY туда больше не доходит. Эта ветка убрана, а не оставлена -неиспользуемой — заодно устранено дублирование, которое было в исходном -`if popup_type == "month": ... else: ...`: вычисление `month_label` -повторялось в обеих ветках идентично. `_select_calendar_date` остаётся -именно той единой функцией, которую и просила issue #6 — «переключение -контролов грануляции и периода... по тем же правилам» — просто теперь она -обслуживает два реально достижимых случая (`month`, `day`), а не три, один -из которых никогда не мог быть вызван. - -### Недельная детализация без явного периода: работает предсказуемо - -Отдельно проверено, что `--granularity weekly` **без** `--date-from`/ -`--date-to` (принимающая дефолтное окно Wordstat как есть) собирается -предсказуемо и без описанной выше проблемы — предсказуемости здесь просто -неоткуда взяться иначе, раз период вообще не запрашивался. Живой прогон -(фраза «ремонт квартир», регион «Москва») дал: - -* 107 строк, `manifest.actual_period` (взят из первой/последней строки - CSV, не из заголовка) — `{"field": "Неделя с", "from": "29.07.2024", - "to": "10.08.2026"}`; -* заголовок CSV внутри данных заявляет более широкий диапазон - (`...16.08.2026`), чем фактические строки (до `10.08.2026`) — тот же - паттерн «заголовок ≠ факт», что и в дневном ряде (см. раздел про хвост - ниже); манифест берёт факт, не заголовок, поэтому это не расхождение с - контрактом; -* dtypes: `"Неделя с"` осталась строкой, числовые колонки типизировались - верно. - -Эта комбинация (`weekly` + без периода) остаётся поддерживаемой; запрет -из предыдущего раздела касается только явного периода. - -### Backfill (дозаполнение задним числом): по-прежнему неустановлено - -Вопрос «дособираются ли значения хвоста задним числом после более позднего -снятия» остаётся открытым, и вывод здесь намеренно не форсируется. Ранее -проведённое сравнение (10 общих дат между двумя выгрузками) было сделано -**в рамках одного дня** — обе выгрузки сняты 21.08 без временного разрыва -между ними, значения совпали, но это не доказывает ни то, что backfill -происходит, ни то, что его нет: у сравнения просто не было среза, -разделённого достаточным временем, чтобы увидеть изменение уже -выгруженного значения. Формулировка «сравнение в рамках одного дня, более -позднего среза нет» — точное и окончательное описание состояния этого -вопроса на конец фазы 2, а не промежуточный шаг к выводу. - -Это помечено как открытый вопрос, требующий отдельного повторного замера -через несколько дней (сравнить значения тех же дат/недель в двух выгрузках, -разделённых временем, а не в рамках одной сессии) — кто-то сделает это -позже, отдельно от этой фазы. - -### Метод живой проверки динамики в фазе 2 - -Полный CLI-прогон (`wordstat collect`) не мог использоваться для проверки -динамики: `top_popular` у Wordstat пуст всегда (задокументированное -свойство платформы, issue #11), и fail-closed на пустых top-экспортах -(влитый PR #19) корректно останавливает прогон на первом виде до того, как -он доходит до `dynamics`. Ослаблять fail-closed или обходить его флагом не -предполагалось и не делалось. Вместо этого динамика проверялась напрямую: -`--resume-dir` с вручную подготовленным `manifest.json`, где -`top_popular`/`top_related`/`regions` уже отмечены собранными (с -заглушечными `.parquet`-файлами), так что `collect_many` реально запрашивал -у живого Wordstat только вид `dynamics`. Это законная точечная проверка -конкретного пути коллектора, а не прогон полного CLI поверх настоящих -данных остальных видов. From 4be8996e3ae23871d617cdfcc6fed104e6a796a3 Mon Sep 17 00:00:00 2001 From: axisrow Date: Fri, 21 Aug 2026 14:33:42 +0700 Subject: [PATCH 5/5] docs: correct phase 1 with the real cause -- our readiness check, not the platform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier PR #21 revert removed a section claiming Wordstat ignores an explicit weekly period. That claim was wrong: it came from a live prompt that never actually passed date_from/date_to (requested_period was null in that run's own log), so the default window it observed was default because no period was requested, not because Wordstat rejected one. A user-provided live screenshot of the UI (24.12.2018 -- 13.01.2019, weekly, applied by hand) disproved the claim directly. The real defect was in our own _wait_for_table_granularity: it checked only that the first cell's format matched the granularity, which the table's pre-existing (stale) content already satisfied before a period change had actually been applied -- a silent wrong-period export, not a platform limitation. A first fix attempt had its own bug: comparing the table's genitive month text ("24 декабря") against the nominative RUSSIAN_MONTHS list used for calendar clicks ("декабрь") never matched. Documents, with live verification: - weekly period applies exactly (24.12.2018..07.01.2019 for a requested 24.12.2018..13.01.2019 -- 07.01 is the start of the last full week ending 13.01, not truncation) - daily period applies with the same known variable trailing tail already documented above in this file (29/30 rows, not a new defect) - the live-check method (collect_many + --resume-dir, dynamics-only, since top_popular's fail-closed still blocks a full CLI run) - the lesson: DOM control state and actual table/export content can lag each other on live Wordstat (same class of issue as #3, for period instead of view switching) Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01JCyJr8vFFDW1eSMyCQyNNk --- docs/ISSUE6_PHASE1_RESEARCH.md | 93 ++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/docs/ISSUE6_PHASE1_RESEARCH.md b/docs/ISSUE6_PHASE1_RESEARCH.md index 6e97e3b..be6b82d 100644 --- a/docs/ISSUE6_PHASE1_RESEARCH.md +++ b/docs/ISSUE6_PHASE1_RESEARCH.md @@ -196,3 +196,96 @@ DOM показывал локализованное короткое предс `tests/test_dtypes.py` и `tests/test_dataset_io.py` закрепляют его. Реальный месячный формат — `август 2024`; исправление docstring, фикстуры и регрессии оставлено фазе 2 и в этот PR не включено. + +## Обновление по итогам фазы 2: недельный период применяется, баг был в нашей проверке готовности + +Первая версия фазы 2 (реализация в PR #21) содержала ошибочный вывод: +«Wordstat игнорирует явно запрошенный недельный период и всегда возвращает +дефолтное окно». Вывод был построен на прогоне, где `date_from`/`date_to` +по факту не передавались (`requested_period: null` в логе того прогона) — +дефолтное окно оказалось дефолтным именно потому, что период не +запрашивался, а не потому что Wordstat его отверг. Автор вывода не +перепроверил формулировку, унаследованную из хендоффа предыдущей сессии, +прежде чем строить на ней запрет валидации. + +Пользователь опроверг это напрямую живым скриншотом UI: `По неделям`, +период `24.12.2018 — 13.01.2019` — явно применённый вручную исторический +период, далеко не дефолтные последние ~2 года. После этого недельный +период был проверен нашим кодом (`_set_period`/`_select_calendar_date`) с +теми же датами через `--resume-dir`-трюк (см. «Метод живой проверки» ниже) +— и тоже не применился, с тем же дефолтным результатом. Это на короткое +время выглядело как подтверждение исходного вывода другим путём, но +причина оказалась не в платформе, а **в нашей же проверке готовности**. + +### Настоящая причина: `_wait_for_table_granularity` проверял формат, не значение + +`_wait_for_table_granularity` (уже существовавшая функция, используемая +после смены грануляции) проверяла только то, что первая ячейка таблицы +*выглядит* как ожидаемая грануляция (регулярка вида «похоже на диапазон +недель»). После смены периода `_set_period` вызывала ту же проверку — но +таблица к этому моменту всё ещё показывала *старые* данные, которые уже +были в правильном недельном формате, поэтому проверка формата +срабатывала мгновенно, ложно сигнализируя готовность. Реальная +перерисовка данных под новый период происходила позже (от долей секунды +до нескольких секунд, по замерам). `_download_current_view` в этом окне +скачивал CSV за прежний (дефолтный) период — молча, без ошибки, ровно тот +«худший исход», которого запрет и пытался избежать, только раньше в +конвейере. + +Живой прогон с добавленным `RegExp(...).test(...)` по значению ячейки (не +только формату) подтвердил: кнопка диапазона дат обновляется мгновенно, +первая строка таблицы — с задержкой, а до тех пор `_wait_for_table_granularity` +уже возвращает «готово». + +### Первая версия фикса содержала свою ошибку: падеж месяца + +Первая версия проверки по значению ячейки сравнивала текст ячейки с +`RUSSIAN_MONTHS` в именительном падеже (`декабрь`) — тем же списком, что +используется для кликов по выпадающему списку месяца в календаре. Но +первая ячейка дневного/недельного ряда использует **родительный** падеж +(`22 июня`, `24 декабря` — «числа июня», «числа декабря»); только +месячная грануляция (`август 2024`) — именительный. `"24 декабря +2018".startsWith("24 декабрь 2018")` никогда не возвращает `true`, так что +первая версия проверки не ловила ложное готово-состояние раньше, а просто +ждала полный таймаут (у которого не было условия совпасть) на каждом +запросе с явным периодом — маскируясь под «Wordstat не успевает». +Добавлен отдельный список `RUSSIAN_MONTHS_GENITIVE`; сравнение теперь +точное (день, месяц и год, а не только день и год — иначе «тот же день, +другой месяц» молча проходил бы проверку). + +### Подтверждено живым прогоном: `requested_period` применяется + +* **Weekly**, запрошено `2018-12-24 — 2019-01-13`: `actual_period` + `24.12.2018 — 07.01.2019`. `07.01.2019` — начало последней полной + недели, заканчивающейся `13.01.2019` (поле `Неделя с` хранит начало + недели, не диапазон) — период применился точно, без усечения, 3 полные + недели. +* **Daily**, запрошено `2026-07-22 — 2026-08-20`: `actual_period` + `22.07.2026 — 19.08.2026`, 29 из 30 строк — тот же вариативный хвост + дневного ряда, что уже задокументирован выше в этом документе, не новый + дефект. + +Запрет явного недельного периода из первой версии фазы 2 полностью +отменён (revert коммита в PR #21) вместе с последовавшим за ним удалением +«мёртвого» кода недельного пикера — тот код не был мёртвым, а +недоделанным. + +### Метод живой проверки + +Полный CLI-прогон по-прежнему недоступен для этой проверки: `top_popular` +пуст всегда (issue #11), и fail-closed на пустых top-экспортах (PR #19) +корректно останавливает прогон до `dynamics`. Проверка велась напрямую +через `WordstatCollector.collect_many` с `--resume-dir`, где +`top_popular`/`top_related`/`regions` заранее отмечены собранными в +manifest (с заглушечными `.parquet`), так что реально у живого Wordstat +запрашивался только вид `dynamics` — тот же метод, что и в исходном +прогоне фазы 2. + +### Урок для дальнейшей работы + +Кнопка/DOM-индикатор выбора (дата в контроле, активный radio) и +фактическое содержимое таблицы/экспорта могут расходиться во времени на +живом Wordstat — это не первый такой случай в этом проекте (issue #3, тот +же класс проблемы для переключения вида вместо периода). Любая новая +проверка готовности должна сверяться с реальным значением в DOM, а не +только с его форматом или с состоянием отдельного контрола.