
В салонах услуг — парикмахерских, груминге, маникюре — есть наблюдение, которое все считают очевидным. Новый мастер первые месяцы работает меньше остальных, и объясняют это одинаково: у него нет своей клиентской базы, а у коллег есть.
Объяснение выглядит самоочевидным ровно до того момента, когда его пробуешь воспроизвести. Я собрал простую модель, ожидал увидеть в ней этот разрыв — и не увидел вовсе. Разрыв появился только после того, как я поменял одно правило, которое к базе отношения не имеет.
Дальше — модель целиком, код, числа и вывод, который меня самого удивил.
Постановка
Салон, четыре мастера, у каждого три места в день. Ёмкость — двенадцать визитов.
Навык у всех одинаковый. Это принципиальное упрощение: я хочу понять, возникает ли разрыв без разницы в мастерстве.
Клиенты двух видов. Постоянный записан к конкретному мастеру и приходит примерно раз в сорок пять дней. Новый приходит в салон, а не к человеку, и его кто-то распределяет.
Единственная механика накопления: обслуженный клиент с вероятностью 0,35 становится постоянным у того, кто его обслужил.
Старт неравный, как в жизни: трое работают полгода и накопили около сорока постоянных, четвёртый вышел сегодня и не имеет никого.
Код
Код
import random, statisticsДНЕЙ = 360МАСТЕРОВ = 4МЕСТ = 3ЁМКОСТЬ = МАСТЕРОВ * МЕСТ # 12 визитов в деньP = 0.35 # шанс, что клиент станет постояннымЧАСТОТА = 45 # раз в сколько дней возвращается постоянныйРАЗБРОС = 7 # плюс-минус к дате возвратаdef прогон(rnd, старт, спрос_в_день, правило): очередь, база = {}, list(старт) # стартовая база должна существовать для модели: раскидываем её визиты # по ближайшему циклу, иначе она просто число в переменной for м, n in enumerate(старт): for _ in range(n): очередь.setdefault(rnd.randrange(ЧАСТОТА), []).append(м) визиты = [0] * МАСТЕРОВ for день in range(ДНЕЙ): свои = очередь.pop(день, []) rnd.shuffle(свои) занято, обслужены = [0] * МАСТЕРОВ, [] # 1. постоянные идут к своему мастеру, если у него осталось место for м in свои: if занято[м] < МЕСТ: занято[м] += 1 обслужены.append(м) # 2. сколько людей придёт сегодня всего спрос = max(0, int(rnd.gauss(спрос_в_день, спрос_в_день * 0.25))) новых = max(0, спрос - len(обслужены)) # 3. новых распределяет салон - и вот здесь вся статья for _ in range(новых): своб = [i for i in range(МАСТЕРОВ) if занято[i] < МЕСТ] if not своб: break if правило == "ровно": м = min(своб, key=lambda i: (занято[i], rnd.random())) else: # «дайте хорошего мастера» м = rnd.choices(своб, weights=[база[i] + 1 for i in своб], k=1)[0] занято[м] += 1 обслужены.append(м) for м in обслужены: визиты[м] += 1 if rnd.random() < P: база[м] += 1 назад = день + ЧАСТОТА + rnd.randint(-РАЗБРОС, РАЗБРОС) if назад < ДНЕЙ: очередь.setdefault(назад, []).append(м) return визитыdef сводка(старт, спрос, правило, прогонов=300): итог = [0.0] * МАСТЕРОВ for s in range(прогонов): for i, v in enumerate(прогон(random.Random(s), старт, спрос, правило)): итог[i] += v ср = [x / прогонов for x in итог] return ср, ср[3] / statistics.mean(ср[:3])СТАРТ = [40, 38, 42, 0]for доля in (1.0, 0.85, 0.7, 0.55): спрос = round(ЁМКОСТЬ * доля) for правило in ("ровно", "репутация"): ср, отношение = сводка(СТАРТ, спрос, правило) print(f"{доля:>5.0%} {правило:<10} {[round(x) for x in ср]} {отношение:.0%}")
Триста прогонов на каждую комбинацию, год работы в каждом.
Что показала модель
Два правила раздачи новых клиентов. Первое — отдаём тому, у кого сегодня свободнее. Второе — «дайте мне хорошего мастера»: шанс попасть к мастеру пропорционален размеру его базы.
|
Загрузка салона |
Раздаём по свободным местам |
«Дайте хорошего» |
|---|---|---|
|
100% (12 из 12) |
100% |
91% |
|
85% (10 из 12) |
99% |
75% |
|
70% (8 из 12) |
99% |
32% |
|
55% (7 из 12) |
98% |
11% |
В ячейках — сколько визитов за год сделал новичок относительно среднего по трём коллегам. При одинаковом навыке.
Первый вывод: сама по себе база разрыва не создаёт
Верхняя строка каждой пары — ровная. От 98 до 100 процентов при любой загрузке, от полной до провальной.
Это противоречит привычному объяснению, и механика понятна, если проговорить. Да, у коллег есть постоянные, и они занимают их места первыми. Но занятое место — это ещё и недоступное место: мастер с полной записью новых клиентов уже не берёт, и они достаются тому, у кого свободно.
То есть база работает в обе стороны. Она даёт гарантированный поток и она же закрывает доступ к новому потоку. При честной раздаче эти два эффекта компенсируют друг друга почти полностью.
Я собирал модель, чтобы измерить величину разрыва, а получил ноль. Это тот случай, когда результат стоит перепроверить, а не поправить: я трижды менял параметры — вероятность возврата, частоту визитов, размер стартовой базы, — и верхняя строка оставалась ровной.
Второй вывод: разрыв создаёт правило раздачи
Нижняя строка каждой пары — та же модель, изменено одно: новый клиент попадает к мастеру с вероятностью, пропорциональной размеру его базы.
Это не выдумка ради эффекта. Так работает любой из привычных механизмов: клиент просит «хорошего мастера», администратор рекомендует того, кого чаще хвалят, запись на сайте показывает мастеров в порядке рейтинга, а сарафанное радио несёт имя того, у кого клиентов больше.
Это классическое предпочтительное присоединение: вероятность получить нового клиента растёт с числом уже имеющихся. У такой системы устойчивое поведение — расслоение, и оно не требует разницы в качестве.
Третий вывод, неожиданный: глубину разрыва задаёт незаполненность
Вот это я не предполагал вовсе.
При полной загрузке правило раздачи почти не важно: 91%. Логика простая — когда салон забит, места у сильных мастеров кончаются, и клиент физически попадает к новичку, как бы его ни рекомендовали.
Дальше спрос падает, и эффект нарастает нелинейно. 85% загрузки — 75%. Уже 70% загрузки — и новичок делает треть работы коллеги. При 55% — одну девятую.
Механика такая. Пока салон полон, дефицитом является место мастера. Как только спрос опускается ниже ёмкости, дефицитом становится клиент, и правило раздачи начинает распределять именно дефицит. Незаполненные места собираются у одного человека — того, у кого меньше всего причин быть выбранным.
Отсюда практическое следствие, которое я бы не вывел из наблюдения: разрыв между мастерами — это не показатель их разного уровня, а индикатор незаполненности вместе с правилом раздачи. Один и тот же коллектив в сезон выглядит ровным, а в спад — расслоившимся, и меняется при этом не люди.
Чего модель не умеет
Перечислю честно, потому что без этого числа выше выглядят убедительнее, чем они есть.
Это не замер. У меня нет обезличенных данных по загрузке мастеров, и я их не использовал. Все числа порождены моделью, и их следует читать как «вот что даёт такая механика», а не «вот что происходит в салонах».
Навык одинаков по построению. В жизни он разный, и часть реального разрыва приходится на него. Модель отвечает только на вопрос, сколько набегает без него.
Возврат клиента — одна вероятность на всех. На деле она зависит от процедуры, от породы, от того, как мастер разговаривает. Более честная модель сделала бы её функцией, но тогда результат стал бы зависеть от формы этой функции, а её никто не знает.
Нет ухода клиентов. База в модели только растёт. Это завышает эффект накопления, то есть работает против моего же вывода — с оттоком верхняя строка была бы ещё ровнее.
Нет расписания и предварительной записи. Постоянный клиент в модели приходит в случайный день, а в жизни он записывается заранее и тем самым блокирует место. Это, наоборот, могло бы усилить эффект дефицита мест при полной загрузке.
Практические следствия для системы записи
Три следствия, которые касаются кода, а не людей.
Порядок мастеров в форме записи — это не оформление, а правило распределения. Сортировка по рейтингу реализует ровно то самое предпочтительное присоединение. Сортировка по свободным окнам — первую строку таблицы. Выбор между ними стоит делать осознанно, а не оставлять по умолчанию.
Показатель «загрузка мастера» без показателя «загрузка салона» не интерпретируется. Одно и то же расслоение означает разное при 100% и при 70% заполненности. Дашборд, который показывает первое без второго, регулярно подталкивает к неверному решению — уволить отстающего, хотя отстаёт правило.
У новичка должен быть механизм входа в поток, и он должен быть явным. Квота новых клиентов, приоритет в раздаче на первые месяцы, ротация — что угодно, лишь бы это было записано как правило, а не оставлено на усмотрение администратора. Иначе система будет расслаиваться сама, без чьего-либо злого умысла, и тем сильнее, чем хуже дела у салона в целом.
Итог
Я начинал с намерением измерить, насколько отсутствие клиентской базы тормозит новичка. Модель ответила, что само по себе — никак: база одновременно даёт поток и закрывает доступ к новому потоку, и эти эффекты гасят друг друга.
Разрыв появляется из правила раздачи новых клиентов и усиливается по мере того, как салон пустеет. При полной загрузке он почти незаметен, при 70% — трёхкратный.
Если у вас есть обезличенные данные по загрузке мастеров, мне очень интересно, воспроизводится ли эта зависимость от заполненности на реальных числах. Код выше запускается как есть и правится в одну строку.
ссылка на оригинал статьи https://habr.com/ru/articles/1087306/