feat(generator): эталонный мир зафиксирован равным стартовому
- Зачем:
- у дней 8-13 описи не нашлось читателя: обещание эталона в 14 дней
снято решением владельца 2026-08-30 (#192, этап 7 — #9).
- Что:
- резолюция перенесена в обе спеки, докстринг описи и словарь CONTEXT.md;
- добавлен тест: проигранный через CLI день совпадает с хешем описи;
- сняты замеры: пересборка описи 20,5 с, снимок 460,7 МБ, make test
176,5 с — записаны в карту проверок и таблицу бюджетов.
- Проверка:
- cd generator && make lint && make typecheck && make test (437 passed);
- git diff по data/world-inventory.json пуст.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f93c924b33
commit
c1b61d81b9
8 files changed
+88
-38
No files matched your search
+2
-2
@@ -118,8 +118,8 @@ ClickHouse. Форма файла решена, длина — нет: стро
|
|||||||
|
|
||||||
**Стартовый мир**:
|
**Стартовый мир**:
|
||||||
Первые восемь дней оси (понедельник по понедельник), которыми стенд
|
Первые восемь дней оси (понедельник по понедельник), которыми стенд
|
||||||
наполняется при каждом `make up`. Маленький кусок эталонного мира, всегда
|
наполняется при каждом `make up`. Он же эталонный мир, всегда один и тот же:
|
||||||
один и тот же: на нём принимаются следующие этапы.
|
на нем принимаются следующие этапы.
|
||||||
_Избегать_: зерновой мир
|
_Избегать_: зерновой мир
|
||||||
|
|
||||||
**Опись мира**:
|
**Опись мира**:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
Стенд нужен трём целям из восьми. Цена — замер, см. «Что проверено».
|
Стенд нужен трём целям из восьми. Цена — замер, см. «Что проверено».
|
||||||
`make smoke` и `make check-services` перемерены 28 августа 2026 года,
|
`make smoke` и `make check-services` перемерены 28 августа 2026 года,
|
||||||
`make config-test` — 21 августа, `make check-clickhouse` — 19 августа,
|
`make config-test` — 21 августа, `make check-clickhouse` — 19 августа,
|
||||||
корневой `make lint` — 9 августа, `make test` — 7 августа; остальные стоят с
|
корневой `make lint` — 9 августа, `make test` — 30 августа; остальные стоят с
|
||||||
замера 6 августа.
|
замера 6 августа.
|
||||||
|
|
||||||
| Цель | Откуда | Что утверждает | Стенд | Цена |
|
| Цель | Откуда | Что утверждает | Стенд | Цена |
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
| `make check-services` | корень | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 71 с |
|
| `make check-services` | корень | **Службы работают**: DAG запускается и доходит, топик создаётся и удаляется, Superset логинится и ходит в базу | нужен | 71 с |
|
||||||
| `make lint` | `generator/` | Код генератора отформатирован и проходит ruff | не нужен | 0,4 с |
|
| `make lint` | `generator/` | Код генератора отформатирован и проходит ruff | не нужен | 0,4 с |
|
||||||
| `make typecheck` | `generator/` | Типы генератора сходятся (ty) | не нужен | 0,5 с |
|
| `make typecheck` | `generator/` | Типы генератора сходятся (ty) | не нужен | 0,5 с |
|
||||||
| `make test` | `generator/` | Генератор делает то, что обещает; схема события остаётся объявленным контрактом, а собранные из кода [описание выгрузки](../formats/clickstream-event.md) и [опись мира](../../data/world-inventory.json) — свежими | не нужен | 71 с |
|
| `make test` | `generator/` | Генератор делает то, что обещает; схема события остаётся объявленным контрактом, а собранные из кода [описание выгрузки](../formats/clickstream-event.md) и [опись мира](../../data/world-inventory.json) — свежими | не нужен | 176,5 с |
|
||||||
|
|
||||||
**Строк с именем `make lint` две, и это не опечатка.** Одноимённые цели за
|
**Строк с именем `make lint` две, и это не опечатка.** Одноимённые цели за
|
||||||
разными дверями охватывают разное: корневая не видит ни одного файла
|
разными дверями охватывают разное: корневая не видит ни одного файла
|
||||||
@@ -121,9 +121,9 @@ ClickHouse отвечает сразу.
|
|||||||
из каталога `generator/`: стенд им не нужен, а `make test` из них самая
|
из каталога `generator/`: стенд им не нужен, а `make test` из них самая
|
||||||
дорогая. Правки дагов и Superset — корневой `make lint`.
|
дорогая. Правки дагов и Superset — корневой `make lint`.
|
||||||
|
|
||||||
Тринадцать секунд из её семидесяти одной — пересборка восьми модельных дней
|
Двадцать секунд из ее ста семидесяти шести — пересборка восьми модельных дней
|
||||||
для сверки с описью мира. Дешевле хеши не сравнить: чтобы узнать, тот ли
|
для сверки с описью мира (20,5 с из 176,5). Дешевле хеши не сравнить: чтобы
|
||||||
получается мир, его надо получить. Место выбрано по той же оси «кого
|
узнать, тот ли получается мир, его надо получить. Место выбрано по той же оси «кого
|
||||||
спрашивают» — вопрос обращён к коду генератора, стенд ему не нужен, — и
|
спрашивают» — вопрос обращён к коду генератора, стенд ему не нужен, — и
|
||||||
краснеет проверка там, где надо: сразу после правки генератора. Не будь её,
|
краснеет проверка там, где надо: сразу после правки генератора. Не будь её,
|
||||||
расхождение всплыло бы получасом позже, на поднятом стенде, где выглядит
|
расхождение всплыло бы получасом позже, на поднятом стенде, где выглядит
|
||||||
@@ -238,6 +238,27 @@ Kafka → STG → ODS, и живёт он в `make check-clickhouse`.
|
|||||||
|
|
||||||
## Что проверено
|
## Что проверено
|
||||||
|
|
||||||
|
**Перезамер 30 августа 2026 года при исполнении #192.** Команды запускались
|
||||||
|
из `generator/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
time make inventory
|
||||||
|
uv run python -m clickstream_generator batch --day 0 --days 8 \
|
||||||
|
--file /tmp/stage7-events.jsonl
|
||||||
|
for snapshot_day in 1 2 3 4 5 6 7; do
|
||||||
|
uv run python -m clickstream_generator snapshot --day "$snapshot_day" \
|
||||||
|
--file "/tmp/stage7-snapshot-${snapshot_day}.jsonl"
|
||||||
|
done
|
||||||
|
wc -c /tmp/stage7-events.jsonl /tmp/stage7-snapshot-{1,2,3,4,5,6,7}.jsonl
|
||||||
|
time make test
|
||||||
|
```
|
||||||
|
|
||||||
|
Опись пересобралась за 20,515 с. Файловый приемник записал 460 731 812 байт:
|
||||||
|
458 478 771 байт событий и 2 253 041 байт слепков. `make test` прошел 437/0
|
||||||
|
за 176,476 с. Прежние 71 с (7 августа, #42) выросли по двум причинам: тестов
|
||||||
|
стало 437 против 407, и потяжелел сам мир — та же пересборка восьми дней
|
||||||
|
стоила 13 с, теперь 20,5. Эта правка добавила один тест — проигрыш дня.
|
||||||
|
|
||||||
**Приемка #172 28 августа 2026 года.** Холодный `create_app()` занимал
|
**Приемка #172 28 августа 2026 года.** Холодный `create_app()` занимал
|
||||||
28,3-28,4 секунды, а чтение подключения после запуска - 0,07 секунды. Предел
|
28,3-28,4 секунды, а чтение подключения после запуска - 0,07 секунды. Предел
|
||||||
30 секунд почти целиком уходил на запуск второго приложения. Увеличивать его
|
30 секунд почти целиком уходил на запуск второго приложения. Увеличивать его
|
||||||
|
|||||||
@@ -532,10 +532,11 @@ Kafka день переигрывается генератором заново:
|
|||||||
|
|
||||||
## 8. Эталонный мир и опись
|
## 8. Эталонный мир и опись
|
||||||
|
|
||||||
В git хранится только опись эталонного мира; сам снимок (14 модельных
|
В git хранится только опись эталонного мира; сам снимок генерируется на
|
||||||
дней) генерируется на месте — при `make up` и при проверках (решение
|
месте — при `make up` и при проверках (решение развилки «Производительность»
|
||||||
развилки «Производительность» карты #26, подробности —
|
карты #26, подробности — [спека генератора](2026-08-01-generator.md),
|
||||||
[спека генератора](2026-08-01-generator.md), раздел 5). Опись несёт
|
раздел 5). Снимок — 8 дней, совпадает со стартовым миром: обещание в 14 дней
|
||||||
|
снято на этапе 7. Опись несёт
|
||||||
паспорт мира (каноническое зерно, версия генератора), контрольные счётчики
|
паспорт мира (каноническое зерно, версия генератора), контрольные счётчики
|
||||||
и хеши по дням; проверки «пустой git diff» и «пересгенерируй день N —
|
и хеши по дням; проверки «пустой git diff» и «пересгенерируй день N —
|
||||||
сравни хеш» живут на ней. Политика версионирования артефакта (бывший туман
|
сравни хеш» живут на ней. Политика версионирования артефакта (бывший туман
|
||||||
@@ -555,8 +556,8 @@ Kafka день переигрывается генератором заново:
|
|||||||
названного читателя в опись не кладётся
|
названного читателя в опись не кладётся
|
||||||
([что хранит опись](../architecture/orders/inventory.md)).
|
([что хранит опись](../architecture/orders/inventory.md)).
|
||||||
|
|
||||||
Снимок вырастет против v1 (ecommerce-массивы, заказы) — размер проверить
|
Размер снимка замерен на этапе 7 — запись в разделе «Что проверено»
|
||||||
при пересборке.
|
[карты проверок](../architecture/testing.md).
|
||||||
|
|
||||||
## 9. Оценка объёма исполнения
|
## 9. Оценка объёма исполнения
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,8 @@
|
|||||||
приём на стороне хранилища.
|
приём на стороне хранилища.
|
||||||
- **Один сериализатор, глупые приёмники.** День-функция выдаёт канонические
|
- **Один сериализатор, глупые приёмники.** День-функция выдаёт канонические
|
||||||
байты; приёмники — файл, Kafka пачкой, Kafka с темпом.
|
байты; приёмники — файл, Kafka пачкой, Kafka с темпом.
|
||||||
- **Числа.** Средний день ~50 тыс. событий; эталонный снимок — 14 дней;
|
- **Числа.** Средний день ~50 тыс. событий; эталонный снимок совпадает со
|
||||||
|
стартовым миром — 8 дней;
|
||||||
в git — только опись мира; автоматических порогов по времени нет.
|
в git — только опись мира; автоматических порогов по времени нет.
|
||||||
|
|
||||||
## 1. Модель мира
|
## 1. Модель мира
|
||||||
@@ -358,10 +359,17 @@
|
|||||||
- **Средний модельный день — ~50 тыс. событий**; суточные волны с пиками до
|
- **Средний модельный день — ~50 тыс. событий**; суточные волны с пиками до
|
||||||
~2× среднего, будни/выходные; ≈8–12 тыс. сессий, 6–8 тыс. посетителей —
|
~2× среднего, будни/выходные; ≈8–12 тыс. сессий, 6–8 тыс. посетителей —
|
||||||
правдоподобный средний магазин.
|
правдоподобный средний магазин.
|
||||||
- **Эталонный снимок — 14 дней**: две полные календарные недели, D0 —
|
- **Эталонный снимок совпадает со стартовым миром — 8 дней**: понедельник
|
||||||
понедельник. Самая короткая длина, при которой есть замороженная зона за
|
по понедельник, полная неделя и один закрытый день за окном K = 7.
|
||||||
окном K = 7, дышащая зона и две волны недельной сезонности. Удлинение до
|
Обещание в 14 дней снято на этапе 7 (решение владельца 2026-08-30):
|
||||||
месяца — дешёвый ход (пересборка описи), если понадобится.
|
у дней 8–13 не нашлось читателя — лабы проигрывают мир дальше без описи,
|
||||||
|
транспорт байты с описью не сверяет, а проверке «пересгенерируй день N —
|
||||||
|
сравни хеш» день 3 годится так же, как день 13. Замеры и тесты, меряющие
|
||||||
|
мир на окне в две недели, не меняются: мир — функция без края, окну
|
||||||
|
замера опись не нужна. Удлинение описи остается дешевым ходом
|
||||||
|
(пересборка) и делается, когда читатель появится — например, если
|
||||||
|
растяжке `classes_vs_inventory` понадобится сверять больше одного
|
||||||
|
закрытого дня.
|
||||||
- **В git — только опись, снимок не хранится.** Снимок генерируется при
|
- **В git — только опись, снимок не хранится.** Снимок генерируется при
|
||||||
создании мира и в проверках генератора: артефакт — кэш чистой функции, кэш в
|
создании мира и в проверках генератора: артефакт — кэш чистой функции, кэш в
|
||||||
git не хранят. Опись несёт паспорт мира, счётчики и хеши по дням; проверки
|
git не хранят. Опись несёт паспорт мира, счётчики и хеши по дням; проверки
|
||||||
@@ -389,7 +397,7 @@
|
|||||||
| Операция | Ориентир |
|
| Операция | Ориентир |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Генерация одного дня | 1,7–2,8 с (замер 7 августа 2026 года) |
|
| Генерация одного дня | 1,7–2,8 с (замер 7 августа 2026 года) |
|
||||||
| Пересборка эталонного мира (14 дней + опись, без транспорта) | до минуты |
|
| Пересборка эталонного мира (8 дней + опись, без транспорта) | 20,5 с (замер 30 августа 2026 года) |
|
||||||
| Заливка стартового мира при `make up` (8 дней, Kafka → матвью → ODS) | 22,5 с (замер 7 августа 2026 года) |
|
| Заливка стартового мира при `make up` (8 дней, Kafka → матвью → ODS) | 22,5 с (замер 7 августа 2026 года) |
|
||||||
| Лаг живого дня | секунды |
|
| Лаг живого дня | секунды |
|
||||||
|
|
||||||
@@ -925,11 +933,13 @@
|
|||||||
|
|
||||||
### Опись мира и словарь
|
### Опись мира и словарь
|
||||||
|
|
||||||
- **Опись мира одна и растет до эталонной** (#42, 2026-08-07; решение владельца
|
- **Опись мира одна** (#42, 2026-08-07; эталонная длина — 8 дней, этап 7,
|
||||||
при нарезке, 2026-08-01). Стартовый мир этапа 2 — первые восемь дней,
|
2026-08-30). Стартовый мир этапа 2 — первые восемь дней,
|
||||||
понедельник по понедельник: полная неделя и первый замкнутый цикл K = 7.
|
понедельник по понедельник: полная неделя и первый замкнутый цикл K = 7.
|
||||||
`data/world-inventory.json` — та самая опись, которую обещает раздел 5, пока
|
`data/world-inventory.json` — та самая опись, которую обещает раздел 5.
|
||||||
короткая; этап 7 продлит ее до 14 дней, а не заведет вторую. В JSON лежат
|
Этап 7 оставил ее восьмидневной: у хвоста в шесть дней не нашлось читателя
|
||||||
|
(резолюция — в разделе 5); вырастет она продолжением этого же файла, а не
|
||||||
|
вторым. В JSON лежат
|
||||||
паспорт мира — зерно, версия генератора и хеш каталога — и строки дней с
|
паспорт мира — зерно, версия генератора и хеш каталога — и строки дней с
|
||||||
датой, числом событий и хешем. Опись собирает `make inventory`, свежесть
|
датой, числом событий и хешем. Опись собирает `make inventory`, свежесть
|
||||||
сторожит `test_inventory.py` тем же способом, что свежесть описания выгрузки.
|
сторожит `test_inventory.py` тем же способом, что свежесть описания выгрузки.
|
||||||
|
|||||||
@@ -8,8 +8,9 @@
|
|||||||
|
|
||||||
Дней в описи восемь: столько заливается в стенд при `make up`. Понедельник по
|
Дней в описи восемь: столько заливается в стенд при `make up`. Понедельник по
|
||||||
понедельник — полная неделя с выходными и первый замкнутый цикл окна K = 7.
|
понедельник — полная неделя с выходными и первый замкнутый цикл окна K = 7.
|
||||||
Эталонный снимок в четырнадцать дней придёт на этапе 7 и станет продолжением
|
Это и есть эталонный мир: обещание в четырнадцать дней снято на этапе 7 —
|
||||||
этой же описи, а не вторым файлом.
|
у хвоста не нашлось читателя. Понадобится длиннее — опись продолжит этот
|
||||||
|
же файл, а не заведет второй.
|
||||||
|
|
||||||
**Сторожат мир хеши, а не паспорт.** Паспорт отвечает на другой вопрос — «чем
|
**Сторожат мир хеши, а не паспорт.** Паспорт отвечает на другой вопрос — «чем
|
||||||
это сделано»: зерно и версия генератора. Поменяй кто-нибудь код так, что мир
|
это сделано»: зерно и версия генератора. Поменяй кто-нибудь код так, что мир
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from clickstream_generator.seeds import CANONICAL_SEED, Component
|
|||||||
|
|
||||||
WEEKDAY = 2
|
WEEKDAY = 2
|
||||||
WEEK = 7
|
WEEK = 7
|
||||||
# Окно эталонного снимка: доли по уровням спроса меряются на нём целиком —
|
# Окно замера — две недели: доли по уровням спроса меряются на нем целиком —
|
||||||
# на одном дне у самого редкого уровня набирается слишком мало карточек.
|
# на одном дне у самого редкого уровня набирается слишком мало карточек.
|
||||||
FORTNIGHT = 14
|
FORTNIGHT = 14
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ INVENTORY_PATH = Path(__file__).resolve().parents[2] / "data" / "world-inventory
|
|||||||
# отправляемых; этот дешевле прочих — его окно короче.
|
# отправляемых; этот дешевле прочих — его окно короче.
|
||||||
RUN_DAY = 2
|
RUN_DAY = 2
|
||||||
|
|
||||||
|
# День любой: хеш дня не зависит ни от соседей, ни от края описи.
|
||||||
|
REPLAY_DAY = 4
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def inventory() -> dict:
|
def inventory() -> dict:
|
||||||
@@ -80,3 +83,17 @@ def test_the_inventory_holds_the_hash_of_every_sent_snapshot(inventory: dict, tm
|
|||||||
|
|
||||||
row = next(row for row in inventory["snapshots"] if row["day"] == RUN_DAY - 1)
|
row = next(row for row in inventory["snapshots"] if row["day"] == RUN_DAY - 1)
|
||||||
assert row["sha256"] == sent
|
assert row["sha256"] == sent
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_replayed_day_matches_its_inventory_hash(tmp_path):
|
||||||
|
"""Опись сверяется с байтами, вышедшими из CLI, а не с собранной тут же:
|
||||||
|
пересчет сравнивался бы сам с собой.
|
||||||
|
"""
|
||||||
|
stored = json.loads(INVENTORY_PATH.read_text(encoding="utf-8"))
|
||||||
|
path = tmp_path / "day.jsonl"
|
||||||
|
|
||||||
|
assert cli.main(["batch", "--day", str(REPLAY_DAY), "--file", str(path)]) == 0
|
||||||
|
|
||||||
|
sent = hashlib.sha256(path.read_bytes()).hexdigest()
|
||||||
|
row = next(row for row in stored["days"] if row["day"] == REPLAY_DAY)
|
||||||
|
assert row["sha256"] == sent
|
||||||
@@ -14,8 +14,8 @@ import pytest
|
|||||||
from clickstream_generator import plan, reference, world
|
from clickstream_generator import plan, reference, world
|
||||||
from clickstream_generator.seeds import CANONICAL_SEED
|
from clickstream_generator.seeds import CANONICAL_SEED
|
||||||
|
|
||||||
# Горизонт эталонного снимка — две недели (спека, раздел 5).
|
# Окно замера плана — две недели: два цикла недельной сезонности.
|
||||||
SNAPSHOT_DAYS = 14
|
WINDOW_DAYS = 14
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@@ -54,9 +54,9 @@ def test_the_plan_is_a_pure_function_of_the_seed():
|
|||||||
|
|
||||||
|
|
||||||
def test_counters_are_a_pure_function_of_the_seed():
|
def test_counters_are_a_pure_function_of_the_seed():
|
||||||
first = plan.counters(CANONICAL_SEED, SNAPSHOT_DAYS)
|
first = plan.counters(CANONICAL_SEED, WINDOW_DAYS)
|
||||||
plan.cohort.cache_clear()
|
plan.cohort.cache_clear()
|
||||||
assert first == plan.counters(CANONICAL_SEED, SNAPSHOT_DAYS)
|
assert first == plan.counters(CANONICAL_SEED, WINDOW_DAYS)
|
||||||
|
|
||||||
|
|
||||||
def test_a_remembered_cohort_cannot_be_spoiled_from_outside():
|
def test_a_remembered_cohort_cannot_be_spoiled_from_outside():
|
||||||
@@ -216,12 +216,12 @@ def test_the_passport_points_into_the_directories():
|
|||||||
|
|
||||||
def test_the_daily_audience_matches_the_spec_band():
|
def test_the_daily_audience_matches_the_spec_band():
|
||||||
"""6–8 тыс. посетителей в день — правдоподобный средний магазин."""
|
"""6–8 тыс. посетителей в день — правдоподобный средний магазин."""
|
||||||
counters = plan.counters(CANONICAL_SEED, SNAPSHOT_DAYS)
|
counters = plan.counters(CANONICAL_SEED, WINDOW_DAYS)
|
||||||
assert all(6_000 <= size <= 8_000 for size in counters.audience)
|
assert all(6_000 <= size <= 8_000 for size in counters.audience)
|
||||||
|
|
||||||
|
|
||||||
def test_the_influx_holds_its_daily_number():
|
def test_the_influx_holds_its_daily_number():
|
||||||
counters = plan.counters(CANONICAL_SEED, SNAPSHOT_DAYS)
|
counters = plan.counters(CANONICAL_SEED, WINDOW_DAYS)
|
||||||
average = sum(counters.new_cookies) / len(counters.new_cookies)
|
average = sum(counters.new_cookies) / len(counters.new_cookies)
|
||||||
assert abs(average - world.DAILY_INFLUX) < world.DAILY_INFLUX // 10
|
assert abs(average - world.DAILY_INFLUX) < world.DAILY_INFLUX // 10
|
||||||
|
|
||||||
@@ -232,7 +232,7 @@ def test_the_influx_breathes_with_the_week():
|
|||||||
def mean(numbers: tuple[int, ...] | list[int]) -> float:
|
def mean(numbers: tuple[int, ...] | list[int]) -> float:
|
||||||
return sum(numbers) / len(numbers)
|
return sum(numbers) / len(numbers)
|
||||||
|
|
||||||
counters = plan.counters(CANONICAL_SEED, SNAPSHOT_DAYS)
|
counters = plan.counters(CANONICAL_SEED, WINDOW_DAYS)
|
||||||
weekdays = [size for day, size in enumerate(counters.new_cookies) if day % 7 < 5]
|
weekdays = [size for day, size in enumerate(counters.new_cookies) if day % 7 < 5]
|
||||||
weekend = [size for day, size in enumerate(counters.new_cookies) if day % 7 >= 5]
|
weekend = [size for day, size in enumerate(counters.new_cookies) if day % 7 >= 5]
|
||||||
expected = mean(world.WEEKLY_PROFILE_PERCENT[5:]) / mean(
|
expected = mean(world.WEEKLY_PROFILE_PERCENT[5:]) / mean(
|
||||||
@@ -250,7 +250,7 @@ def test_the_influx_differs_even_on_the_same_weekday():
|
|||||||
def test_the_influx_is_visible_on_the_plan():
|
def test_the_influx_is_visible_on_the_plan():
|
||||||
"""Накопленная аудитория растёт с горизонтом и с дневной не сходится."""
|
"""Накопленная аудитория растёт с горизонтом и с дневной не сходится."""
|
||||||
week = plan.counters(CANONICAL_SEED, 7)
|
week = plan.counters(CANONICAL_SEED, 7)
|
||||||
fortnight = plan.counters(CANONICAL_SEED, SNAPSHOT_DAYS)
|
fortnight = plan.counters(CANONICAL_SEED, WINDOW_DAYS)
|
||||||
assert week.visitors < fortnight.visitors
|
assert week.visitors < fortnight.visitors
|
||||||
assert fortnight.visitors > 2 * max(fortnight.audience)
|
assert fortnight.visitors > 2 * max(fortnight.audience)
|
||||||
|
|
||||||
@@ -258,21 +258,21 @@ def test_the_influx_is_visible_on_the_plan():
|
|||||||
def test_the_horizon_is_a_prefix_not_another_world():
|
def test_the_horizon_is_a_prefix_not_another_world():
|
||||||
"""Продление истории днём N+1 не трогает дни 1…N."""
|
"""Продление истории днём N+1 не трогает дни 1…N."""
|
||||||
week = plan.counters(CANONICAL_SEED, 7)
|
week = plan.counters(CANONICAL_SEED, 7)
|
||||||
fortnight = plan.counters(CANONICAL_SEED, SNAPSHOT_DAYS)
|
fortnight = plan.counters(CANONICAL_SEED, WINDOW_DAYS)
|
||||||
assert fortnight.audience[:7] == week.audience
|
assert fortnight.audience[:7] == week.audience
|
||||||
assert fortnight.new_cookies[:7] == week.new_cookies
|
assert fortnight.new_cookies[:7] == week.new_cookies
|
||||||
|
|
||||||
|
|
||||||
def test_counters_count_the_same_audience_that_the_day_gets():
|
def test_counters_count_the_same_audience_that_the_day_gets():
|
||||||
"""Счётчики и дня-функция спрашивают один план — расходиться им негде."""
|
"""Счётчики и дня-функция спрашивают один план — расходиться им негде."""
|
||||||
counters = plan.counters(CANONICAL_SEED, SNAPSHOT_DAYS)
|
counters = plan.counters(CANONICAL_SEED, WINDOW_DAYS)
|
||||||
for day in (0, 7, SNAPSHOT_DAYS - 1):
|
for day in (0, 7, WINDOW_DAYS - 1):
|
||||||
actual = plan.audience(CANONICAL_SEED, day).client_id.size
|
actual = plan.audience(CANONICAL_SEED, day).client_id.size
|
||||||
assert actual == counters.audience[day]
|
assert actual == counters.audience[day]
|
||||||
|
|
||||||
|
|
||||||
def test_counters_know_the_pairs_before_a_single_event():
|
def test_counters_know_the_pairs_before_a_single_event():
|
||||||
counters = plan.counters(CANONICAL_SEED, SNAPSHOT_DAYS)
|
counters = plan.counters(CANONICAL_SEED, WINDOW_DAYS)
|
||||||
assert counters.pairs > 0
|
assert counters.pairs > 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user