feat(generator): корзина отвязана от страницы, у товара — уровень спроса

- Зачем:
  - приёмка #40 нашла в торговых данных два точных равенства, каких в живом
    магазине не бывает: событие корзины случалось ровно у визитов со
    страницей /cart, а внутри такого визита в корзину уходили все открытые
    карточки. Привлекательность товара было нечем измерить, а аналитик читал
    бы эти равенства как склейку в разметке.
- Что:
  - класть в корзину может любой визит, открывший карточку; страница /cart
    осталась шагом воронки, а у визита, дошедшего до неё, корзина непуста.
  - у товара появился уровень спроса — колонка каталога с тремя значениями
    и два ряда вероятностей в числах мира: намерение визита берётся из шага
    воронки, а не из метки покупателя, которая уже действует через неё.
  - уровни рассыпаны по каталогу одной колодой: одинакового расклада по
    категориям нет, связи с ценой нет, и то и другое сторожится тестами.
  - числа мира перемерены: конверсия карточки в корзину 8,62% (по уровням
    13,1 / 9,2 / 5,8), кладут без открытия корзины 41,4% таких визитов,
    средний день 49 834 события, конверсия визита в покупку 2,43%.
  - спека генератора и мастер-спека приведены к новой форме каталога.
- Проверка:
  - make lint && make typecheck && make test — 394 passed (было 383).
  - трафиковая половина побайтово та же: sha256 по (URL, времени, WatchID)
    всех просмотров за 14 канонических дней совпадает со снимком до правки.
This commit is contained in:
2026-08-02 22:46:30 +03:00
parent 4165f10cdd
commit c0f4d21df9
10 changed files with 734 additions and 248 deletions
+181 -181
View File
@@ -1,181 +1,181 @@
sku,name,category,brand,price
HOME-0001,Комплект органайзеров для шкафа,Товары для дома,Домовит,129000
HOME-0002,Сушилка для белья напольная,Товары для дома,Домовит,249900
HOME-0003,Настольная лампа «Тихий вечер»,Товары для дома,Ладный дом,188990
HOME-0004,Набор вешалок 12 штук,Товары для дома,Домовит,79000
TEXT-0001,Комплект постельного белья полутораспальный,Текстиль,Сатин,348990
TEXT-0002,Плед-покрывало 200×220,Текстиль,Сатин,279000
TEXT-0003,Полотенце банное 70×140,Текстиль,Сатин,89000
TEXT-0004,Штора блэкаут 200×260,Текстиль,Ладный дом,218990
POSU-0001,Сковорода 26 см с антипригарным покрытием,Посуда,Кухмастер,198990
POSU-0002,Набор кастрюль 3 предмета,Посуда,Кухмастер,458999
POSU-0003,Чайник заварочный стеклянный 1 л,Посуда,Кухмастер,118990
POSU-0004,Набор столовых приборов 24 предмета,Посуда,Ладья,289000
TECH-0001,Электрочайник 1.7 л,Бытовая техника,Электрон,188990
TECH-0002,Блендер погружной 800 Вт,Бытовая техника,Электрон,279000
TECH-0003,Отпариватель ручной,Бытовая техника,Электрон,349000
TECH-0004,Робот-пылесос,Бытовая техника,Электрон,1899000
KIDS-0001,Стульчик для кормления,Детские товары,Малышок,649000
KIDS-0002,Ночник-проектор «Звёзды»,Детские товары,Малышок,128990
KIDS-0003,Набор детской посуды 5 предметов,Детские товары,Малышок,88999
WEAR-0001,Тапочки домашние войлочные,Одежда и обувь,Ходики,98990
WEAR-0002,Халат махровый,Одежда и обувь,Сатин,249000
WEAR-0003,Носки хлопковые 5 пар,Одежда и обувь,Ходики,59000
HOME-0005,Корзина для белья 45 л,Товары для дома,Домовит,119000
HOME-0006,Контейнер для хранения 18 л,Товары для дома,Домовит,69000
HOME-0007,Чехол для одежды 60×100,Товары для дома,Ладный дом,38990
HOME-0008,Коврик придверный резиновый 40×60,Товары для дома,Домовит,59000
HOME-0009,Гладильная доска настольная 73×30,Товары для дома,Ладный дом,149000
HOME-0010,Ведро хозяйственное с крышкой 12 л,Товары для дома,Домовит,48990
HOME-0011,Швабра с отжимом и ведром 8 л,Товары для дома,Домовит,178990
HOME-0012,Щётка для пола с черенком 120 см,Товары для дома,Домовит,45000
HOME-0013,Совок со щёткой на длинной ручке,Товары для дома,Домовит,79000
HOME-0014,Таз хозяйственный круглый 14 л,Товары для дома,Домовит,38990
HOME-0015,Этажерка для ванной 3 яруса,Товары для дома,Ладный дом,139000
HOME-0016,Полка настенная металлическая 45 см,Товары для дома,Ладный дом,99000
HOME-0017,Зеркало настенное круглое 50 см,Товары для дома,Ладный дом,168990
HOME-0018,Набор коробок для обуви 6 штук,Товары для дома,Домовит,129000
HOME-0019,Подставка для зонтов металлическая,Товары для дома,Ладный дом,158990
HOME-0020,Крючки самоклеящиеся 8 штук,Товары для дома,Домовит,29000
HOME-0021,Верёвка бельевая 20 м,Товары для дома,Домовит,19000
HOME-0022,Прищепки усиленные 24 штуки,Товары для дома,Домовит,24990
HOME-0023,Мешки вакуумные для одежды 5 штук,Товары для дома,Домовит,68999
HOME-0024,Органайзер подвесной с 12 карманами,Товары для дома,Ладный дом,79000
HOME-0025,Часы настенные круглые 30 см,Товары для дома,Ладный дом,88990
HOME-0026,Светильник настольный с прищепкой 7 Вт,Товары для дома,Ладный дом,119000
HOME-0027,Удлинитель бытовой 3 розетки 3 м,Товары для дома,Ладный дом,99000
HOME-0028,Лестница-стремянка 3 ступени,Товары для дома,Домовит,299000
HOME-0029,Ящик для инструментов 16 дюймов,Товары для дома,Домовит,138990
HOME-0030,Набор плечиков для брюк 6 штук,Товары для дома,Домовит,89000
TEXT-0005,Пододеяльник двуспальный 175×215,Текстиль,Сатин,169000
TEXT-0006,Простыня на резинке 160×200,Текстиль,Сатин,148990
TEXT-0007,Наволочки хлопковые 50×70 2 штуки,Текстиль,Сатин,79000
TEXT-0008,Одеяло всесезонное 172×205,Текстиль,Сатин,299000
TEXT-0009,Подушка стёганая 50×70,Текстиль,Сатин,128990
TEXT-0010,Наматрасник непромокаемый 160×200,Текстиль,Сатин,189000
TEXT-0011,Покрывало стёганое 180×210,Текстиль,Сатин,238999
TEXT-0012,Плед флисовый 150×200,Текстиль,Сатин,118990
TEXT-0013,Полотенце лицевое 50×90,Текстиль,Сатин,49000
TEXT-0014,Полотенца кухонные 40×60 3 штуки,Текстиль,Сатин,59000
TEXT-0015,Коврик для ванной хлопковый 50×80,Текстиль,Ладный дом,99000
TEXT-0016,Штора для ванной 180×200,Текстиль,Ладный дом,79000
TEXT-0017,Тюль вуаль 300×260,Текстиль,Ладный дом,199000
TEXT-0018,Шторы кухонные 150×170 2 полотна,Текстиль,Ладный дом,179000
TEXT-0019,Скатерть водоотталкивающая 140×180,Текстиль,Ладный дом,129000
TEXT-0020,Дорожка на стол хлопковая 40×140,Текстиль,Ладный дом,59000
TEXT-0021,Салфетки столовые льняные 4 штуки,Текстиль,Сатин,79000
TEXT-0022,Чехлы на табурет 35×35 4 штуки,Текстиль,Ладный дом,108990
TEXT-0023,Подушка декоративная 40×40,Текстиль,Сатин,68999
TEXT-0024,Чехол для диванной подушки 45×45,Текстиль,Сатин,39000
TEXT-0025,Фартук кухонный с карманом,Текстиль,Ладный дом,59000
TEXT-0026,Прихватки стёганые 20×20 2 штуки,Текстиль,Ладный дом,38990
TEXT-0027,Халат вафельный размер 48–50,Текстиль,Сатин,199000
TEXT-0028,Комплект полотенец 50×90 и 70×140,Текстиль,Сатин,138999
TEXT-0029,Одеяло детское байковое 100×140,Текстиль,Сатин,169000
TEXT-0030,Подушка для стула 40×40,Текстиль,Ладный дом,59000
POSU-0005,Кастрюля эмалированная 3 л,Посуда,Кухмастер,168990
POSU-0006,Ковш нержавеющий с крышкой 1.5 л,Посуда,Кухмастер,138990
POSU-0007,Сотейник литой с крышкой 28 см,Посуда,Кухмастер,269000
POSU-0008,Сковорода блинная 22 см,Посуда,Кухмастер,149000
POSU-0009,Казан алюминиевый 6 л,Посуда,Кухмастер,299000
POSU-0010,Форма для запекания стеклянная 2.4 л,Посуда,Кухмастер,118990
POSU-0011,Противень стальной 37×27,Посуда,Кухмастер,79000
POSU-0012,Набор форм для выпечки 3 штуки,Посуда,Кухмастер,139000
POSU-0013,Миска нержавеющая 24 см,Посуда,Кухмастер,68990
POSU-0014,Дуршлаг стальной с ручками 22 см,Посуда,Кухмастер,89000
POSU-0015,Тёрка четырёхгранная 23 см,Посуда,Кухмастер,49000
POSU-0016,Доска разделочная бамбуковая 35×25,Посуда,Кухмастер,78990
POSU-0017,Набор ножей с подставкой 6 предметов,Посуда,Кухмастер,248990
POSU-0018,Половник и шумовка 2 предмета,Посуда,Кухмастер,58990
POSU-0019,Контейнеры пищевые 7 предметов,Посуда,Кухмастер,99000
POSU-0020,Банки для сыпучих продуктов 4 штуки,Посуда,Ладья,138990
POSU-0021,Тарелки обеденные фарфоровые 6 штук,Посуда,Ладья,178999
POSU-0022,Тарелки суповые керамические 4 штуки,Посуда,Ладья,129000
POSU-0023,Салатник стеклянный 23 см,Посуда,Ладья,68999
POSU-0024,Кружки керамические 350 мл 4 штуки,Посуда,Ладья,118990
POSU-0025,Стаканы высокие 400 мл 6 штук,Посуда,Ладья,98990
POSU-0026,Бокалы для воды 300 мл 6 штук,Посуда,Ладья,149000
POSU-0027,Термос стальной 1 л,Посуда,Ладья,179000
POSU-0028,Термокружка с крышкой 450 мл,Посуда,Ладья,99000
POSU-0029,Сахарница фарфоровая 400 мл,Посуда,Ладья,59000
POSU-0030,Маслёнка стеклянная с крышкой,Посуда,Ладья,68990
TECH-0005,Миксер ручной 500 Вт,Бытовая техника,Электрон,248990
TECH-0006,Измельчитель с чашей 1.5 л,Бытовая техника,Электрон,298990
TECH-0007,Стационарный блендер 1.8 л,Бытовая техника,Электрон,549000
TECH-0008,Мясорубка электрическая 1800 Вт,Бытовая техника,Электрон,799000
TECH-0009,Тостер на 2 ломтика 850 Вт,Бытовая техника,Электрон,299000
TECH-0010,Вафельница электрическая 1000 Вт,Бытовая техника,Электрон,348999
TECH-0011,Мультиварка с чашей 5 л,Бытовая техника,Электрон,699000
TECH-0012,Электроплитка на 1 конфорку 1500 Вт,Бытовая техника,Электрон,249000
TECH-0013,Мини-печь с грилем 30 л,Бытовая техника,Электрон,899000
TECH-0014,Микроволновая печь 20 л,Бытовая техника,Электрон,999000
TECH-0015,Сушилка для овощей 5 поддонов,Бытовая техника,Электрон,499000
TECH-0016,Кофемолка электрическая 200 Вт,Бытовая техника,Электрон,199000
TECH-0017,Капельная кофеварка 1.2 л,Бытовая техника,Электрон,399000
TECH-0018,Соковыжималка шнековая 150 Вт,Бытовая техника,Электрон,799000
TECH-0019,Утюг паровой 2400 Вт,Бытовая техника,Электрон,349000
TECH-0020,Швейная машина 12 операций,Бытовая техника,Электрон,1299000
TECH-0021,Пылесос контейнерный 2000 Вт,Бытовая техника,Электрон,1099000
TECH-0022,Пылесос вертикальный аккумуляторный,Бытовая техника,Электрон,1499000
TECH-0023,Электровеник с контейнером 0.5 л,Бытовая техника,Электрон,699000
TECH-0024,Фен с диффузором 2200 Вт,Бытовая техника,Электрон,299000
TECH-0025,Щипцы для волос 45 Вт,Бытовая техника,Электрон,249000
TECH-0026,Машинка для стрижки с 6 насадками,Бытовая техника,Электрон,399000
TECH-0027,Весы напольные до 180 кг,Бытовая техника,Электрон,198990
TECH-0028,Весы кухонные до 5 кг,Бытовая техника,Электрон,148990
TECH-0029,Увлажнитель воздуха 3 л,Бытовая техника,Электрон,499000
TECH-0030,Тепловентилятор керамический 1500 Вт,Бытовая техника,Электрон,398990
KIDS-0004,Кроватка кукольная деревянная 45 см,Детские товары,Малышок,248999
KIDS-0005,Манеж складной 100×100,Детские товары,Малышок,798990
KIDS-0006,Горшок детский со спинкой,Детские товары,Малышок,79000
KIDS-0007,Ванночка детская 84 см,Детские товары,Малышок,168990
KIDS-0008,Подставка детская двухступенчатая,Детские товары,Малышок,89000
KIDS-0009,Накладка на унитаз с ручками,Детские товары,Малышок,69000
KIDS-0010,Сиденье для купания на присосках,Детские товары,Малышок,138990
KIDS-0011,Пелёнки муслиновые 80×100 3 штуки,Детские товары,Малышок,129000
KIDS-0012,Одеяло для коляски 90×110,Детские товары,Малышок,148990
KIDS-0013,Нагрудники непромокаемые 5 штук,Детские товары,Малышок,58999
KIDS-0014,Бутылочка с ручками 250 мл,Детские товары,Малышок,49000
KIDS-0015,Поильник непроливайка 300 мл,Детские товары,Малышок,54990
KIDS-0016,Контейнер для пустышки 2 штуки,Детские товары,Малышок,25000
KIDS-0017,Погремушки мягкие 3 штуки,Детские товары,Малышок,59000
KIDS-0018,Кубики развивающие 12 штук,Детские товары,Малышок,89000
KIDS-0019,Пирамидка деревянная 8 колец,Детские товары,Малышок,78990
KIDS-0020,Сортер с геометрическими фигурами,Детские товары,Малышок,98990
KIDS-0021,Конструктор деревянный 50 деталей,Детские товары,Малышок,139000
KIDS-0022,Доска для рисования магнитная 30×40,Детские товары,Малышок,128990
KIDS-0023,Мольберт детский двусторонний 105 см,Детские товары,Малышок,298999
KIDS-0024,Набор для лепки 12 цветов,Детские товары,Малышок,69000
KIDS-0025,Коврик игровой складной 180×150,Детские товары,Малышок,399000
KIDS-0026,Палатка игровая с окошками,Детские товары,Малышок,199000
KIDS-0027,Каталка с ручкой и звуком,Детские товары,Малышок,169000
KIDS-0028,Самокат трёхколёсный до 50 кг,Детские товары,Малышок,298999
KIDS-0029,Санки со спинкой и ремнём,Детские товары,Малышок,349000
KIDS-0030,Рюкзак детский объёмом 8 л,Детские товары,Малышок,128990
WEAR-0004,Футболка хлопковая размер 48,Одежда и обувь,Сатин,78990
WEAR-0005,Майка хлопковая размер 46,Одежда и обувь,Сатин,58990
WEAR-0006,Лонгслив трикотажный размер 50,Одежда и обувь,Сатин,108990
WEAR-0007,Водолазка хлопковая размер 44,Одежда и обувь,Сатин,118990
WEAR-0008,Толстовка с капюшоном размер 52,Одежда и обувь,Сатин,198999
WEAR-0009,Брюки спортивные размер 50,Одежда и обувь,Сатин,169000
WEAR-0010,Легинсы хлопковые размер 46,Одежда и обувь,Сатин,98990
WEAR-0011,Пижама трикотажная размер 48,Одежда и обувь,Сатин,179000
WEAR-0012,Ночная сорочка хлопковая размер 50,Одежда и обувь,Сатин,128999
WEAR-0013,Фартук рабочий с тремя карманами,Одежда и обувь,Сатин,79000
WEAR-0014,Перчатки хозяйственные 3 пары,Одежда и обувь,Ходики,39000
WEAR-0015,Варежки вязаные размер 8,Одежда и обувь,Ходики,68990
WEAR-0016,Шапка вязаная с отворотом размер 56,Одежда и обувь,Ходики,88990
WEAR-0017,Шарф шерстяной 30×180,Одежда и обувь,Ходики,119000
WEAR-0018,Ремень текстильный длиной 120 см,Одежда и обувь,Ходики,78990
WEAR-0019,Стельки войлочные 2 пары,Одежда и обувь,Ходики,38990
WEAR-0020,Шнурки круглые 120 см 3 пары,Одежда и обувь,Ходики,25000
WEAR-0021,Кеды текстильные размер 42,Одежда и обувь,Ходики,179000
WEAR-0022,Кроссовки прогулочные размер 39,Одежда и обувь,Ходики,248990
WEAR-0023,Ботинки утеплённые размер 41,Одежда и обувь,Ходики,348990
WEAR-0024,Сапоги резиновые размер 40,Одежда и обувь,Ходики,199000
WEAR-0025,Полуботинки кожаные размер 43,Одежда и обувь,Ходики,398999
WEAR-0026,Сандалии с ремешками размер 38,Одежда и обувь,Ходики,189000
WEAR-0027,Сланцы резиновые размер 41,Одежда и обувь,Ходики,79000
WEAR-0028,Балетки текстильные размер 37,Одежда и обувь,Ходики,149000
WEAR-0029,Туфли домашние утеплённые размер 39,Одежда и обувь,Ходики,129000
WEAR-0030,Галоши садовые размер 42,Одежда и обувь,Ходики,98990
sku,name,category,brand,price,demand
HOME-0001,Комплект органайзеров для шкафа,Товары для дома,Домовит,129000,обычный
HOME-0002,Сушилка для белья напольная,Товары для дома,Домовит,249900,обычный
HOME-0003,Настольная лампа «Тихий вечер»,Товары для дома,Ладный дом,188990,магнит
HOME-0004,Набор вешалок 12 штук,Товары для дома,Домовит,79000,магнит
TEXT-0001,Комплект постельного белья полутораспальный,Текстиль,Сатин,348990,обычный
TEXT-0002,Плед-покрывало 200×220,Текстиль,Сатин,279000,залёживается
TEXT-0003,Полотенце банное 70×140,Текстиль,Сатин,89000,обычный
TEXT-0004,Штора блэкаут 200×260,Текстиль,Ладный дом,218990,залёживается
POSU-0001,Сковорода 26 см с антипригарным покрытием,Посуда,Кухмастер,198990,залёживается
POSU-0002,Набор кастрюль 3 предмета,Посуда,Кухмастер,458999,магнит
POSU-0003,Чайник заварочный стеклянный 1 л,Посуда,Кухмастер,118990,магнит
POSU-0004,Набор столовых приборов 24 предмета,Посуда,Ладья,289000,магнит
TECH-0001,Электрочайник 1.7 л,Бытовая техника,Электрон,188990,обычный
TECH-0002,Блендер погружной 800 Вт,Бытовая техника,Электрон,279000,залёживается
TECH-0003,Отпариватель ручной,Бытовая техника,Электрон,349000,обычный
TECH-0004,Робот-пылесос,Бытовая техника,Электрон,1899000,залёживается
KIDS-0001,Стульчик для кормления,Детские товары,Малышок,649000,магнит
KIDS-0002,Ночник-проектор «Звёзды»,Детские товары,Малышок,128990,магнит
KIDS-0003,Набор детской посуды 5 предметов,Детские товары,Малышок,88999,обычный
WEAR-0001,Тапочки домашние войлочные,Одежда и обувь,Ходики,98990,обычный
WEAR-0002,Халат махровый,Одежда и обувь,Сатин,249000,магнит
WEAR-0003,Носки хлопковые 5 пар,Одежда и обувь,Ходики,59000,залёживается
HOME-0005,Корзина для белья 45 л,Товары для дома,Домовит,119000,магнит
HOME-0006,Контейнер для хранения 18 л,Товары для дома,Домовит,69000,обычный
HOME-0007,Чехол для одежды 60×100,Товары для дома,Ладный дом,38990,залёживается
HOME-0008,Коврик придверный резиновый 40×60,Товары для дома,Домовит,59000,обычный
HOME-0009,Гладильная доска настольная 73×30,Товары для дома,Ладный дом,149000,залёживается
HOME-0010,Ведро хозяйственное с крышкой 12 л,Товары для дома,Домовит,48990,залёживается
HOME-0011,Швабра с отжимом и ведром 8 л,Товары для дома,Домовит,178990,обычный
HOME-0012,Щётка для пола с черенком 120 см,Товары для дома,Домовит,45000,обычный
HOME-0013,Совок со щёткой на длинной ручке,Товары для дома,Домовит,79000,магнит
HOME-0014,Таз хозяйственный круглый 14 л,Товары для дома,Домовит,38990,магнит
HOME-0015,Этажерка для ванной 3 яруса,Товары для дома,Ладный дом,139000,магнит
HOME-0016,Полка настенная металлическая 45 см,Товары для дома,Ладный дом,99000,обычный
HOME-0017,Зеркало настенное круглое 50 см,Товары для дома,Ладный дом,168990,залёживается
HOME-0018,Набор коробок для обуви 6 штук,Товары для дома,Домовит,129000,магнит
HOME-0019,Подставка для зонтов металлическая,Товары для дома,Ладный дом,158990,обычный
HOME-0020,Крючки самоклеящиеся 8 штук,Товары для дома,Домовит,29000,обычный
HOME-0021,Верёвка бельевая 20 м,Товары для дома,Домовит,19000,обычный
HOME-0022,Прищепки усиленные 24 штуки,Товары для дома,Домовит,24990,залёживается
HOME-0023,Мешки вакуумные для одежды 5 штук,Товары для дома,Домовит,68999,обычный
HOME-0024,Органайзер подвесной с 12 карманами,Товары для дома,Ладный дом,79000,обычный
HOME-0025,Часы настенные круглые 30 см,Товары для дома,Ладный дом,88990,магнит
HOME-0026,Светильник настольный с прищепкой 7 Вт,Товары для дома,Ладный дом,119000,обычный
HOME-0027,Удлинитель бытовой 3 розетки 3 м,Товары для дома,Ладный дом,99000,магнит
HOME-0028,Лестница-стремянка 3 ступени,Товары для дома,Домовит,299000,обычный
HOME-0029,Ящик для инструментов 16 дюймов,Товары для дома,Домовит,138990,обычный
HOME-0030,Набор плечиков для брюк 6 штук,Товары для дома,Домовит,89000,обычный
TEXT-0005,Пододеяльник двуспальный 175×215,Текстиль,Сатин,169000,обычный
TEXT-0006,Простыня на резинке 160×200,Текстиль,Сатин,148990,обычный
TEXT-0007,Наволочки хлопковые 50×70 2 штуки,Текстиль,Сатин,79000,залёживается
TEXT-0008,Одеяло всесезонное 172×205,Текстиль,Сатин,299000,обычный
TEXT-0009,Подушка стёганая 50×70,Текстиль,Сатин,128990,обычный
TEXT-0010,Наматрасник непромокаемый 160×200,Текстиль,Сатин,189000,магнит
TEXT-0011,Покрывало стёганое 180×210,Текстиль,Сатин,238999,обычный
TEXT-0012,Плед флисовый 150×200,Текстиль,Сатин,118990,обычный
TEXT-0013,Полотенце лицевое 50×90,Текстиль,Сатин,49000,обычный
TEXT-0014,Полотенца кухонные 40×60 3 штуки,Текстиль,Сатин,59000,обычный
TEXT-0015,Коврик для ванной хлопковый 50×80,Текстиль,Ладный дом,99000,залёживается
TEXT-0016,Штора для ванной 180×200,Текстиль,Ладный дом,79000,обычный
TEXT-0017,Тюль вуаль 300×260,Текстиль,Ладный дом,199000,обычный
TEXT-0018,Шторы кухонные 150×170 2 полотна,Текстиль,Ладный дом,179000,залёживается
TEXT-0019,Скатерть водоотталкивающая 140×180,Текстиль,Ладный дом,129000,обычный
TEXT-0020,Дорожка на стол хлопковая 40×140,Текстиль,Ладный дом,59000,залёживается
TEXT-0021,Салфетки столовые льняные 4 штуки,Текстиль,Сатин,79000,обычный
TEXT-0022,Чехлы на табурет 35×35 4 штуки,Текстиль,Ладный дом,108990,обычный
TEXT-0023,Подушка декоративная 40×40,Текстиль,Сатин,68999,магнит
TEXT-0024,Чехол для диванной подушки 45×45,Текстиль,Сатин,39000,обычный
TEXT-0025,Фартук кухонный с карманом,Текстиль,Ладный дом,59000,магнит
TEXT-0026,Прихватки стёганые 20×20 2 штуки,Текстиль,Ладный дом,38990,магнит
TEXT-0027,Халат вафельный размер 48–50,Текстиль,Сатин,199000,залёживается
TEXT-0028,Комплект полотенец 50×90 и 70×140,Текстиль,Сатин,138999,обычный
TEXT-0029,Одеяло детское байковое 100×140,Текстиль,Сатин,169000,обычный
TEXT-0030,Подушка для стула 40×40,Текстиль,Ладный дом,59000,обычный
POSU-0005,Кастрюля эмалированная 3 л,Посуда,Кухмастер,168990,обычный
POSU-0006,Ковш нержавеющий с крышкой 1.5 л,Посуда,Кухмастер,138990,залёживается
POSU-0007,Сотейник литой с крышкой 28 см,Посуда,Кухмастер,269000,магнит
POSU-0008,Сковорода блинная 22 см,Посуда,Кухмастер,149000,обычный
POSU-0009,Казан алюминиевый 6 л,Посуда,Кухмастер,299000,обычный
POSU-0010,Форма для запекания стеклянная 2.4 л,Посуда,Кухмастер,118990,магнит
POSU-0011,Противень стальной 37×27,Посуда,Кухмастер,79000,залёживается
POSU-0012,Набор форм для выпечки 3 штуки,Посуда,Кухмастер,139000,залёживается
POSU-0013,Миска нержавеющая 24 см,Посуда,Кухмастер,68990,залёживается
POSU-0014,Дуршлаг стальной с ручками 22 см,Посуда,Кухмастер,89000,залёживается
POSU-0015,Тёрка четырёхгранная 23 см,Посуда,Кухмастер,49000,обычный
POSU-0016,Доска разделочная бамбуковая 35×25,Посуда,Кухмастер,78990,магнит
POSU-0017,Набор ножей с подставкой 6 предметов,Посуда,Кухмастер,248990,обычный
POSU-0018,Половник и шумовка 2 предмета,Посуда,Кухмастер,58990,обычный
POSU-0019,Контейнеры пищевые 7 предметов,Посуда,Кухмастер,99000,залёживается
POSU-0020,Банки для сыпучих продуктов 4 штуки,Посуда,Ладья,138990,залёживается
POSU-0021,Тарелки обеденные фарфоровые 6 штук,Посуда,Ладья,178999,обычный
POSU-0022,Тарелки суповые керамические 4 штуки,Посуда,Ладья,129000,обычный
POSU-0023,Салатник стеклянный 23 см,Посуда,Ладья,68999,обычный
POSU-0024,Кружки керамические 350 мл 4 штуки,Посуда,Ладья,118990,обычный
POSU-0025,Стаканы высокие 400 мл 6 штук,Посуда,Ладья,98990,обычный
POSU-0026,Бокалы для воды 300 мл 6 штук,Посуда,Ладья,149000,обычный
POSU-0027,Термос стальной 1 л,Посуда,Ладья,179000,залёживается
POSU-0028,Термокружка с крышкой 450 мл,Посуда,Ладья,99000,залёживается
POSU-0029,Сахарница фарфоровая 400 мл,Посуда,Ладья,59000,магнит
POSU-0030,Маслёнка стеклянная с крышкой,Посуда,Ладья,68990,обычный
TECH-0005,Миксер ручной 500 Вт,Бытовая техника,Электрон,248990,обычный
TECH-0006,Измельчитель с чашей 1.5 л,Бытовая техника,Электрон,298990,обычный
TECH-0007,Стационарный блендер 1.8 л,Бытовая техника,Электрон,549000,магнит
TECH-0008,Мясорубка электрическая 1800 Вт,Бытовая техника,Электрон,799000,магнит
TECH-0009,Тостер на 2 ломтика 850 Вт,Бытовая техника,Электрон,299000,залёживается
TECH-0010,Вафельница электрическая 1000 Вт,Бытовая техника,Электрон,348999,обычный
TECH-0011,Мультиварка с чашей 5 л,Бытовая техника,Электрон,699000,магнит
TECH-0012,Электроплитка на 1 конфорку 1500 Вт,Бытовая техника,Электрон,249000,обычный
TECH-0013,Мини-печь с грилем 30 л,Бытовая техника,Электрон,899000,залёживается
TECH-0014,Микроволновая печь 20 л,Бытовая техника,Электрон,999000,обычный
TECH-0015,Сушилка для овощей 5 поддонов,Бытовая техника,Электрон,499000,обычный
TECH-0016,Кофемолка электрическая 200 Вт,Бытовая техника,Электрон,199000,обычный
TECH-0017,Капельная кофеварка 1.2 л,Бытовая техника,Электрон,399000,залёживается
TECH-0018,Соковыжималка шнековая 150 Вт,Бытовая техника,Электрон,799000,залёживается
TECH-0019,Утюг паровой 2400 Вт,Бытовая техника,Электрон,349000,обычный
TECH-0020,Швейная машина 12 операций,Бытовая техника,Электрон,1299000,обычный
TECH-0021,Пылесос контейнерный 2000 Вт,Бытовая техника,Электрон,1099000,залёживается
TECH-0022,Пылесос вертикальный аккумуляторный,Бытовая техника,Электрон,1499000,магнит
TECH-0023,Электровеник с контейнером 0.5 л,Бытовая техника,Электрон,699000,магнит
TECH-0024,Фен с диффузором 2200 Вт,Бытовая техника,Электрон,299000,обычный
TECH-0025,Щипцы для волос 45 Вт,Бытовая техника,Электрон,249000,обычный
TECH-0026,Машинка для стрижки с 6 насадками,Бытовая техника,Электрон,399000,залёживается
TECH-0027,Весы напольные до 180 кг,Бытовая техника,Электрон,198990,залёживается
TECH-0028,Весы кухонные до 5 кг,Бытовая техника,Электрон,148990,обычный
TECH-0029,Увлажнитель воздуха 3 л,Бытовая техника,Электрон,499000,обычный
TECH-0030,Тепловентилятор керамический 1500 Вт,Бытовая техника,Электрон,398990,магнит
KIDS-0004,Кроватка кукольная деревянная 45 см,Детские товары,Малышок,248999,обычный
KIDS-0005,Манеж складной 100×100,Детские товары,Малышок,798990,залёживается
KIDS-0006,Горшок детский со спинкой,Детские товары,Малышок,79000,магнит
KIDS-0007,Ванночка детская 84 см,Детские товары,Малышок,168990,обычный
KIDS-0008,Подставка детская двухступенчатая,Детские товары,Малышок,89000,залёживается
KIDS-0009,Накладка на унитаз с ручками,Детские товары,Малышок,69000,обычный
KIDS-0010,Сиденье для купания на присосках,Детские товары,Малышок,138990,залёживается
KIDS-0011,Пелёнки муслиновые 80×100 3 штуки,Детские товары,Малышок,129000,обычный
KIDS-0012,Одеяло для коляски 90×110,Детские товары,Малышок,148990,магнит
KIDS-0013,Нагрудники непромокаемые 5 штук,Детские товары,Малышок,58999,обычный
KIDS-0014,Бутылочка с ручками 250 мл,Детские товары,Малышок,49000,обычный
KIDS-0015,Поильник непроливайка 300 мл,Детские товары,Малышок,54990,залёживается
KIDS-0016,Контейнер для пустышки 2 штуки,Детские товары,Малышок,25000,магнит
KIDS-0017,Погремушки мягкие 3 штуки,Детские товары,Малышок,59000,магнит
KIDS-0018,Кубики развивающие 12 штук,Детские товары,Малышок,89000,залёживается
KIDS-0019,Пирамидка деревянная 8 колец,Детские товары,Малышок,78990,обычный
KIDS-0020,Сортер с геометрическими фигурами,Детские товары,Малышок,98990,обычный
KIDS-0021,Конструктор деревянный 50 деталей,Детские товары,Малышок,139000,обычный
KIDS-0022,Доска для рисования магнитная 30×40,Детские товары,Малышок,128990,обычный
KIDS-0023,Мольберт детский двусторонний 105 см,Детские товары,Малышок,298999,залёживается
KIDS-0024,Набор для лепки 12 цветов,Детские товары,Малышок,69000,обычный
KIDS-0025,Коврик игровой складной 180×150,Детские товары,Малышок,399000,обычный
KIDS-0026,Палатка игровая с окошками,Детские товары,Малышок,199000,обычный
KIDS-0027,Каталка с ручкой и звуком,Детские товары,Малышок,169000,залёживается
KIDS-0028,Самокат трёхколёсный до 50 кг,Детские товары,Малышок,298999,обычный
KIDS-0029,Санки со спинкой и ремнём,Детские товары,Малышок,349000,залёживается
KIDS-0030,Рюкзак детский объёмом 8 л,Детские товары,Малышок,128990,обычный
WEAR-0004,Футболка хлопковая размер 48,Одежда и обувь,Сатин,78990,залёживается
WEAR-0005,Майка хлопковая размер 46,Одежда и обувь,Сатин,58990,обычный
WEAR-0006,Лонгслив трикотажный размер 50,Одежда и обувь,Сатин,108990,магнит
WEAR-0007,Водолазка хлопковая размер 44,Одежда и обувь,Сатин,118990,обычный
WEAR-0008,Толстовка с капюшоном размер 52,Одежда и обувь,Сатин,198999,обычный
WEAR-0009,Брюки спортивные размер 50,Одежда и обувь,Сатин,169000,залёживается
WEAR-0010,Легинсы хлопковые размер 46,Одежда и обувь,Сатин,98990,залёживается
WEAR-0011,Пижама трикотажная размер 48,Одежда и обувь,Сатин,179000,обычный
WEAR-0012,Ночная сорочка хлопковая размер 50,Одежда и обувь,Сатин,128999,обычный
WEAR-0013,Фартук рабочий с тремя карманами,Одежда и обувь,Сатин,79000,залёживается
WEAR-0014,Перчатки хозяйственные 3 пары,Одежда и обувь,Ходики,39000,обычный
WEAR-0015,Варежки вязаные размер 8,Одежда и обувь,Ходики,68990,обычный
WEAR-0016,Шапка вязаная с отворотом размер 56,Одежда и обувь,Ходики,88990,магнит
WEAR-0017,Шарф шерстяной 30×180,Одежда и обувь,Ходики,119000,обычный
WEAR-0018,Ремень текстильный длиной 120 см,Одежда и обувь,Ходики,78990,обычный
WEAR-0019,Стельки войлочные 2 пары,Одежда и обувь,Ходики,38990,залёживается
WEAR-0020,Шнурки круглые 120 см 3 пары,Одежда и обувь,Ходики,25000,обычный
WEAR-0021,Кеды текстильные размер 42,Одежда и обувь,Ходики,179000,обычный
WEAR-0022,Кроссовки прогулочные размер 39,Одежда и обувь,Ходики,248990,обычный
WEAR-0023,Ботинки утеплённые размер 41,Одежда и обувь,Ходики,348990,залёживается
WEAR-0024,Сапоги резиновые размер 40,Одежда и обувь,Ходики,199000,обычный
WEAR-0025,Полуботинки кожаные размер 43,Одежда и обувь,Ходики,398999,залёживается
WEAR-0026,Сандалии с ремешками размер 38,Одежда и обувь,Ходики,189000,обычный
WEAR-0027,Сланцы резиновые размер 41,Одежда и обувь,Ходики,79000,обычный
WEAR-0028,Балетки текстильные размер 37,Одежда и обувь,Ходики,149000,залёживается
WEAR-0029,Туфли домашние утеплённые размер 39,Одежда и обувь,Ходики,129000,магнит
WEAR-0030,Галоши садовые размер 42,Одежда и обувь,Ходики,98990,обычный
1 sku name category brand price demand
2 HOME-0001 Комплект органайзеров для шкафа Товары для дома Домовит 129000 обычный
3 HOME-0002 Сушилка для белья напольная Товары для дома Домовит 249900 обычный
4 HOME-0003 Настольная лампа «Тихий вечер» Товары для дома Ладный дом 188990 магнит
5 HOME-0004 Набор вешалок 12 штук Товары для дома Домовит 79000 магнит
6 TEXT-0001 Комплект постельного белья полутораспальный Текстиль Сатин 348990 обычный
7 TEXT-0002 Плед-покрывало 200×220 Текстиль Сатин 279000 залёживается
8 TEXT-0003 Полотенце банное 70×140 Текстиль Сатин 89000 обычный
9 TEXT-0004 Штора блэкаут 200×260 Текстиль Ладный дом 218990 залёживается
10 POSU-0001 Сковорода 26 см с антипригарным покрытием Посуда Кухмастер 198990 залёживается
11 POSU-0002 Набор кастрюль 3 предмета Посуда Кухмастер 458999 магнит
12 POSU-0003 Чайник заварочный стеклянный 1 л Посуда Кухмастер 118990 магнит
13 POSU-0004 Набор столовых приборов 24 предмета Посуда Ладья 289000 магнит
14 TECH-0001 Электрочайник 1.7 л Бытовая техника Электрон 188990 обычный
15 TECH-0002 Блендер погружной 800 Вт Бытовая техника Электрон 279000 залёживается
16 TECH-0003 Отпариватель ручной Бытовая техника Электрон 349000 обычный
17 TECH-0004 Робот-пылесос Бытовая техника Электрон 1899000 залёживается
18 KIDS-0001 Стульчик для кормления Детские товары Малышок 649000 магнит
19 KIDS-0002 Ночник-проектор «Звёзды» Детские товары Малышок 128990 магнит
20 KIDS-0003 Набор детской посуды 5 предметов Детские товары Малышок 88999 обычный
21 WEAR-0001 Тапочки домашние войлочные Одежда и обувь Ходики 98990 обычный
22 WEAR-0002 Халат махровый Одежда и обувь Сатин 249000 магнит
23 WEAR-0003 Носки хлопковые 5 пар Одежда и обувь Ходики 59000 залёживается
24 HOME-0005 Корзина для белья 45 л Товары для дома Домовит 119000 магнит
25 HOME-0006 Контейнер для хранения 18 л Товары для дома Домовит 69000 обычный
26 HOME-0007 Чехол для одежды 60×100 Товары для дома Ладный дом 38990 залёживается
27 HOME-0008 Коврик придверный резиновый 40×60 Товары для дома Домовит 59000 обычный
28 HOME-0009 Гладильная доска настольная 73×30 Товары для дома Ладный дом 149000 залёживается
29 HOME-0010 Ведро хозяйственное с крышкой 12 л Товары для дома Домовит 48990 залёживается
30 HOME-0011 Швабра с отжимом и ведром 8 л Товары для дома Домовит 178990 обычный
31 HOME-0012 Щётка для пола с черенком 120 см Товары для дома Домовит 45000 обычный
32 HOME-0013 Совок со щёткой на длинной ручке Товары для дома Домовит 79000 магнит
33 HOME-0014 Таз хозяйственный круглый 14 л Товары для дома Домовит 38990 магнит
34 HOME-0015 Этажерка для ванной 3 яруса Товары для дома Ладный дом 139000 магнит
35 HOME-0016 Полка настенная металлическая 45 см Товары для дома Ладный дом 99000 обычный
36 HOME-0017 Зеркало настенное круглое 50 см Товары для дома Ладный дом 168990 залёживается
37 HOME-0018 Набор коробок для обуви 6 штук Товары для дома Домовит 129000 магнит
38 HOME-0019 Подставка для зонтов металлическая Товары для дома Ладный дом 158990 обычный
39 HOME-0020 Крючки самоклеящиеся 8 штук Товары для дома Домовит 29000 обычный
40 HOME-0021 Верёвка бельевая 20 м Товары для дома Домовит 19000 обычный
41 HOME-0022 Прищепки усиленные 24 штуки Товары для дома Домовит 24990 залёживается
42 HOME-0023 Мешки вакуумные для одежды 5 штук Товары для дома Домовит 68999 обычный
43 HOME-0024 Органайзер подвесной с 12 карманами Товары для дома Ладный дом 79000 обычный
44 HOME-0025 Часы настенные круглые 30 см Товары для дома Ладный дом 88990 магнит
45 HOME-0026 Светильник настольный с прищепкой 7 Вт Товары для дома Ладный дом 119000 обычный
46 HOME-0027 Удлинитель бытовой 3 розетки 3 м Товары для дома Ладный дом 99000 магнит
47 HOME-0028 Лестница-стремянка 3 ступени Товары для дома Домовит 299000 обычный
48 HOME-0029 Ящик для инструментов 16 дюймов Товары для дома Домовит 138990 обычный
49 HOME-0030 Набор плечиков для брюк 6 штук Товары для дома Домовит 89000 обычный
50 TEXT-0005 Пододеяльник двуспальный 175×215 Текстиль Сатин 169000 обычный
51 TEXT-0006 Простыня на резинке 160×200 Текстиль Сатин 148990 обычный
52 TEXT-0007 Наволочки хлопковые 50×70 2 штуки Текстиль Сатин 79000 залёживается
53 TEXT-0008 Одеяло всесезонное 172×205 Текстиль Сатин 299000 обычный
54 TEXT-0009 Подушка стёганая 50×70 Текстиль Сатин 128990 обычный
55 TEXT-0010 Наматрасник непромокаемый 160×200 Текстиль Сатин 189000 магнит
56 TEXT-0011 Покрывало стёганое 180×210 Текстиль Сатин 238999 обычный
57 TEXT-0012 Плед флисовый 150×200 Текстиль Сатин 118990 обычный
58 TEXT-0013 Полотенце лицевое 50×90 Текстиль Сатин 49000 обычный
59 TEXT-0014 Полотенца кухонные 40×60 3 штуки Текстиль Сатин 59000 обычный
60 TEXT-0015 Коврик для ванной хлопковый 50×80 Текстиль Ладный дом 99000 залёживается
61 TEXT-0016 Штора для ванной 180×200 Текстиль Ладный дом 79000 обычный
62 TEXT-0017 Тюль вуаль 300×260 Текстиль Ладный дом 199000 обычный
63 TEXT-0018 Шторы кухонные 150×170 2 полотна Текстиль Ладный дом 179000 залёживается
64 TEXT-0019 Скатерть водоотталкивающая 140×180 Текстиль Ладный дом 129000 обычный
65 TEXT-0020 Дорожка на стол хлопковая 40×140 Текстиль Ладный дом 59000 залёживается
66 TEXT-0021 Салфетки столовые льняные 4 штуки Текстиль Сатин 79000 обычный
67 TEXT-0022 Чехлы на табурет 35×35 4 штуки Текстиль Ладный дом 108990 обычный
68 TEXT-0023 Подушка декоративная 40×40 Текстиль Сатин 68999 магнит
69 TEXT-0024 Чехол для диванной подушки 45×45 Текстиль Сатин 39000 обычный
70 TEXT-0025 Фартук кухонный с карманом Текстиль Ладный дом 59000 магнит
71 TEXT-0026 Прихватки стёганые 20×20 2 штуки Текстиль Ладный дом 38990 магнит
72 TEXT-0027 Халат вафельный размер 48–50 Текстиль Сатин 199000 залёживается
73 TEXT-0028 Комплект полотенец 50×90 и 70×140 Текстиль Сатин 138999 обычный
74 TEXT-0029 Одеяло детское байковое 100×140 Текстиль Сатин 169000 обычный
75 TEXT-0030 Подушка для стула 40×40 Текстиль Ладный дом 59000 обычный
76 POSU-0005 Кастрюля эмалированная 3 л Посуда Кухмастер 168990 обычный
77 POSU-0006 Ковш нержавеющий с крышкой 1.5 л Посуда Кухмастер 138990 залёживается
78 POSU-0007 Сотейник литой с крышкой 28 см Посуда Кухмастер 269000 магнит
79 POSU-0008 Сковорода блинная 22 см Посуда Кухмастер 149000 обычный
80 POSU-0009 Казан алюминиевый 6 л Посуда Кухмастер 299000 обычный
81 POSU-0010 Форма для запекания стеклянная 2.4 л Посуда Кухмастер 118990 магнит
82 POSU-0011 Противень стальной 37×27 Посуда Кухмастер 79000 залёживается
83 POSU-0012 Набор форм для выпечки 3 штуки Посуда Кухмастер 139000 залёживается
84 POSU-0013 Миска нержавеющая 24 см Посуда Кухмастер 68990 залёживается
85 POSU-0014 Дуршлаг стальной с ручками 22 см Посуда Кухмастер 89000 залёживается
86 POSU-0015 Тёрка четырёхгранная 23 см Посуда Кухмастер 49000 обычный
87 POSU-0016 Доска разделочная бамбуковая 35×25 Посуда Кухмастер 78990 магнит
88 POSU-0017 Набор ножей с подставкой 6 предметов Посуда Кухмастер 248990 обычный
89 POSU-0018 Половник и шумовка 2 предмета Посуда Кухмастер 58990 обычный
90 POSU-0019 Контейнеры пищевые 7 предметов Посуда Кухмастер 99000 залёживается
91 POSU-0020 Банки для сыпучих продуктов 4 штуки Посуда Ладья 138990 залёживается
92 POSU-0021 Тарелки обеденные фарфоровые 6 штук Посуда Ладья 178999 обычный
93 POSU-0022 Тарелки суповые керамические 4 штуки Посуда Ладья 129000 обычный
94 POSU-0023 Салатник стеклянный 23 см Посуда Ладья 68999 обычный
95 POSU-0024 Кружки керамические 350 мл 4 штуки Посуда Ладья 118990 обычный
96 POSU-0025 Стаканы высокие 400 мл 6 штук Посуда Ладья 98990 обычный
97 POSU-0026 Бокалы для воды 300 мл 6 штук Посуда Ладья 149000 обычный
98 POSU-0027 Термос стальной 1 л Посуда Ладья 179000 залёживается
99 POSU-0028 Термокружка с крышкой 450 мл Посуда Ладья 99000 залёживается
100 POSU-0029 Сахарница фарфоровая 400 мл Посуда Ладья 59000 магнит
101 POSU-0030 Маслёнка стеклянная с крышкой Посуда Ладья 68990 обычный
102 TECH-0005 Миксер ручной 500 Вт Бытовая техника Электрон 248990 обычный
103 TECH-0006 Измельчитель с чашей 1.5 л Бытовая техника Электрон 298990 обычный
104 TECH-0007 Стационарный блендер 1.8 л Бытовая техника Электрон 549000 магнит
105 TECH-0008 Мясорубка электрическая 1800 Вт Бытовая техника Электрон 799000 магнит
106 TECH-0009 Тостер на 2 ломтика 850 Вт Бытовая техника Электрон 299000 залёживается
107 TECH-0010 Вафельница электрическая 1000 Вт Бытовая техника Электрон 348999 обычный
108 TECH-0011 Мультиварка с чашей 5 л Бытовая техника Электрон 699000 магнит
109 TECH-0012 Электроплитка на 1 конфорку 1500 Вт Бытовая техника Электрон 249000 обычный
110 TECH-0013 Мини-печь с грилем 30 л Бытовая техника Электрон 899000 залёживается
111 TECH-0014 Микроволновая печь 20 л Бытовая техника Электрон 999000 обычный
112 TECH-0015 Сушилка для овощей 5 поддонов Бытовая техника Электрон 499000 обычный
113 TECH-0016 Кофемолка электрическая 200 Вт Бытовая техника Электрон 199000 обычный
114 TECH-0017 Капельная кофеварка 1.2 л Бытовая техника Электрон 399000 залёживается
115 TECH-0018 Соковыжималка шнековая 150 Вт Бытовая техника Электрон 799000 залёживается
116 TECH-0019 Утюг паровой 2400 Вт Бытовая техника Электрон 349000 обычный
117 TECH-0020 Швейная машина 12 операций Бытовая техника Электрон 1299000 обычный
118 TECH-0021 Пылесос контейнерный 2000 Вт Бытовая техника Электрон 1099000 залёживается
119 TECH-0022 Пылесос вертикальный аккумуляторный Бытовая техника Электрон 1499000 магнит
120 TECH-0023 Электровеник с контейнером 0.5 л Бытовая техника Электрон 699000 магнит
121 TECH-0024 Фен с диффузором 2200 Вт Бытовая техника Электрон 299000 обычный
122 TECH-0025 Щипцы для волос 45 Вт Бытовая техника Электрон 249000 обычный
123 TECH-0026 Машинка для стрижки с 6 насадками Бытовая техника Электрон 399000 залёживается
124 TECH-0027 Весы напольные до 180 кг Бытовая техника Электрон 198990 залёживается
125 TECH-0028 Весы кухонные до 5 кг Бытовая техника Электрон 148990 обычный
126 TECH-0029 Увлажнитель воздуха 3 л Бытовая техника Электрон 499000 обычный
127 TECH-0030 Тепловентилятор керамический 1500 Вт Бытовая техника Электрон 398990 магнит
128 KIDS-0004 Кроватка кукольная деревянная 45 см Детские товары Малышок 248999 обычный
129 KIDS-0005 Манеж складной 100×100 Детские товары Малышок 798990 залёживается
130 KIDS-0006 Горшок детский со спинкой Детские товары Малышок 79000 магнит
131 KIDS-0007 Ванночка детская 84 см Детские товары Малышок 168990 обычный
132 KIDS-0008 Подставка детская двухступенчатая Детские товары Малышок 89000 залёживается
133 KIDS-0009 Накладка на унитаз с ручками Детские товары Малышок 69000 обычный
134 KIDS-0010 Сиденье для купания на присосках Детские товары Малышок 138990 залёживается
135 KIDS-0011 Пелёнки муслиновые 80×100 3 штуки Детские товары Малышок 129000 обычный
136 KIDS-0012 Одеяло для коляски 90×110 Детские товары Малышок 148990 магнит
137 KIDS-0013 Нагрудники непромокаемые 5 штук Детские товары Малышок 58999 обычный
138 KIDS-0014 Бутылочка с ручками 250 мл Детские товары Малышок 49000 обычный
139 KIDS-0015 Поильник непроливайка 300 мл Детские товары Малышок 54990 залёживается
140 KIDS-0016 Контейнер для пустышки 2 штуки Детские товары Малышок 25000 магнит
141 KIDS-0017 Погремушки мягкие 3 штуки Детские товары Малышок 59000 магнит
142 KIDS-0018 Кубики развивающие 12 штук Детские товары Малышок 89000 залёживается
143 KIDS-0019 Пирамидка деревянная 8 колец Детские товары Малышок 78990 обычный
144 KIDS-0020 Сортер с геометрическими фигурами Детские товары Малышок 98990 обычный
145 KIDS-0021 Конструктор деревянный 50 деталей Детские товары Малышок 139000 обычный
146 KIDS-0022 Доска для рисования магнитная 30×40 Детские товары Малышок 128990 обычный
147 KIDS-0023 Мольберт детский двусторонний 105 см Детские товары Малышок 298999 залёживается
148 KIDS-0024 Набор для лепки 12 цветов Детские товары Малышок 69000 обычный
149 KIDS-0025 Коврик игровой складной 180×150 Детские товары Малышок 399000 обычный
150 KIDS-0026 Палатка игровая с окошками Детские товары Малышок 199000 обычный
151 KIDS-0027 Каталка с ручкой и звуком Детские товары Малышок 169000 залёживается
152 KIDS-0028 Самокат трёхколёсный до 50 кг Детские товары Малышок 298999 обычный
153 KIDS-0029 Санки со спинкой и ремнём Детские товары Малышок 349000 залёживается
154 KIDS-0030 Рюкзак детский объёмом 8 л Детские товары Малышок 128990 обычный
155 WEAR-0004 Футболка хлопковая размер 48 Одежда и обувь Сатин 78990 залёживается
156 WEAR-0005 Майка хлопковая размер 46 Одежда и обувь Сатин 58990 обычный
157 WEAR-0006 Лонгслив трикотажный размер 50 Одежда и обувь Сатин 108990 магнит
158 WEAR-0007 Водолазка хлопковая размер 44 Одежда и обувь Сатин 118990 обычный
159 WEAR-0008 Толстовка с капюшоном размер 52 Одежда и обувь Сатин 198999 обычный
160 WEAR-0009 Брюки спортивные размер 50 Одежда и обувь Сатин 169000 залёживается
161 WEAR-0010 Легинсы хлопковые размер 46 Одежда и обувь Сатин 98990 залёживается
162 WEAR-0011 Пижама трикотажная размер 48 Одежда и обувь Сатин 179000 обычный
163 WEAR-0012 Ночная сорочка хлопковая размер 50 Одежда и обувь Сатин 128999 обычный
164 WEAR-0013 Фартук рабочий с тремя карманами Одежда и обувь Сатин 79000 залёживается
165 WEAR-0014 Перчатки хозяйственные 3 пары Одежда и обувь Ходики 39000 обычный
166 WEAR-0015 Варежки вязаные размер 8 Одежда и обувь Ходики 68990 обычный
167 WEAR-0016 Шапка вязаная с отворотом размер 56 Одежда и обувь Ходики 88990 магнит
168 WEAR-0017 Шарф шерстяной 30×180 Одежда и обувь Ходики 119000 обычный
169 WEAR-0018 Ремень текстильный длиной 120 см Одежда и обувь Ходики 78990 обычный
170 WEAR-0019 Стельки войлочные 2 пары Одежда и обувь Ходики 38990 залёживается
171 WEAR-0020 Шнурки круглые 120 см 3 пары Одежда и обувь Ходики 25000 обычный
172 WEAR-0021 Кеды текстильные размер 42 Одежда и обувь Ходики 179000 обычный
173 WEAR-0022 Кроссовки прогулочные размер 39 Одежда и обувь Ходики 248990 обычный
174 WEAR-0023 Ботинки утеплённые размер 41 Одежда и обувь Ходики 348990 залёживается
175 WEAR-0024 Сапоги резиновые размер 40 Одежда и обувь Ходики 199000 обычный
176 WEAR-0025 Полуботинки кожаные размер 43 Одежда и обувь Ходики 398999 залёживается
177 WEAR-0026 Сандалии с ремешками размер 38 Одежда и обувь Ходики 189000 обычный
178 WEAR-0027 Сланцы резиновые размер 41 Одежда и обувь Ходики 79000 обычный
179 WEAR-0028 Балетки текстильные размер 37 Одежда и обувь Ходики 149000 залёживается
180 WEAR-0029 Туфли домашние утеплённые размер 39 Одежда и обувь Ходики 129000 магнит
181 WEAR-0030 Галоши садовые размер 42 Одежда и обувь Ходики 98990 обычный
+6 -4
View File
@@ -223,10 +223,12 @@ smoke — сравнение `system.columns` поднятого стенда с
## 3. Каталог товаров
CSV в репозитории (`data/catalog/products.csv`: `sku`, `name`, `category`,
`brand`, `price`) — **словарь ClickHouse** из файла. Тот же файл использует
генератор — расхождений нет по построению. Даёт `dictGet` в витринах и
разговор о политике обновления словаря. На кластере файл монтируется в обе
ноды, словарь создаётся ON CLUSTER.
`brand`, `price`, `demand`) — **словарь ClickHouse** из файла. Тот же файл
использует генератор — расхождений нет по построению. Даёт `dictGet` в
витринах и разговор о политике обновления словаря. На кластере файл
монтируется в обе ноды, словарь создаётся ON CLUSTER. Последняя колонка —
уровень спроса товара, заведена при исполнении #50 (спека генератора,
раздел 9): генератор решает по ней, что уходит из карточки в корзину.
## 4. Сверка `purchase` против заказов
+84 -8
View File
@@ -754,14 +754,90 @@ pytest-тест с маркером `perf` и таймаутом-обрубан
склонность из цены* — жёсткая связь, в которой менти найдёт ровно то,
что мы в неё вложили, и ничего сверх.
- **Границы правки — те же вилки раздела 5**: аудитория 6–8 тыс., средний
день около 50 тыс. событий (сейчас 49 509 — запас невелик, и рост
событий корзины считается против него), конверсия визита около 2%
(сейчас 2,43%). Заказ рисуется из корзины, а корзина сузилась, поэтому
доля брошенных позиций (число мира — 15%, замер по покупающим корзинам —
12%) и состав заказа (1,87 позиции) перемеряются вместе с
остальным: сохраняется не число, а свойство — заказ уже корзины и
однопозиционных заказов около половины. Числа канонического мира после
правки перемеряются и переписываются.
день около 50 тыс. событий, конверсия визита около 2%. Заказ рисуется из
корзины, а корзина сузилась, поэтому доля брошенных позиций и состав
заказа перемеряются вместе с остальным: сохраняется не число, а
свойство — заказ уже корзины и однопозиционных заказов около половины.
**Измерено после правки #50** (канонический мир, 14 дней; правый столбец —
то же до правки). Доли по уровням и состав заказа считаются по окну снимка
целиком, а не как среднее дневных долей: заказов в дне около 240, и на одном
дне такая доля гуляет на несколько пунктов от одной случайности выборки.
Трафиковая половина не сдвинулась ни на строку: набор просмотров страниц
сверен со снимком, снятым до первой правки.
| величина | стало | было |
|---|---|---|
| дневная аудитория | 6 637–7 595, средняя 7 166 | без изменений |
| визитов в день | 9 879 | без изменений |
| событий в день, средних | 49 834 | 49 509 |
| просмотров карточек в день | 25 067 | без изменений |
| событий корзины в день | 2 161, в 1 306 визитах | 1 836, в 765 визитах |
| из них визиты без страницы `/cart` | 541 — 41,4% | 0 |
| визитов со страницей `/cart` без позиции | 0 | 0 |
| просмотров карточек, дошедших до корзины | 8,62% | 7,32% |
| конверсия карточки в корзину: магнит / обычный / залёживается | 13,1% / 9,2% / 5,8% | 7,7% у всех |
| событий корзины вне `/cart`: магнит / обычный / залёживается | 42,0% / 20,2% / 17,1% | 0 у всех |
| конверсия визита в покупку | 2,43% | без изменений |
| позиций на заказ | 1,74; однопозиционных 56,4% | 1,87; 50,3% |
| брошено позиций в покупающих корзинах | 7,25% | 12,0% |
Знаменатель «просмотров карточек» здесь — только просмотры страниц. При
постановке #50 в него попали и строки событий корзины: событие садится на
карточку и несёт её же страницу, отчего вышло 26 903 просмотра и 6,82%
вместо 25 067 и 7,32%. Величина одна и та же, счёт разный.
Решено при исполнении #50 самой реализацией:
- **Намерение визита берётся из шага воронки, а не из метки покупателя.**
Кладёт всякий визит, открывший карточку, но дошедший до `/cart` кладёт
почти всё, что сравнивал, а прочий — редко. Метка покупателя в это
решение не заходит, и не потому, что не влияет: она уже влияет — через
воронку, где помеченному дано 18% против 6%. Взять её вторым слагаемым
значило бы посчитать одно и то же дважды и протянуть план состава сквозь
шов, по которому день отдаёт торговой половине только страницы и товары.
Плата названа: у визита без намерения добавление ни с чем в человеке не
связано — упрощение, но не ложь, потому что склонные покупать и так
преобладают среди дошедших до `/cart`.
- **Два ряда вероятностей, а не ряд с множителем.** Уровень спроса читается
дважды: ряд для визита с намерением и ряд для того, кто смотрит. Один ряд
с множителем намерения не годится: вероятности визита с намерением обязаны
быть высокими (иначе его корзина схлопывается до одной позиции, а заказ до
одного товара), а у потолка в 100% разница уровней съедается, и конверсия
по уровням перестаёт различаться. Разрыв уровней внутри рядов поэтому
разный: у смотрящего товар решает почти всё, у пришедшего покупать —
заслонён намерением.
- **Доля брошенных позиций опущена с 15% до 10%.** Корзина сузилась с 2,4
позиции до 1,7, и прежние 15% чаще выносили её целиком: спасённая позиция
возвращала заказ к одному товару, и однопозиционных заказов набиралось под
60%. Свойство сохранено, число перемерено — ровно так, как этот блок и
оговаривал.
- **Ни одному уровню спроса нельзя дать нулевую вероятность.** Ноль у
залежавшегося товара выглядел заманчиво — он дешевле всего разводил
конверсию уровней, — но по товарам этого уровня возвращал ровно то
равенство, ради которого правка и делалась: событие корзины случалось бы
только внутри визита, открывшего `/cart`, без единого исключения на
четверти каталога. Отвязка либо про весь ассортимент, либо она не отвязка.
Разводить уровни поэтому приходится не нулём внизу второго ряда, а
ослаблением залежавшегося в первом: пришедший покупать берёт его реже
ходового.
- **Уровень спроса — колонка `demand` в конце файла каталога**, значения
словами («магнит», «обычный», «залёживается»). Раздан он одной колодой на
весь каталог: доли по категориям расходятся сами (магнитов от четырёх до
девяти на тридцать строк). Раскладывать колоду поровну в каждую категорию
было бы той же неправдоподобной ровностью, какую тикет вычищал из данных.
Связь с ценой отклонена выше, и тест сторожит её отсутствие — по четвертям
цены и по медиане, потому что одни только края списка обманываются
четырьмя исключениями. Хвост соседям: словарь ClickHouse над этим файлом
ещё не построен (#37/#43) — колонку надо взять в его описание сразу, а не
догонять правкой; манифесту нужен хеш каталога (хвост #42) тем более,
потому что теперь в файле живёт ещё и поведение.
- **Доля просмотров карточек, доходящих до корзины, выросла с 7,3% до 8,6%
и подошла к потолку вилки.** Иначе не сошлось: треть кладущих визитов вне
`/cart` требует своих позиций, а состав заказа требует, чтобы визит с
намерением клал почти всё сравненное. Обе границы тянут число вверх, и
опустить его — значит отпустить одну из них. Место до 9% невелико, и
следующая правка торговой половины считается против него.
Остаётся открытым, за тикетами:
+22 -9
View File
@@ -10,16 +10,22 @@
раз в неделю, корзина в двадцать позиций.
Что решено формой файла, а не его длиной: колонки `sku,name,category,brand,
price`; артикул четыре латинские буквы категории, дефис и четыре цифры;
цена целые копейки (деньги генератор считает целыми, спека, раздел 2).
Часть цен кратна рублю, часть несёт копейки как в обычной рознице
(1 289,90 ). Без копеек урок про Float64 был бы беспредметным: округлять
нечего, и разрыв между `productPrice` и `purchaseRevenue` пришлось бы
выдумывать (спека генератора, раздел 9).
price,demand`; артикул четыре латинские буквы категории, дефис и четыре
цифры; цена целые копейки (деньги генератор считает целыми, спека,
раздел 2). Часть цен кратна рублю, часть несёт копейки как в обычной
рознице (1 289,90 ). Без копеек урок про Float64 был бы беспредметным:
округлять нечего, и разрыв между `productPrice` и `purchaseRevenue`
пришлось бы выдумывать (спека генератора, раздел 9).
Строк в файле может быть сколько угодно: ни генератор, ни тесты их не
считают, а товар для карточки выбирается равномерно внутри категории.
Популярность товаров не моделируется придумывать вес каждой строке
пришлось бы вручную, а каталог растёт механически.
**Уровень спроса** привлекательность товара, а не частота его показа:
карточку любого товара открывают одинаково часто, но магнит из открытой
карточки уходит в корзину чаще, чем залежавшийся. Уровень приписан строке
в файле он постоянная часть мира, а не бросок дня, и приписан россыпью:
ни с ценой, ни с категорией он не связан. Связь с ценой была бы жёсткой, и
менти нашёл бы в данных ровно то, что мы в них вложили (спека генератора,
раздел 9). Сами вероятности живут в числах мира, здесь только имена уровней.
"""
import csv
@@ -34,7 +40,11 @@ from numpy.typing import NDArray
# Каталог не настраивается извне — он часть мира, а не запуска.
CATALOG_PATH = Path(__file__).resolve().parents[3] / "data" / "catalog" / "products.csv"
COLUMNS = ("sku", "name", "category", "brand", "price")
COLUMNS = ("sku", "name", "category", "brand", "price", "demand")
# Уровни спроса по убыванию желанности: порядок здесь — порядок чисел мира,
# которыми уровень превращается в вероятность (`world.SHOPPING_ADD_PERCENT`).
DEMAND_LEVELS = ("магнит", "обычный", "залёживается")
@dataclass(frozen=True, slots=True)
@@ -70,6 +80,7 @@ class Catalog:
category: NDArray[np.int64]
brand: NDArray[np.object_]
price: NDArray[np.int64]
demand: NDArray[np.int64]
grouped: NDArray[np.int64]
first: NDArray[np.int64]
count: NDArray[np.int64]
@@ -85,12 +96,14 @@ def catalog() -> Catalog:
category = np.array([index[row["category"]] for row in rows], dtype=np.int64)
grouped = np.argsort(category, kind="stable")
count = np.bincount(category, minlength=len(CATEGORIES))
level = {name: number for number, name in enumerate(DEMAND_LEVELS)}
return Catalog(
sku=np.array([row["sku"] for row in rows], dtype=object),
name=np.array([row["name"] for row in rows], dtype=object),
category=category,
brand=np.array([row["brand"] for row in rows], dtype=object),
price=np.array([int(row["price"]) for row in rows], dtype=np.int64),
demand=np.array([level[row["demand"]] for row in rows], dtype=np.int64),
grouped=grouped,
first=np.concatenate(([0], np.cumsum(count)[:-1])),
count=count,
+98 -32
View File
@@ -8,12 +8,18 @@
граница модельных суток режет всё, что за неё вышло: визит, у которого
подтверждение срезано полуночью, покупки не даёт заказа не было.
**Корзина шире заказа.** Посетитель кладёт товары тех карточек, которые
открывал в этом визите; карточка, открытая дважды, даёт одну позицию
повторный просмотр это раздумье, а не второй товар. Покупает он не всё:
часть позиций остаётся брошенной. Иначе событие корзины не рассказывало бы
ничего сверх покупки заказ был бы её точной копией, и сравнивать было бы
нечего.
**Кладёт любой визит, открывший карточку.** Положить и корзину не открыть
самый массовый сюжет магазина, поэтому событие корзины не привязано к
странице `/cart`: иначе факт добавления идеально предсказывался бы более
поздним просмотром страницы, а такого равенства в живых данных не бывает.
Решается каждая открытая карточка отдельно, а не визит целиком, и решают её
двое: намерение визита и уровень спроса товара (числа мира). Карточка,
открытая дважды, даёт одну позицию повторный просмотр это раздумье, а не
второй товар.
**Корзина шире заказа.** Покупает посетитель не всё, что положил: часть
позиций остаётся брошенной. Иначе событие корзины не рассказывало бы ничего
сверх покупки заказ был бы её точной копией, и сравнивать было бы нечего.
**Деньги считаются целыми копейками.** В колонку `productPrice` ложатся
целые рубли, как у Метрики, а дробное число в событии одно
@@ -145,7 +151,7 @@ def weave(
события встают между ними, и поток пересобирается одним порядком.
"""
rng = day_stream(seed, day, Component.COMMERCE)
baskets = _baskets(page, product, columns["VisitID"])
baskets = _baskets(rng, page, product, columns["VisitID"])
if not len(baskets):
return columns, page, product
@@ -172,13 +178,24 @@ def _draws(rng: np.random.Generator, baskets: _Baskets) -> _Draws:
def _baskets(
page: NDArray[np.uint8], product: NDArray[np.int64], visit: NDArray[np.uint64]
rng: np.random.Generator,
page: NDArray[np.uint8],
product: NDArray[np.int64],
visit: NDArray[np.uint64],
) -> _Baskets:
"""Что посетитель положил в корзину и дошёл ли до заказа.
Корзина есть у визита, дошедшего до страницы корзины; в ней товары всех
карточек этого визита. Карточка, открытая дважды, даёт одну позицию
остаётся первая: тогда товар и кладут.
Корзина есть у всякого визита, положившего хоть что-то, не только у
дошедшего до страницы `/cart`. Кандидаты карточки, открытые в визите,
по одной на товар: карточка, открытая дважды, даёт одну позицию, и
остаётся первая тогда товар и кладут.
Намерение визита берётся из шага воронки: тот, кто дошёл до страницы
корзины, пришёл покупать. Позиция у него есть всегда страница корзины
при пустой корзине невозможна, и держится это высокой вероятностью, а
не принуждением: спасать приходится считаные проценты таких визитов.
Принуждай мы всех, корзина схлопнулась бы до одной позиции, а заказ до
одного товара.
"""
order = np.argsort(visit, kind="stable")
page, product, visit = page[order], product[order], visit[order]
@@ -187,54 +204,103 @@ def _baskets(
# упорядоченным, а сортировка по визиту устойчивая.
started = np.concatenate(([True], visit[1:] != visit[:-1]))
number = np.cumsum(started) - 1
visits = int(started.sum())
with_cart = number[page == Page.CART]
basket_of_visit = np.full(int(started.sum()), -1, dtype=np.int64)
basket_of_visit[with_cart] = np.arange(with_cart.size)
cards = np.flatnonzero((page == Page.PRODUCT) & (basket_of_visit[number] >= 0))
goods = catalog.catalog()
cards = np.flatnonzero(page == Page.PRODUCT)
# Ключ «визит и товар»: `np.unique` отдаёт индексы первых вхождений,
# поэтому вторая карточка того же товара позиции не добавляет.
key = number[cards] * catalog.catalog().sku.size + product[cards]
positions = np.sort(cards[np.unique(key, return_index=True)[1]])
# поэтому вторая карточка того же товара кандидата не добавляет.
key = number[cards] * goods.sku.size + product[cards]
opened = np.sort(cards[np.unique(key, return_index=True)[1]])
holder = number[opened]
# Подтверждение без корзины невозможно: полночь режет визит с хвоста, а
# корзина в нём раньше подтверждения — поэтому у каждого подтверждения
# корзина есть, и номер её всегда найдётся.
shopping = np.zeros(visits, dtype=np.bool_)
shopping[number[page == Page.CART]] = True
taken = _taken(rng, goods.demand[product[opened]], shopping[holder])
# Кандидаты визита лежат подряд, визиты идут по порядку — поэтому начало
# каждого куска находится накопленной суммой.
opened_count = np.bincount(holder, minlength=visits)
opened_first = np.cumsum(opened_count) - opened_count
empty = shopping & (np.bincount(holder[taken], minlength=visits) == 0)
_at_least_one(rng, taken, opened_first, opened_count, empty)
positions, owner = opened[taken], holder[taken]
with_basket = np.unique(owner)
basket_of_visit = np.full(visits, -1, dtype=np.int64)
basket_of_visit[with_basket] = np.arange(with_basket.size)
# Подтверждение без корзины невозможно: до него визит прошёл страницу
# корзины, а у неё позиция есть всегда — поэтому номер корзины найдётся.
confirmed = np.flatnonzero(page == Page.CONFIRMATION)
confirmation = np.full(with_cart.size, -1, dtype=np.int64)
confirmation = np.full(with_basket.size, -1, dtype=np.int64)
confirmation[basket_of_visit[number[confirmed]]] = order[confirmed]
basket = basket_of_visit[number[positions]]
count = np.bincount(basket, minlength=with_cart.size)
basket = basket_of_visit[owner]
count = np.bincount(basket, minlength=with_basket.size)
return _Baskets(
anchor=order[positions],
product=product[positions],
basket=basket,
# Позиции лежат подряд, корзины идут по порядку визитов — поэтому
# начало каждого куска находится накопленной суммой.
# Позиции лежат подряд, корзины идут по порядку визитов — начало
# каждого куска находится той же накопленной суммой.
first=np.cumsum(count) - count,
count=count,
confirmation=confirmation,
)
def _taken(
rng: np.random.Generator,
demand: NDArray[np.int64],
shopping: NDArray[np.bool_],
) -> NDArray[np.bool_]:
"""Дошла ли открытая карточка до корзины: решают намерение и товар.
Два ряда чисел мира по уровням спроса: один для визита, пришедшего
покупать, другой для того, кто просто смотрит. Товар решает у обоих, но
у смотрящего он решает почти всё потому конверсия «карточка корзина»
по уровням спроса и становится различимой.
"""
chance = np.where(
shopping,
np.array(world.SHOPPING_ADD_PERCENT)[demand],
np.array(world.BROWSING_ADD_PERCENT)[demand],
)
return rng.integers(0, 100, demand.size) < chance
def _kept(rng: np.random.Generator, baskets: _Baskets) -> NDArray[np.bool_]:
"""Какие позиции корзины дошли до заказа: часть остаётся брошенной.
Пустым заказ не бывает: если брошены все позиции, одна возвращается
какая, решает свой бросок. Хотя бы одна позиция у корзины есть всегда:
перед корзиной визит обязательно открывал карточку.
Пустым заказ не бывает: если брошены все позиции, одна возвращается.
"""
kept = (
rng.integers(0, 100, baskets.product.size) >= world.ABANDONED_POSITION_PERCENT
)
rescued = baskets.first + rng.integers(0, baskets.count)
empty = np.bincount(baskets.basket[kept], minlength=len(baskets)) == 0
kept[rescued[empty]] = True
_at_least_one(rng, kept, baskets.first, baskets.count, empty)
return kept
def _at_least_one(
rng: np.random.Generator,
chosen: NDArray[np.bool_],
first: NDArray[np.int64],
count: NDArray[np.int64],
empty: NDArray[np.bool_],
) -> None:
"""Возвращает один выбор тем группам, у которых бросок унёс все.
Приём общий у двух мест: у визита выбираются карточки, у корзины
позиции, а правило одно пустой ни та, ни другая быть не может. Какой
выбор вернуть, решает свой бросок: брать первый значило бы, что ранняя
карточка визита переживает любой бросок. Группа без членов сюда не
приходит: у визита со страницей корзины карточка была, у корзины
позиция.
"""
chosen[first[empty] + rng.integers(0, count[empty])] = True
def _coupons(rng: np.random.Generator, baskets: int) -> NDArray[np.int64]:
"""Промокод корзины: номер строки в таблице кодов или −1, если кода нет."""
code = rng.integers(0, len(world.COUPONS), baskets)
+3 -2
View File
@@ -258,8 +258,9 @@ def _walk(
goods = catalog.catalog()
shown = page == Page.PRODUCT
row_category = np.repeat(visits.category, visits.pages)[shown]
# Товар — равномерно внутри категории визита: популярность строк не
# моделируется, каталог дорастает механически (см. `catalog`).
# Товар — равномерно внутри категории визита: карточки всех товаров
# открывают одинаково часто, а различает их уровень спроса — уже в
# корзине, а не в показе (см. `catalog`).
inside = rng.integers(0, goods.count[row_category])
product = np.full(total, -1, dtype=np.int64)
product[shown] = goods.grouped[goods.first[row_category] + inside]
+30 -1
View File
@@ -160,11 +160,40 @@ BUYER_CHECKOUT_OF_CART_PERCENT = 60
# страницу, на которой посетитель его нажал.
TRADE_DELAY_SECONDS = (2, 8)
# Кто и что кладёт в корзину: доля открытых карточек, которая до неё дошла.
# Оба ряда читаются по уровням спроса товара (`catalog.DEMAND_LEVELS`):
# магнит, обычный, залёживается.
#
# Первый ряд — визит, дошедший до страницы корзины. Он пришёл покупать и
# кладёт почти всё, что сравнивал; уровень решает, что именно из сравненного
# он выберет, и залежавшийся товар проигрывает соседям по вкладке. Верх ряда
# высок намеренно: опусти его — и корзина такого визита схлопнется до одной
# позиции, а заказ до одного товара.
#
# Второй ряд — визит, который до страницы корзины не дошёл. Кладёт он редко,
# и магнит уходит у него из карточки втрое чаще обычного товара и вшестеро
# чаще залежавшегося: случайного посетителя решает соблазнить сам товар,
# больше решать нечему. Событий при этом выходит почти поровну — магнитов
# 47% таких добавлений, обычных 42%: обычных в каталоге вдвое с лишним
# больше, и разница уровней живёт в доле открытых карточек, а не в счёте
# событий. Нулю в этом ряду места нет: обнулить уровень значило бы вернуть
# по его товарам ровно то равенство «положил — открыл корзину», ради
# которого правка и делалась.
#
# Спрос растянут во втором ряду сильнее, чем в первом, и это не подгонка:
# намерение заслоняет товар, поэтому привлекательность видна там, где
# намерения нет.
SHOPPING_ADD_PERCENT = (98, 94, 55)
BROWSING_ADD_PERCENT = (6, 2, 1)
# Доля позиций корзины, которые остались брошенными: заказ уже корзины.
# Иначе событие корзины не рассказывало бы ничего сверх покупки — заказ был
# бы её точной копией, и сравнивать было бы нечего. Пустым заказ не бывает:
# если брошены все позиции, одна остаётся (спека генератора, раздел 9).
ABANDONED_POSITION_PERCENT = 15
# Число опущено с 15% вместе с отвязкой корзины от страницы корзины: корзина
# стала уже (1,7 позиции вместо 2,4), и прежние 15% чаще выносили её целиком,
# а спасённая позиция возвращала заказ к одному товару.
ABANDONED_POSITION_PERCENT = 10
# Сколько штук одного товара берут: веса для 1, 2, 3 штук. Обычно одна,
# изредка две-три — непродовольственная розница.
+92
View File
@@ -6,8 +6,10 @@
целая цена.
"""
import collections
import csv
import re
import statistics
import numpy as np
@@ -78,6 +80,96 @@ def test_categories_are_told_apart_by_prefix_and_by_address():
assert len(prefixes) == len(slugs) == len(catalog.CATEGORIES)
def test_every_row_carries_a_known_demand_level():
"""Уровень спроса есть у каждой строки, и уровней ровно три."""
known = set(catalog.DEMAND_LEVELS)
by_category: dict[str, set[str]] = {}
for row in rows():
by_category.setdefault(row["category"], set()).add(row["demand"])
assert set().union(*by_category.values()) == known
# Уровни рассыпаны по всему ассортименту: категории целиком из магнитов
# или целиком из залежавшегося в магазине не бывает.
for category, here in by_category.items():
assert here == known, category
def test_the_demand_is_scattered_and_not_dealt_out_evenly():
"""Одинаковый расклад в каждой категории — та же неправдоподобная ровность.
Ровно её тикет и вычищал из данных: в живом магазине магнитов в посуде и
в одежде не поровну. Уровень тянется из одной колоды на весь каталог,
поэтому доли категорий расходятся сами.
"""
magnets: dict[str, int] = {}
for row in rows():
magnets.setdefault(row["category"], 0)
magnets[row["category"]] += row["demand"] == catalog.DEMAND_LEVELS[0]
assert len(set(magnets.values())) > 1, magnets
def price_tells_the_level(catalogue: list[dict[str, str]]) -> bool:
"""Читается ли уровень спроса по цене товара.
Спрашивается двумя способами сразу, потому что каждый по отдельности
обманывается: по четвертям цены все ли три уровня встречаются в каждой,
и по медиане цены уровня не съехала ли она от медианы каталога. Одни
только края списка ничего не стерегут: четырёх исключений в самых
дешёвых и самых дорогих строках хватает, чтобы вернуть в них все уровни.
"""
known = set(catalog.DEMAND_LEVELS)
by_price = sorted(catalogue, key=lambda row: int(row["price"]))
step = len(by_price) // 4
quarters = [by_price[start : start + step] for start in range(0, 4 * step, step)]
if any({row["demand"] for row in part} != known for part in quarters):
return True
whole = statistics.median(int(row["price"]) for row in catalogue)
for level in catalog.DEMAND_LEVELS:
here = [int(row["price"]) for row in catalogue if row["demand"] == level]
if not 0.7 < statistics.median(here) / whole < 1.3:
return True
return False
def derived_from_price(catalogue: list[dict[str, str]]) -> list[dict[str, str]]:
"""Подложный каталог: уровень выведен из цены, по краям — исключения.
Исключения подобраны так, чтобы в самой дешёвой и самой дорогой четверти
нашлись все три уровня: столько и нужно, чтобы обмануть проверку, которая
смотрит только на края списка.
"""
magnet, usual, slow = catalog.DEMAND_LEVELS
by_price = sorted(catalogue, key=lambda row: int(row["price"]))
counted = collections.Counter(row["demand"] for row in catalogue)
deck = [level for level in catalog.DEMAND_LEVELS for _ in range(counted[level])]
forged = [
dict(row, demand=level) for row, level in zip(by_price, deck, strict=True)
]
forged[0]["demand"] = slow
forged[-1]["demand"] = magnet
forged[-2]["demand"] = usual
return forged
def test_the_demand_level_is_not_the_price_in_disguise():
"""Отклонённый вариант: выводить склонность из цены (спека, раздел 9).
Жёсткая связь дала бы менти ровно то, что мы в неё вложили: «дорогое
залёживается» читалось бы из каталога, а не из данных.
"""
assert not price_tells_the_level(rows())
# Сторож сторожа: подмени уровни ценой — проверка обязана покраснеть,
# и краевые исключения её не должны обманывать.
assert price_tells_the_level(derived_from_price(rows()))
def test_the_demand_level_reaches_the_arrays_row_by_row():
goods = catalog.catalog()
assert [catalog.DEMAND_LEVELS[number] for number in goods.demand.tolist()] == [
row["demand"] for row in rows()
]
def test_the_catalog_is_grouped_by_category_whatever_the_file_order():
goods = catalog.catalog()
assert goods.count.sum() == goods.sku.size
+200 -10
View File
@@ -21,6 +21,9 @@ from clickstream_generator.seeds import CANONICAL_SEED, Component
WEEKDAY = 2
WEEK = 7
# Окно эталонного снимка: доли по уровням спроса меряются на нём целиком —
# на одном дне у самого редкого уровня набирается слишком мало карточек.
FORTNIGHT = 14
@pytest.fixture(autouse=True)
@@ -40,6 +43,13 @@ def week() -> list[day.Day]:
return [day.stream(CANONICAL_SEED, number) for number in range(WEEK)]
@pytest.fixture(scope="module")
def fortnight(week: list[day.Day]) -> list[day.Day]:
"""Две недели: неделя пересчитанной не бывает — день чист от соседей."""
later = range(WEEK, FORTNIGHT)
return week + [day.stream(CANONICAL_SEED, number) for number in later]
def rows_of(events: day.Day, kind: str) -> dict[str, NDArray[Any]]:
"""Строки одного типа события — все колонки разом."""
here = events.columns["EventType"] == kind
@@ -69,11 +79,58 @@ def test_the_day_carries_both_trade_events(weekday: day.Day):
assert carts["WatchID"].size > 2 * orders["WatchID"].size
def visits_with(events: day.Day, page: int) -> set[int]:
"""Визиты, у которых эта страница магазина дожила до потока.
Считаются просмотры страниц, а не строки с такой же страницей: торговое
событие садится на свою страницу и несёт её же, поэтому маска по одному
`page` посчитала бы событие корзины за просмотр карточки.
"""
here = (events.columns["EventType"] == "pageview") & (events.page == page)
return set(events.columns["VisitID"][here].tolist())
def confirmed_visits(events: day.Day) -> set[int]:
"""Визиты, чья страница подтверждения дожила до потока."""
return visits_with(events, Page.CONFIRMATION)
def visits_that_put(events: day.Day) -> set[int]:
"""Визиты с событием корзины — по типу события, а не по массивам товаров.
Строка покупки тоже несёт товары, поэтому «строка с `productID`» это
не «событие корзины».
"""
return set(rows_of(events, commerce.ADD_TO_CART)["VisitID"].tolist())
def cart_products(events: day.Day) -> list[int]:
"""Номера товаров, положенных в корзину, — по позиции на строку."""
here = events.columns["EventType"] == commerce.ADD_TO_CART
return events.product[here].tolist()
def card_views(events: day.Day) -> int:
"""Просмотры карточек товара в дне — строками потока, с повторами."""
pageview = events.columns["EventType"] == "pageview"
here = pageview & (events.page == Page.CONFIRMATION)
return set(events.columns["VisitID"][here].tolist())
return int((pageview & (events.page == Page.PRODUCT)).sum())
def opened_cards(events: day.Day) -> set[tuple[int, int]]:
"""Разные карточки визитов: повторный просмотр внутри визита — один раз.
Так же считается и позиция корзины, поэтому это и есть честный
знаменатель конверсии «просмотр карточки корзина».
"""
pageview = events.columns["EventType"] == "pageview"
here = pageview & (events.page == Page.PRODUCT)
return set(
zip(
events.columns["VisitID"][here].tolist(),
events.product[here].tolist(),
strict=True,
)
)
def test_a_confirmation_in_the_stream_always_has_its_purchase(weekday: day.Day):
@@ -238,26 +295,63 @@ def test_the_rounded_price_does_not_add_up_to_the_revenue(weekday: day.Day):
assert 0.1 < apart / orders["WatchID"].size < 0.9
def test_the_order_is_narrower_than_the_cart(weekday: day.Day):
"""Часть положенных позиций не куплена — иначе сравнивать было бы нечего."""
carts = rows_of(weekday, commerce.ADD_TO_CART)
orders = rows_of(weekday, commerce.PURCHASE)
def basket_and_order(events: day.Day) -> tuple[list[int], list[int]]:
"""По каждому заказу — сколько позиций было в корзине и сколько куплено.
Заодно проверяется само отношение «заказ из корзины»: пустых заказов не
бывает, купленного мимо корзины не бывает, позиция в заказе одна на товар.
"""
carts = rows_of(events, commerce.ADD_TO_CART)
orders = rows_of(events, commerce.PURCHASE)
put: dict[int, set[str]] = {}
for visit, article in zip(
carts["VisitID"].tolist(), (cell[0] for cell in carts["productID"]), strict=True
):
put.setdefault(visit, set()).add(article)
dropped = whole = 0
baskets: list[int] = []
sizes: list[int] = []
for visit, bought in zip(
orders["VisitID"].tolist(), cells(orders["productID"]), strict=True
):
assert bought, "пустых заказов не бывает"
assert set(bought) <= put[visit], "куплено то, чего не клали в корзину"
assert len(set(bought)) == len(bought), "позиция в заказе одна на товар"
dropped += len(put[visit]) - len(bought)
whole += len(put[visit])
assert 0.05 < dropped / whole < 0.30
baskets.append(len(put[visit]))
sizes.append(len(bought))
return baskets, sizes
def test_the_order_is_narrower_than_the_cart(weekday: day.Day):
"""Часть положенных позиций не куплена — иначе сравнивать было бы нечего."""
baskets, sizes = basket_and_order(weekday)
assert sum(baskets) > sum(sizes)
def test_the_order_keeps_its_shape_across_the_snapshot(fortnight: list[day.Day]):
"""Состав заказа — доли окна снимка, а не одного дня.
Заказов в дне около 240, и доля однопозиционных гуляет по дням на
несколько пунктов от одной случайности выборки; вилки тикета заданы на
14 днях, там же они и меряются. Список позиций единственный носитель
урока про вложенный JSON: схлопнись он в один товар, урок бы умер.
"""
baskets: list[int] = []
sizes: list[int] = []
for events in fortnight:
was, bought = basket_and_order(events)
baskets += was
sizes += bought
assert 1.6 < sum(sizes) / len(sizes) < 2.2
assert 0.40 < sum(size == 1 for size in sizes) / len(sizes) < 0.60
# Брошено не ничего и не всё. Вилка та же, что и до правки: число мира
# опустилось с 15% до 10%, замер по покупающим корзинам — с 12,0% до
# 7,2%, и запас до нижней границы остался в полтора раза. Ниже её
# ронять незачем: с ней сторож замечает, что число мира срезали вдвое,
# а без неё — уже нет.
dropped = 1 - sum(sizes) / sum(baskets)
assert 0.05 < dropped < 0.30
def test_the_cart_holds_only_what_the_visitor_opened(weekday: day.Day):
@@ -284,6 +378,102 @@ def test_the_cart_holds_only_what_the_visitor_opened(weekday: day.Day):
assert len(set(products)) == len(products)
def test_putting_something_in_the_cart_is_not_opening_the_cart_page(
fortnight: list[day.Day],
):
"""Положил и корзину не открыл — самый массовый сюжет магазина.
До правки два множества совпадали в точности все 14 дней, и факт
добавления идеально предсказывался более поздним просмотром страницы
такого равенства в живых данных не бывает. Сторож меряет долю, а не факт
различия одним визитом: одно расхождение прошлую ложь не лечит.
"""
quiet = putting = 0
for events in fortnight:
put = visits_that_put(events)
quiet += len(put - visits_with(events, Page.CART))
putting += len(put)
assert quiet / putting > 1 / 3
def test_a_visit_that_opened_the_cart_page_has_something_in_the_cart(
fortnight: list[day.Day],
):
"""Страница корзины при пустой корзине невозможна.
Это включение намеренное и остаётся: ломалось обратное. Проверяется на
обеих неделях и на буднях, и на выходных: правило про всякий визит, а
не про удачный день.
"""
for events in fortnight:
assert visits_with(events, Page.CART) <= visits_that_put(events)
def test_no_demand_level_is_locked_behind_the_cart_page(fortnight: list[day.Day]):
"""Отвязка от страницы корзины — про весь ассортимент, а не про часть.
Обнули вероятность целому уровню и по его товарам вернётся ровно то
равенство, ради которого правка делалась: «положили значит, откроют
корзину», без единого исключения на четверти каталога. Сторож меряет
долю у каждого уровня, а не факт: одно событие такую примету не лечит.
"""
goods = catalog.catalog()
levels = range(len(catalog.DEMAND_LEVELS))
quiet = [0] * len(catalog.DEMAND_LEVELS)
put = [0] * len(catalog.DEMAND_LEVELS)
for events in fortnight:
shopping = visits_with(events, Page.CART)
here = events.columns["EventType"] == commerce.ADD_TO_CART
for visit, number in zip(
events.columns["VisitID"][here].tolist(),
events.product[here].tolist(),
strict=True,
):
put[goods.demand[number]] += 1
quiet[goods.demand[number]] += visit not in shopping
for level in levels:
assert quiet[level] / put[level] > 0.10, catalog.DEMAND_LEVELS[level]
def test_the_demand_level_tells_the_cart_conversion_apart(fortnight: list[day.Day]):
"""Привлекательность товара стала измеримой величиной, а не шумом.
Знаменатель разные карточки товаров этого уровня: повторный просмотр
внутри визита считается один раз, так же как считается позиция. До
правки решение принималось один раз на визит, и по sku выходил чистый
шум: внутри воронки 100%, вне её 0%.
"""
goods = catalog.catalog()
levels = range(len(catalog.DEMAND_LEVELS))
shown = [0] * len(catalog.DEMAND_LEVELS)
put = [0] * len(catalog.DEMAND_LEVELS)
for events in fortnight:
for _, number in opened_cards(events):
shown[goods.demand[number]] += 1
for number in cart_products(events):
put[goods.demand[number]] += 1
share = [put[level] / shown[level] for level in levels]
magnet, usual, slow = share
assert magnet > usual > slow
assert magnet >= 2 * slow
def test_the_cart_events_stay_inside_the_event_budget(fortnight: list[day.Day]):
"""Меняется не сколько кладут, а кто и что: бюджет событий на месте.
Доля просмотров карточек, дошедших до корзины, та же величина, что
держала бюджет до правки; средний день остаётся около 50 тыс. событий
(спека генератора, разделы 5 и 9).
"""
put = sum(len(cart_products(events)) for events in fortnight)
seen = sum(card_views(events) for events in fortnight)
assert 0.05 < put / seen < 0.09
average = sum(len(events) for events in fortnight) / len(fortnight)
assert 48_000 < average < 52_000
def test_a_trade_event_sits_on_the_page_that_sent_it(weekday: day.Day):
"""Корзина — на карточке того самого товара, покупка — на подтверждении.
+18 -1
View File
@@ -6,7 +6,7 @@
«в среднем 34 возврата» и «средняя кука активна 1,9 дня».
"""
from clickstream_generator import world
from clickstream_generator import catalog, world
def mean_by_weights(values: tuple[int, ...], weights: tuple[int, ...]) -> float:
@@ -150,6 +150,23 @@ def test_the_buyer_mark_shows_on_both_steps_of_the_funnel():
assert 2 <= lift <= 6
def test_the_demand_levels_keep_their_order_and_yield_to_the_intent():
"""Ряды вероятностей читаются по уровням каталога, и оба убывают.
Совпадение длин не формальность: ряды индексируются номером уровня,
и новый уровень в файле каталога обязан получить здесь своё число.
"""
rows = (world.SHOPPING_ADD_PERCENT, world.BROWSING_ADD_PERCENT)
for row in rows:
assert len(row) == len(catalog.DEMAND_LEVELS)
assert row[0] > row[1] > row[2], row
# Намерение сильнее товара: визит, пришедший покупать, кладёт чаще на
# любом уровне — иначе слово «намерение» ничего бы не значило.
assert all(shopping > browsing for shopping, browsing in zip(*rows, strict=True))
# Но и не всё подряд: товар решает у обоих, спрос не декорация.
assert min(world.SHOPPING_ADD_PERCENT) < 100
def test_the_buyer_mark_makes_the_cookie_live_longer():
"""Второй рычаг метки: без долгой жизни второй покупке негде случиться."""
assert world.BUYER_ONE_SHOT_PERCENT < world.ONE_SHOT_PERCENT