Планирование компенсаций и расчеты в сложных сбытовых организациях могут быть чрезвычайно сложными для традиционных баз данных. Достаточно крупные организации часто заканчивают пакетирование этих процессов в одночасье или еженедельной работой.
Для целей этого поста в блоге давайте взглянем на Slashco — самую горячую многоуровневую маркетинговую «гильдию» в World of Warcraft. Получите полные наборы данных и запросы Cypher, использованные в этой записи блога здесь .
В этой гильдии «партнеры» могут присоединиться и продать прекрасное приключенческое снаряжение Слашко многим народам Азерота, а также нанять своих друзей и семью для продажи продуктов Слашко. Когда сотрудник нанимает кого-то для вступления в гильдию, этот новый член становится частью «нисходящего потока» сотрудника. В качестве роялти за привлечение этого нового участника, партнеры будут получать небольшую комиссию за каждый предмет, проданный членами «нижестоящих» из них.
В этом графике компенсаций есть продавцы, которые будут продавать продукты напрямую, а также нанимать людей для продажи продуктов от имени Slashco. Я создал образец модели на основе популярной многоуровневой маркетинговой модели. Он состоит из четырех источников дохода:
- Прямые продажи: за каждый предмет, который продает член команды напрямую, он зарабатывает комиссию.
- Роялти-фри: процент, полученный от розничной цены товаров, проданных людьми «в пределах их нижнего течения», то есть тех, кого они завербовали для работы в Slashco, и, соответственно, тех, кого набрали их новобранцы.
- Оптовая прибыль: процент, заработанный на товарах, проданных тем, кто «в пределах своей низовья» (эти товары затем будут перепроданы потребителям.
- Мировая выручка от продаж: процент от всех продаж, сделанных теми, кто работает в Slashco (или распределение доходов).
Вот пример того, как типичная организация MLM может компенсировать своих партнеров:
Мы видим, что в зависимости от того, на каком уровне вы находитесь, то, как вы получаете компенсацию как за прямые продажи, так и за пассивные потоки дохода, может сильно различаться. Таким образом, расчет компенсации может быть по меньшей мере волосатым.
Если бы мы перенесли наши данные из прежнего планировщика компенсаций продаж, было бы лучше перенести все эти данные в ряд файлов .csv. В мире баз данных файлы значений, разделенных запятыми, часто являются наименьшим общим знаменателем — поэтому для 90% проектов, над которыми я работаю, я начинаю там.
Давайте представим, что мы поместили наши старые данные MySQL в серию из 3 CSV-файлов:
Это идеализированные дампы, в которых я синтезировал примерно три десятка CSV-файлов на три, просто чтобы показать пример того, как выглядят данные до и после процесса ETL в Neo4j ( LOAD CSV ).
При проверке в Excel (вы можете посмотреть их в любом другом текстовом редакторе), мы увидим серию заголовков и строк со значениями, которые соответствуют этим заголовкам. Например, на изображении ниже мы видим, что транзакция с идентификатором 1000000 имеет значение 30 под заголовком «salesRepID», 22 под заголовком «period», 7780 под заголовком «item1» и т. Д. и т. д. Нам нужно будет сказать Neo4j, как интерпретировать эту информацию в виде серии узлов и отношений вместо квадратных данных «строки и столбца».
Прежде чем мы действительно загрузим какие-либо данные в Neo4j, важно знать, какие вопросы будут важны для нашего бизнеса. В этом случае мы моделируем инструмент компенсации продаж. Наши главные запросы могут быть примерно такими:
- Комиссия за каждого представителя, по периодам, в соответствии с правилами компенсации
- Комиссия за каждого представителя, ежегодно в соответствии с правилами компенсации
- Лидер продаж
- Топ продаж по продажам
- Лучший торговый представитель по крупнейшей сделке
- Продажи по периодам
- Самые продаваемые товары
- Какие предметы чаще всего продаются вместе?
Когда мы знаем эти вопросы, мы можем построить модель, которая позволяет быстро и эффективно отвечать на эти вопросы.
Как правило, если часть данных важна для вашего прецедента «сделайте его физическим», что означает, что если вы собираетесь выполнять обход или фильтрацию на основе определенного бита информации, то сделайте его узлом или отношением.
Например: поскольку мы знаем, что «сделки» важны для нашей таблицы лидеров продаж, мы собираемся сделать идею сделки (транзакции) конкретным типом узла. Затем мы добавим информацию об этой транзакции, например, когда она произошла, кому она принадлежит и какие элементы содержались в сделке. Тем не менее, в качестве «последнего шага» мы будем выполнять некоторые базовые математические операции над ценой, которая специфична для этого элемента, поэтому мы будем сохранять это как свойство.
Модель данных приложения компенсации Slaschco
Скрипты, которые я использую для создания этого примера, находятся в этом git-репо .
Теперь, когда у нас есть модель данных, давайте запустим Neo4j и передадим наш скрипт импорта (см. Здесь ). По сути, мы создаем несколько ограничений и индексов, а затем рассказываем Neo4j, как интерпретировать наши CSV-файлы в приведенной выше модели.
Теперь, когда мы загрузили все наши данные, давайте откроем наш браузер и начнем отвечать на некоторые из наших самых популярных запросов.
Мы будем работать в обратном направлении
5. Рекомендация
- Какие предметы чаще всего продаются вместе?
//recommendation engine, what items are most frequently co-sold?
MATCH path = (item:Item)-[:CONTAINS]-(:Transaction)-[:CONTAINS]-(item2:Item)
WHERE id(item) > id(item2)
WITH item, item2, count(distinct path) as instances
ORDER BY instances DESC
LIMIT 3
RETURN item.name, item2.name, instances;
4. Глобальная отчетность
- Продажи по периодам
- Самые продаваемые товары
//total sales volume by period descending
MATCH (p:Period)-[:OCCURED_IN]-(t:Transaction)-[:CONTAINS]-(i:Item)
WITH sum(i.price) as sales, p
ORDER BY sales DESC
LIMIT 10
RETURN sales, p.period;
MATCH (t:Transaction)-[:CONTAINS]-(i:Item)
WITH count(distinct(t)) as itemSales, i
ORDER BY itemSales DESC
LIMIT 5
RETURN i.name as name, itemSales as count;
3. Лидер продаж
- Топ продаж по общему объему продаж
- Лучший торговый представитель по крупнейшей сделке
//Who has sold the most volume?
MATCH (rep)-[:SOLD]-(txn)-[:CONTAINS]-(itm)
WITH rep, round(sum(itm.price)) as volume
ORDER BY volume DESC
LIMIT 5
RETURN rep.name as name, volume;
//Who closed the largest deal?
MATCH (rep)-[:SOLD]-(txn)
WITH rep, txn
MATCH (txn)-[:CONTAINS]-(itm)
WITH rep, txn, round(sum(itm.price)) as dealSize
ORDER BY dealSize DESC
LIMIT 5
RETURN rep.name as name, txn.transactionID as transction, dealSize as `deal size`;
2. Комиссия за каждого представителя, ежегодно в соответствии с правилами компенсации
Из-за сложности запросов я решил запустить их с каждым уровнем повторений, выделенным в отдельный запрос, однако все они следуют базовой форме запроса «что мне делать со всем этим золотым»:
//level 6 comp
MATCH (transaction)-[:CONTAINS]-(item)
WITH sum(item.price*.05) as globalRoyalty
MATCH (big_boss:Person {level:6})<-[r:REPORTS_TO*..]-(downStreamers)-[:SOLD]-(transaction)-[:CONTAINS]-(item)
WITH sum(item.price*.1)+sum(item.wholesalePrice*.5) + globalRoyalty as downStreamGlobal6, big_boss
MATCH (boss)-[:SOLD]-(transaction)-[:CONTAINS]-(item)
WITH sum(item.price*.65) + downStreamGlobal6 as tc6, big_boss.name as n6
RETURN tc6, n6;
1. Комиссия, взимаемая с каждого представителя, по периоду в соответствии с правилами компенсации
Это выглядит пугающе похоже на наш последний запрос, за исключением того, что мы добавили короткий шаблон
(transaction)-[:OCCURRED_IN]-(period {period:35})
который отфильтрует все транзакции, которые произошли в периоды, которые не являются 35-ыми. Мы по-прежнему видим, что для набора данных разумного размера (100 сотрудников, 20 тыс. Позиций, 10 тыс. Транзакций) neo4j работает молниеносно.
//level 6 comp with time period
MATCH (transaction)-[:OCCURRED_IN]-(p:Period {period:35})
WITH transaction, p
MATCH (transaction)-[:CONTAINS]-(item)
WITH sum(item.price*.05) as globalRoyalty, p
MATCH (transaction)-[:OCCURRED_IN]-(p:Period {period:35})
WITH globalRoyalty, p, transaction
MATCH (big_boss:Person {level:6})<-[r:REPORTS_TO*..]-(downStreamers)-[:SOLD]-(transaction)-[:CONTAINS]-(item)
WITH sum(item.price*.1)+sum(item.wholesalePrice*.5) + globalRoyalty as downStreamGlobal6, big_boss, p
MATCH (transaction)-[:OCCURRED_IN]-(p:Period {period:35})
WITH transaction, downStreamGlobal6, big_boss
MATCH (boss)-[:SOLD]-(transaction)-[:CONTAINS]-(item)
WITH sum(item.price*.65) + downStreamGlobal6 as tc6, big_boss.name as n6
RETURN tc6, n6;
// КВГ
[ Как контент сообщества, этот пост отражает взгляды и мнения конкретного автора и не обязательно отражает официальную позицию Neo4j. Этот пост был первоначально написан Кевином Ван Ганди в блоге Кевина Ван Ганди . ]