customeranalytics 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- customeranalytics/__init__.py +319 -0
- customeranalytics/configs.py +480 -0
- customeranalytics/data_storage_configurations/__init__.py +245 -0
- customeranalytics/data_storage_configurations/data_access.py +165 -0
- customeranalytics/data_storage_configurations/data_works_pipeline.py +207 -0
- customeranalytics/data_storage_configurations/es_create_index.py +544 -0
- customeranalytics/data_storage_configurations/logger.py +36 -0
- customeranalytics/data_storage_configurations/query_es.py +157 -0
- customeranalytics/data_storage_configurations/reports.py +684 -0
- customeranalytics/data_storage_configurations/schedule_data_integration.py +355 -0
- customeranalytics/docs/queries.yaml +245 -0
- customeranalytics/docs/web_configs.yaml +2 -0
- customeranalytics/exploratory_analysis/__init__.py +109 -0
- customeranalytics/exploratory_analysis/churn.py +232 -0
- customeranalytics/exploratory_analysis/cohorts.py +535 -0
- customeranalytics/exploratory_analysis/descriptive_statistics.py +470 -0
- customeranalytics/exploratory_analysis/funnels.py +407 -0
- customeranalytics/exploratory_analysis/product_analytics.py +340 -0
- customeranalytics/exploratory_analysis/promotion_analytics.py +299 -0
- customeranalytics/exploratory_analysis/rfm.py +202 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_avg_order_count_per_promo_per_cust.csv +99 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_chart_1_search.csv +2 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_chart_2_search.csv +46 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_chart_3_search.csv +2 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_chart_4_search.csv +2 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_churn.csv +2 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_churn_weekly.csv +10 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_client_feature_predicted.csv +1590 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_client_kpis.csv +7060 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_clvrfm_anomaly.csv +561 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_clvsegments_amount.csv +8 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_customer_journey.csv +18 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_clv.csv +103 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_cohort_downloads.csv +68 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_cohort_from_1_to_2.csv +66 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_cohort_from_2_to_3.csv +64 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_cohort_from_3_to_4.csv +55 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_dimension_values.csv +199 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_funnel.csv +36 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_funnel_downloads.csv +68 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_inorganic_ratio.csv +6070 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_orders.csv +57 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_organic_orders.csv +64 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_products_of_sales.csv +12574 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_promotion_discount.csv +64 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_daily_promotion_revenue.csv +6070 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_dcohort_anomaly.csv +6 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_dcohort_anomaly_2.csv +118 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_deliver.csv +47 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_deliver_kpis.csv +2 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_deliver_weekday_hour.csv +25 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_dfunnel_anomaly.csv +118 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_dimension_kpis.csv +199 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_dorders_anomaly.csv +85 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_frequency_clusters.csv +6 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_frequency_recency.csv +3740 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_funnel.csv +25 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_funnel_downloads.csv +25 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_inorganic_ratio.csv +2353 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_orders.csv +25 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_hourly_organic_orders.csv +2353 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_inorganic_orders_per_promotion_per_day.csv +6070 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_kpis.csv +2 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_monetary_clusters.csv +6 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_monetary_frequency.csv +3740 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_monthly_funnel.csv +5 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_monthly_funnel_downloads.csv +5 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_monthly_orders.csv +4 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_most_combined_products.csv +11 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_most_ordered_categories.csv +11 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_most_ordered_products.csv +11 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_order_and_payment_amount_differences.csv +91 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_prepare.csv +101 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_prepare_weekday_hour.csv +25 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_product_kpis.csv +201 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_product_usage_before_after_amount_accept.csv +11 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_product_usage_before_after_amount_reject.csv +7 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_product_usage_before_after_orders_accept.csv +11 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_product_usage_before_after_orders_reject.csv +11 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_comparison.csv +91 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_kpis.csv +99 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_number_of_customer.csv +99 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_usage_before_after_amount_accept.csv +81 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_usage_before_after_amount_reject.csv +11 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_usage_before_after_orders_accept.csv +55 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_promotion_usage_before_after_orders_reject.csv +37 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_purchase_amount_distribution.csv +22 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_recency_clusters.csv +6 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_recency_monetary.csv +3740 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_rfm.csv +2001 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_ride.csv +101 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_ride_weekday_hour.csv +25 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_segmentation.csv +11 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_daily_before_after_amount.csv +10 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_daily_before_after_orders.csv +9 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_monthly_before_after_amount.csv +10 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_monthly_before_after_orders.csv +11 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_weekly_before_after_amount.csv +10 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_segments_change_weekly_before_after_orders.csv +10 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_user_counts_per_order_seq.csv +47 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_average_order_per_user.csv +13 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_average_payment_amount.csv +13 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_average_session_per_user.csv +13 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_cohort_downloads.csv +13 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_cohort_from_1_to_2.csv +13 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_cohort_from_2_to_3.csv +13 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_cohort_from_3_to_4.csv +12 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_funnel.csv +25 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_funnel_downloads.csv +25 -0
- customeranalytics/exploratory_analysis/sample_data/sample_data_weekly_orders.csv +10 -0
- customeranalytics/ml_process/__init__.py +64 -0
- customeranalytics/ml_process/ab_test.py +615 -0
- customeranalytics/ml_process/anomaly_detection.py +456 -0
- customeranalytics/ml_process/clv_prediction.py +301 -0
- customeranalytics/ml_process/customer_segmentation.py +354 -0
- customeranalytics/ml_process/delivery_analytics.py +754 -0
- customeranalytics/utils.py +186 -0
- customeranalytics/web/app/__init__.py +37 -0
- customeranalytics/web/app/base/__init__.py +9 -0
- customeranalytics/web/app/base/forms.py +16 -0
- customeranalytics/web/app/base/models.py +43 -0
- customeranalytics/web/app/base/routes.py +126 -0
- customeranalytics/web/app/base/static/assets/css/app.css +20 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Black.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Black.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-BlackItalic.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-BlackItalic.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Bold.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Bold.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-BoldItalic.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-BoldItalic.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraBold.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraBold.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraBoldItalic.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraBoldItalic.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraLight-BETA.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraLight-BETA.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraLightItalic-BETA.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ExtraLightItalic-BETA.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Italic.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Italic.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Light-BETA.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Light-BETA.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-LightItalic-BETA.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-LightItalic-BETA.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Medium.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Medium.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-MediumItalic.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-MediumItalic.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Regular.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Regular.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-SemiBold.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-SemiBold.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-SemiBoldItalic.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-SemiBoldItalic.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Thin-BETA.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-Thin-BETA.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ThinItalic-BETA.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-ThinItalic-BETA.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-italic.var.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter-upright.var.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/Inter.var.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.eot +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.svg +3570 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.ttf +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-brands-400.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.eot +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.svg +801 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.ttf +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-regular-400.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.eot +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.svg +5028 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.ttf +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.woff +0 -0
- customeranalytics/web/app/base/static/assets/fonts/fa-solid-900.woff2 +0 -0
- customeranalytics/web/app/base/static/assets/img/avatars/avatar-1.png +0 -0
- customeranalytics/web/app/base/static/assets/img/avatars/avatar-2.png +0 -0
- customeranalytics/web/app/base/static/assets/img/avatars/avatar-3.png +0 -0
- customeranalytics/web/app/base/static/assets/img/avatars/avatar-4.png +0 -0
- customeranalytics/web/app/base/static/assets/img/avatars/avatar-5.png +0 -0
- customeranalytics/web/app/base/static/assets/img/avatars/info.jpeg +0 -0
- customeranalytics/web/app/base/static/assets/img/icons/customanality.png +0 -0
- customeranalytics/web/app/base/static/assets/img/icons/icon-48x48.png +0 -0
- customeranalytics/web/app/base/static/assets/js/app.js +2 -0
- customeranalytics/web/app/base/static/assets/js/app.js.LICENSE +566 -0
- customeranalytics/web/app/base/static/assets/js/app.js.LICENSE.txt +556 -0
- customeranalytics/web/app/base/static/assets/js/modules/bootstrap.js +15 -0
- customeranalytics/web/app/base/static/assets/js/modules/chartjs.js +4 -0
- customeranalytics/web/app/base/static/assets/js/modules/feather.js +8 -0
- customeranalytics/web/app/base/static/assets/js/modules/flatpickr.js +4 -0
- customeranalytics/web/app/base/static/assets/js/modules/moment.js +4 -0
- customeranalytics/web/app/base/static/assets/js/modules/sidebar.js +23 -0
- customeranalytics/web/app/base/static/assets/js/modules/theme.js +16 -0
- customeranalytics/web/app/base/templates/accounts/login.html +81 -0
- customeranalytics/web/app/base/templates/accounts/register.html +80 -0
- customeranalytics/web/app/base/templates/includes/footer.html +28 -0
- customeranalytics/web/app/base/templates/includes/navigation.html +51 -0
- customeranalytics/web/app/base/templates/includes/scripts.html +3 -0
- customeranalytics/web/app/base/templates/includes/sidebar.html +146 -0
- customeranalytics/web/app/base/templates/layouts/base-fullscreen.html +36 -0
- customeranalytics/web/app/base/templates/layouts/base.html +84 -0
- customeranalytics/web/app/base/util.py +30 -0
- customeranalytics/web/app/home/__init__.py +9 -0
- customeranalytics/web/app/home/forms.py +1039 -0
- customeranalytics/web/app/home/models.py +438 -0
- customeranalytics/web/app/home/profiles.py +310 -0
- customeranalytics/web/app/home/routes.py +386 -0
- customeranalytics/web/app/home/search.py +288 -0
- customeranalytics/web/app/home/static/assets/img/avatars/avatar-1.png +0 -0
- customeranalytics/web/app/home/static/assets/img/avatars/avatar-2.png +0 -0
- customeranalytics/web/app/home/static/assets/img/avatars/avatar-3.png +0 -0
- customeranalytics/web/app/home/static/assets/img/avatars/avatar-4.png +0 -0
- customeranalytics/web/app/home/static/assets/img/avatars/avatar-5.png +0 -0
- customeranalytics/web/app/home/static/assets/img/icons/customanality.png +0 -0
- customeranalytics/web/app/home/static/assets/img/icons/icon-48x48.png +0 -0
- customeranalytics/web/app/home/templates/abtest-product.html +280 -0
- customeranalytics/web/app/home/templates/abtest-promotion.html +362 -0
- customeranalytics/web/app/home/templates/abtest-segments.html +298 -0
- customeranalytics/web/app/home/templates/add-data-delivery.html +184 -0
- customeranalytics/web/app/home/templates/add-data-product.html +177 -0
- customeranalytics/web/app/home/templates/add-data-purchase.html +392 -0
- customeranalytics/web/app/home/templates/anomaly.html +261 -0
- customeranalytics/web/app/home/templates/clv.html +211 -0
- customeranalytics/web/app/home/templates/cohorts.html +308 -0
- customeranalytics/web/app/home/templates/customer-segmentation.html +308 -0
- customeranalytics/web/app/home/templates/data-es.html +152 -0
- customeranalytics/web/app/home/templates/data-execute.html +397 -0
- customeranalytics/web/app/home/templates/delivery.html +404 -0
- customeranalytics/web/app/home/templates/funnel-customer.html +240 -0
- customeranalytics/web/app/home/templates/funnel-session.html +236 -0
- customeranalytics/web/app/home/templates/index.html +524 -0
- customeranalytics/web/app/home/templates/index2.html +515 -0
- customeranalytics/web/app/home/templates/page-403.html +46 -0
- customeranalytics/web/app/home/templates/page-404.html +45 -0
- customeranalytics/web/app/home/templates/page-500.html +45 -0
- customeranalytics/web/app/home/templates/pages-blank.html +33 -0
- customeranalytics/web/app/home/templates/pages-sign-in.html +67 -0
- customeranalytics/web/app/home/templates/pages-sign-up.html +61 -0
- customeranalytics/web/app/home/templates/product.html +203 -0
- customeranalytics/web/app/home/templates/profile.html +1009 -0
- customeranalytics/web/app/home/templates/rfm.html +254 -0
- customeranalytics/web/app/home/templates/search.html +167 -0
- customeranalytics/web/app/home/templates/settings.html +205 -0
- customeranalytics/web/app/home/templates/stats-desc.html +230 -0
- customeranalytics/web/app/home/templates/stats-purchase.html +221 -0
- customeranalytics/web/config.py +30 -0
- customeranalytics/web/db.sqlite3 +0 -0
- customeranalytics/web/run.py +30 -0
- customeranalytics-0.1.dist-info/METADATA +1102 -0
- customeranalytics-0.1.dist-info/RECORD +254 -0
- customeranalytics-0.1.dist-info/WHEEL +4 -0
- customeranalytics-0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime
|
|
4
|
+
import random
|
|
5
|
+
from time import gmtime, strftime
|
|
6
|
+
import pytz
|
|
7
|
+
from elasticsearch import Elasticsearch
|
|
8
|
+
from elasticsearch import helpers
|
|
9
|
+
import argparse
|
|
10
|
+
from itertools import product
|
|
11
|
+
|
|
12
|
+
import sys, os, inspect
|
|
13
|
+
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
|
14
|
+
parentdir = os.path.dirname(currentdir)
|
|
15
|
+
sys.path.insert(0, parentdir)
|
|
16
|
+
|
|
17
|
+
from customeranalytics.configs import default_es_port, default_es_host
|
|
18
|
+
from customeranalytics.utils import current_date_to_day, get_index_group, convert_str_to_hour, convert_to_date, convert_dt_to_day_str
|
|
19
|
+
from customeranalytics.utils import dimension_decision, convert_to_iso_format
|
|
20
|
+
from customeranalytics.data_storage_configurations.query_es import QueryES
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ProductAnalytics:
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
def __init__(self,
|
|
28
|
+
has_product_connection=True,
|
|
29
|
+
host=None,
|
|
30
|
+
port=None,
|
|
31
|
+
download_index='downloads',
|
|
32
|
+
order_index='orders'):
|
|
33
|
+
"""
|
|
34
|
+
!!!!
|
|
35
|
+
******* ******** *****
|
|
36
|
+
Dimensional Product Analytic:
|
|
37
|
+
Product Analytic must be created individually for dimensions. For instance, the Data set contains locations dimension.
|
|
38
|
+
In this case, each location of 'orders' and 'downloads' indexes must be created individually.
|
|
39
|
+
by using 'download_index' and 'order_index' dimension can be assigned in order to create a Product Analyse
|
|
40
|
+
|
|
41
|
+
download_index; downloads_location1 this will be the location dimension of
|
|
42
|
+
parameters in order to query downloads indexes; 'location1'.
|
|
43
|
+
download_index; orders_location1 this will be the location dimension of
|
|
44
|
+
parameters in order to query orders indexes; 'location1'.
|
|
45
|
+
******* ******** *****
|
|
46
|
+
!!!
|
|
47
|
+
|
|
48
|
+
:param has_product_connection: has_product_connection if there is no products data then, no product analytics
|
|
49
|
+
:param host: elasticsearch host
|
|
50
|
+
:param port: elasticsearch port
|
|
51
|
+
:param download_index: elasticsearch port
|
|
52
|
+
:param order_index: elasticsearch port
|
|
53
|
+
"""
|
|
54
|
+
self.port = default_es_port if port is None else port
|
|
55
|
+
self.host = default_es_host if host is None else host
|
|
56
|
+
self.has_product_connection = has_product_connection
|
|
57
|
+
self.download_index = download_index
|
|
58
|
+
self.order_index = order_index
|
|
59
|
+
self.query_es = QueryES(port=port, host=host)
|
|
60
|
+
self.fields_products = ["id", "client", "basket", "session_start_date", "payment_amount", "discount_amount"]
|
|
61
|
+
self.products = pd.DataFrame()
|
|
62
|
+
self.hourly_products = pd.DataFrame()
|
|
63
|
+
self.hourly_product_cat = pd.DataFrame()
|
|
64
|
+
self.product_pairs = pd.DataFrame()
|
|
65
|
+
self.daily_products = pd.DataFrame()
|
|
66
|
+
self.product_kpis = pd.DataFrame()
|
|
67
|
+
self.analysis = ["most_ordered_products", "most_ordered_categories",
|
|
68
|
+
"hourly_products_of_sales", "hourly_categories_of_sales",
|
|
69
|
+
"most_combined_products", 'daily_products_of_sales', 'product_kpis']
|
|
70
|
+
|
|
71
|
+
def dimensional_query(self, boolean_query=None):
|
|
72
|
+
if dimension_decision(self.order_index):
|
|
73
|
+
if boolean_query is None:
|
|
74
|
+
boolean_query = [{"term": {"dimension": self.order_index}}]
|
|
75
|
+
else:
|
|
76
|
+
boolean_query += [{"term": {"dimension": self.order_index}}]
|
|
77
|
+
return boolean_query
|
|
78
|
+
|
|
79
|
+
def get_products(self, end_date):
|
|
80
|
+
"""
|
|
81
|
+
1. Fetch data from orders with filter; actions.purchased: True
|
|
82
|
+
2. Example of basket dictionary on order document;
|
|
83
|
+
'basket': {'p_10': {'price': 6.12, 'category': 'p_c_8', 'rank': 109},
|
|
84
|
+
'p_145': {'price': 12.0, 'category': 'p_c_9', 'rank': 175},
|
|
85
|
+
'p_168': {'price': 13.12, 'category': 'p_c_10', 'rank': 82},
|
|
86
|
+
'p_9': {'price': 0.52, 'category': 'p_c_3', 'rank': 9},
|
|
87
|
+
'p_4': {'price': 3.72, 'category': 'p_c_8', 'rank': 69},
|
|
88
|
+
'p_104': {'price': 8.88, 'category': 'p_c_10', 'rank': 97},
|
|
89
|
+
'p_74': {'price': 8.395, 'category': 'p_c_10', 'rank': 35}
|
|
90
|
+
}
|
|
91
|
+
a. Keys of dictionary are products.
|
|
92
|
+
:param end_date: last date of data set
|
|
93
|
+
"""
|
|
94
|
+
self.query_es = QueryES(port=self.port, host=self.host)
|
|
95
|
+
self.query_es.date_queries_builder({"session_start_date": {"lt": end_date}})
|
|
96
|
+
self.query_es.query_builder(fields=None, _source=True,
|
|
97
|
+
boolean_queries=self.dimensional_query([{"term": {"actions.has_basket": True}}]))
|
|
98
|
+
self.products = self.query_es.get_data_from_es()
|
|
99
|
+
self.products = pd.DataFrame([{col: r['_source'][col] for col in self.fields_products} for r in self.products])
|
|
100
|
+
self.products = self.products.query('basket == basket')
|
|
101
|
+
self.products['products'] = self.products['basket'].apply(
|
|
102
|
+
lambda x: list(x.keys()) if x == x else None) # get product_ids
|
|
103
|
+
self.products = self.products.query('products == products')
|
|
104
|
+
# get prices
|
|
105
|
+
self.products['price'] = self.products.apply(
|
|
106
|
+
lambda row: [row['basket'][i]['price'] for i in row['products']], axis=1)
|
|
107
|
+
self.products['category'] = self.products.apply(
|
|
108
|
+
lambda row: [row['basket'][i]['category'] for i in row['products']], axis=1)
|
|
109
|
+
self.products['products'] = self.products.apply(
|
|
110
|
+
lambda row: list(zip([row['id']] * len(row['products']),
|
|
111
|
+
[row['client']] * len(row['products']),
|
|
112
|
+
[row['payment_amount']] * len(row['products']),
|
|
113
|
+
[row['discount_amount']] * len(row['products']),
|
|
114
|
+
[row['session_start_date']] * len(row['products']),
|
|
115
|
+
row['products'],
|
|
116
|
+
row['category'],
|
|
117
|
+
row['price'])), axis=1)
|
|
118
|
+
# concatenate products columns and convert it to dataframe with columns produc_id and price
|
|
119
|
+
self.products = pd.DataFrame(np.concatenate(list(self.products['products']))).rename(
|
|
120
|
+
columns={0: "id", 1: "client", 2: "payment_amount", 3: "discount_amount",
|
|
121
|
+
4: 'session_start_date', 5: "products", 6: "category", 7: "price"})
|
|
122
|
+
# subtract price from payment amount.
|
|
123
|
+
# This will works to see how product of addition affects the total basket of payment amount
|
|
124
|
+
self.products['payment_amount'] = self.products['payment_amount'].apply(lambda x: float(x))
|
|
125
|
+
self.products['price'] = self.products['price'].apply(lambda x: float(x))
|
|
126
|
+
self.products['session_start_date'] = self.products['session_start_date'].apply(lambda x: convert_to_date(x))
|
|
127
|
+
|
|
128
|
+
def get_customer_segments(self, date):
|
|
129
|
+
"""
|
|
130
|
+
Collecting segments of customers from the reports index.
|
|
131
|
+
:param date: given report date
|
|
132
|
+
"""
|
|
133
|
+
date = current_date_to_day().isoformat() if date is None else date
|
|
134
|
+
self.products = pd.merge(self.products,
|
|
135
|
+
self.cs.fetch(start_date=convert_dt_to_day_str(date))[['client', 'segments']],
|
|
136
|
+
on='client', how='left')
|
|
137
|
+
|
|
138
|
+
def get_most_ordered_products(self):
|
|
139
|
+
"""
|
|
140
|
+
the products of orders are counted and sorted according to the order counts.
|
|
141
|
+
So, the most ordered products are detected.
|
|
142
|
+
"""
|
|
143
|
+
return self.products.groupby("products").agg(
|
|
144
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(
|
|
145
|
+
columns={"id": "order_count"}).sort_values(by='order_count', ascending=False)
|
|
146
|
+
|
|
147
|
+
def get_most_ordered_categories(self):
|
|
148
|
+
"""
|
|
149
|
+
the product categories of orders are counted and sorted according to the order counts.
|
|
150
|
+
So, the most ordered products are detected.
|
|
151
|
+
"""
|
|
152
|
+
return self.products.groupby("category").agg({"id": lambda x: len(np.unique(x))}).reset_index().rename(
|
|
153
|
+
columns={"id": "order_count"}).sort_values(by='order_count', ascending=False)
|
|
154
|
+
|
|
155
|
+
def get_hourly_products_of_sales(self):
|
|
156
|
+
"""
|
|
157
|
+
Which product is preferred to purchase via purchased hour?
|
|
158
|
+
"""
|
|
159
|
+
self.products['hours'] = self.products['session_start_date'].apply(lambda x: convert_str_to_hour(x))
|
|
160
|
+
self.hourly_products = self.products.groupby(["hours", "products"]).agg(
|
|
161
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(columns={"id": "order_count"})
|
|
162
|
+
self.hourly_products['product_order_count_rank'] = self.hourly_products.sort_values(
|
|
163
|
+
by=["hours", "order_count"], ascending=False).groupby("hours").cumcount() + 1
|
|
164
|
+
self.hourly_products = pd.merge(self.hourly_products,
|
|
165
|
+
self.products.groupby("hours").agg(
|
|
166
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(
|
|
167
|
+
columns={"id": "hourly_total_orders"}), on='hours', how='left')
|
|
168
|
+
self.hourly_products['hourly_order_ratio'] = self.hourly_products['order_count'] / self.hourly_products[
|
|
169
|
+
'hourly_total_orders']
|
|
170
|
+
return self.hourly_products
|
|
171
|
+
|
|
172
|
+
def get_hourly_categories_of_sales(self):
|
|
173
|
+
"""
|
|
174
|
+
Which category is preferred to purchase via purchased hour?
|
|
175
|
+
"""
|
|
176
|
+
self.hourly_product_cat = self.products.groupby(["hours", "category"]).agg(
|
|
177
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(columns={"id": "order_count"})
|
|
178
|
+
self.hourly_product_cat['product_cat_order_count_rank'] = self.hourly_product_cat.sort_values(
|
|
179
|
+
by=["hours", "order_count"], ascending=False).groupby("hours").cumcount() + 1
|
|
180
|
+
self.hourly_product_cat = pd.merge(self.hourly_product_cat,
|
|
181
|
+
self.products.groupby("hours").agg(
|
|
182
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(
|
|
183
|
+
columns={"id": "hourly_total_orders"}),
|
|
184
|
+
on='hours', how='left')
|
|
185
|
+
self.hourly_product_cat['hourly_order_ratio'] = self.hourly_product_cat['order_count'] / \
|
|
186
|
+
self.hourly_product_cat['hourly_total_orders']
|
|
187
|
+
return self.hourly_product_cat
|
|
188
|
+
|
|
189
|
+
def get_most_combined_products(self):
|
|
190
|
+
"""
|
|
191
|
+
The number of product in the basket shows us the the combination of products that is chosen by client.
|
|
192
|
+
In this analysis, pair of products are counted according to
|
|
193
|
+
purchased order or products that are selected by user.
|
|
194
|
+
Ex: basket 1; prod_1, prod_2 || basket 2; prod_1, prod_2, prod_3 || basket 3; prod_1, prod_2, prod_3
|
|
195
|
+
|
|
196
|
+
pairs || number_of_pairs
|
|
197
|
+
-----------------------------------
|
|
198
|
+
prod_1 - prod_2 || 3
|
|
199
|
+
prod_2 - prod_3 || 2
|
|
200
|
+
prod_3 - prod_1 || 2
|
|
201
|
+
|
|
202
|
+
"""
|
|
203
|
+
self.product_pairs = self.products.groupby("id").agg({"products": lambda x: list(x)}).reset_index()
|
|
204
|
+
self.product_pairs['product_pairs'] = self.product_pairs['products'].apply(
|
|
205
|
+
lambda x: np.array(list(product(x, x))).tolist())
|
|
206
|
+
self.product_pairs = pd.DataFrame(np.concatenate(list(self.product_pairs['product_pairs'])).tolist())
|
|
207
|
+
self.product_pairs['total_pairs'] = 1
|
|
208
|
+
self.product_pairs = self.product_pairs[self.product_pairs[0] != self.product_pairs[1]]
|
|
209
|
+
self.product_pairs = self.product_pairs.groupby([0, 1]).agg({"total_pairs": "sum"}).reset_index()
|
|
210
|
+
self.product_pairs = self.product_pairs.sort_values('total_pairs', ascending=False)
|
|
211
|
+
self.product_pairs = self.product_pairs.rename(columns={0: "pair_1", 1: "pair_2"})
|
|
212
|
+
self.product_pairs['product_pair'] = self.product_pairs.apply(
|
|
213
|
+
lambda row: " - ".join(list(sorted([row['pair_1'], row['pair_2']]))), axis=1)
|
|
214
|
+
self.product_pairs = self.product_pairs.groupby("product_pair").agg({"total_pairs": "first"}).reset_index()
|
|
215
|
+
return self.product_pairs
|
|
216
|
+
|
|
217
|
+
def get_daily_product_sales(self):
|
|
218
|
+
"""
|
|
219
|
+
Which product is preferred to purchase via purchased day?
|
|
220
|
+
"""
|
|
221
|
+
self.products['daily'] = self.products['session_start_date'].apply(lambda x: convert_dt_to_day_str(x))
|
|
222
|
+
self.products = self.products.query("payment_amount == payment_amount")
|
|
223
|
+
self.products['payment_amount'] = self.products['payment_amount'].apply(lambda x: float(x))
|
|
224
|
+
self.daily_products = self.products.reset_index().groupby(["daily", "products"]).agg(
|
|
225
|
+
{"payment_amount": "sum", 'index': 'count'}).reset_index().rename(columns={"index": "order_count"})
|
|
226
|
+
return self.daily_products
|
|
227
|
+
|
|
228
|
+
def get_product_kpis(self):
|
|
229
|
+
"""
|
|
230
|
+
These KPIs is for the searching bar, when searching text is detected as product. There are 4 KPIS
|
|
231
|
+
which are shown at the the left top of the dashboard.
|
|
232
|
+
"""
|
|
233
|
+
kpi_1 = self.products[['products', 'client']].reset_index().groupby(["client", "products"]).agg(
|
|
234
|
+
{"index": "count"}).reset_index().groupby("products").agg(
|
|
235
|
+
{"index": "mean"}).reset_index().rename(columns={"index": "average_product_sold_per_user_kpi"})
|
|
236
|
+
kpi_2 = self.products[['products', 'payment_amount']].groupby("products").agg(
|
|
237
|
+
{"payment_amount": "sum"}).reset_index().rename(columns={"payment_amount": "total_product_revenue_kpi"})
|
|
238
|
+
try:
|
|
239
|
+
kpi_3 = self.products[['products', 'discount_amount']].query("discount_amount == discount_amount")
|
|
240
|
+
kpi_3['discount_amount'] = kpi_3['discount_amount'].apply(lambda x: float(x))
|
|
241
|
+
kpi_3 = kpi_3.groupby("products").agg({"discount_amount": "sum"}).reset_index().rename(
|
|
242
|
+
columns={"discount_amount": "total_product_discount_kpi"})
|
|
243
|
+
except:
|
|
244
|
+
kpi_3 = self.products[['products']]
|
|
245
|
+
kpi_3['total_product_discount_kpi'] = 0
|
|
246
|
+
|
|
247
|
+
kpi_4 = self.products[['products', 'client']].groupby("products").agg(
|
|
248
|
+
{"client": lambda x: len(np.unique(x))}).reset_index().rename(columns={"client": "total_product_cust_kpi"})
|
|
249
|
+
|
|
250
|
+
self.product_kpis = pd.DataFrame(self.products.groupby('products').count().reset_index()[['products']]).merge(
|
|
251
|
+
kpi_1, on='products', how='left').merge(
|
|
252
|
+
kpi_2, on='products', how='left').merge(
|
|
253
|
+
kpi_3, on='products', how='left').merge(kpi_4, on='products', how='left').fillna(0)
|
|
254
|
+
|
|
255
|
+
return self.product_kpis
|
|
256
|
+
|
|
257
|
+
def execute_product_analysis(self, end_date=None):
|
|
258
|
+
"""
|
|
259
|
+
1. Check for products data available on elasticsearch orders index.
|
|
260
|
+
2. Get products data and convert basket data into the data-frame format.
|
|
261
|
+
3. Execute each analysis separately.
|
|
262
|
+
4. Insert into 'reports' index.
|
|
263
|
+
"""
|
|
264
|
+
if self.has_product_connection:
|
|
265
|
+
self.get_products(end_date=convert_to_iso_format(current_date_to_day() if end_date is None else end_date))
|
|
266
|
+
for ins in list(zip(self.analysis, [self.get_most_ordered_products, self.get_most_ordered_categories,
|
|
267
|
+
self.get_hourly_products_of_sales, self.get_hourly_categories_of_sales,
|
|
268
|
+
self.get_most_combined_products, self.get_daily_product_sales,
|
|
269
|
+
self.get_product_kpis])):
|
|
270
|
+
_result = ins[1]()
|
|
271
|
+
self.insert_into_reports_index(product_analytic=_result,
|
|
272
|
+
pa_type=ins[0], start_date=end_date, index=self.order_index)
|
|
273
|
+
del _result
|
|
274
|
+
|
|
275
|
+
def insert_into_reports_index(self, product_analytic, pa_type, start_date=None, index='orders'):
|
|
276
|
+
"""
|
|
277
|
+
via query_es.py, each report can be inserted into the reports index with the given format.
|
|
278
|
+
{"id": unique report id,
|
|
279
|
+
"report_date": start_date or current date,
|
|
280
|
+
"report_name": "product_analytic",
|
|
281
|
+
"index": "main",
|
|
282
|
+
"report_types": {"type": "most_ordered_products", "most_ordered_categories", "hourly_products_of_sales",
|
|
283
|
+
"hourly_categories_of_sales", "hourly_categories_of_sales", "most_combined_products"
|
|
284
|
+
},
|
|
285
|
+
"data": product_analytic.to_dict("records") - dataframe to list of dictionary
|
|
286
|
+
}
|
|
287
|
+
:param product_analytic: data set, data frame
|
|
288
|
+
:param start_date: data start date
|
|
289
|
+
:param pa_type: product analytic type
|
|
290
|
+
:param index: dimentionality of data index orders_location1 ; dimension = location1
|
|
291
|
+
"""
|
|
292
|
+
list_of_obj = [{"id": np.random.randint(200000000),
|
|
293
|
+
"report_date": current_date_to_day().isoformat() if start_date is None else start_date,
|
|
294
|
+
"report_name": "product_analytic",
|
|
295
|
+
"index": get_index_group(index),
|
|
296
|
+
"report_types": {"type": pa_type},
|
|
297
|
+
"data": product_analytic.fillna(0).to_dict("records")}]
|
|
298
|
+
self.query_es.insert_data_to_index(list_of_obj, index='reports')
|
|
299
|
+
|
|
300
|
+
def fetch(self, product_analytic_name, start_date=None):
|
|
301
|
+
"""
|
|
302
|
+
This allows us to query the created Product Analytics.
|
|
303
|
+
product_analytic_name is crucial for us to collect the correct filters.
|
|
304
|
+
Example of queries;
|
|
305
|
+
- product_analytic_name: most_ordered_products,
|
|
306
|
+
- start_date: 2021-01-01T00:00:00
|
|
307
|
+
|
|
308
|
+
{'size': 10000000,
|
|
309
|
+
'from': 0,
|
|
310
|
+
'_source': True,
|
|
311
|
+
'query': {'bool': {'must': [
|
|
312
|
+
{'term': {'report_name': 'p'}},
|
|
313
|
+
{"term": {"index": "orders_location1"}}
|
|
314
|
+
{'term': {'report_types.type': 'most_ordered_products'}},
|
|
315
|
+
{'range': {'report_date': {'lt': '2021-04-01T00:00:00'}}}]}}}
|
|
316
|
+
|
|
317
|
+
- start date will be filtered from data frame. In this example; .query("daily > @start_date")
|
|
318
|
+
|
|
319
|
+
:param product_analytic_name: most_ordered_products.
|
|
320
|
+
:param start_date: product analytic report created date
|
|
321
|
+
:param index: index_name in order to get dimension_of data. If there is no dimension, no need to be assigned
|
|
322
|
+
:return: data frame
|
|
323
|
+
"""
|
|
324
|
+
boolean_queries, date_queries = [], []
|
|
325
|
+
boolean_queries = [{"term": {"report_types.type": product_analytic_name}},
|
|
326
|
+
{"term": {"index": get_index_group(self.order_index)}}]
|
|
327
|
+
|
|
328
|
+
if start_date is not None:
|
|
329
|
+
date_queries = [{"range": {"report_date": {"lt": convert_to_iso_format(start_date)}}}]
|
|
330
|
+
|
|
331
|
+
self.query_es = QueryES(port=self.port,
|
|
332
|
+
host=self.host)
|
|
333
|
+
self.query_es.query_builder(fields=None, _source=True,
|
|
334
|
+
date_queries=date_queries,
|
|
335
|
+
boolean_queries=boolean_queries)
|
|
336
|
+
_res = self.query_es.get_data_from_es(index="reports")
|
|
337
|
+
_data = pd.DataFrame()
|
|
338
|
+
if len(_res) != 0:
|
|
339
|
+
_data = pd.DataFrame(_res[0]['_source']['data'])
|
|
340
|
+
return _data
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime
|
|
4
|
+
import random
|
|
5
|
+
from time import gmtime, strftime
|
|
6
|
+
import pytz
|
|
7
|
+
from elasticsearch import Elasticsearch
|
|
8
|
+
from elasticsearch import helpers
|
|
9
|
+
import argparse
|
|
10
|
+
from itertools import product
|
|
11
|
+
|
|
12
|
+
import sys, os, inspect
|
|
13
|
+
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
|
14
|
+
parentdir = os.path.dirname(currentdir)
|
|
15
|
+
sys.path.insert(0, parentdir)
|
|
16
|
+
|
|
17
|
+
from customeranalytics.configs import default_es_port, default_es_host, none_types
|
|
18
|
+
from customeranalytics.utils import current_date_to_day, get_index_group, convert_str_to_hour, convert_to_date, convert_dt_to_day_str
|
|
19
|
+
from customeranalytics.utils import dimension_decision, convert_to_iso_format
|
|
20
|
+
from customeranalytics.data_storage_configurations.query_es import QueryES
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PromotionAnalytics:
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
def __init__(self,
|
|
28
|
+
has_promotion_connection=True,
|
|
29
|
+
host=None,
|
|
30
|
+
port=None,
|
|
31
|
+
download_index='downloads',
|
|
32
|
+
order_index='orders'):
|
|
33
|
+
"""
|
|
34
|
+
!!!!
|
|
35
|
+
******* ******** *****
|
|
36
|
+
Dimensional Promotion Analytic:
|
|
37
|
+
Promotion must be created individually for dimensions. For instance, the Data set contains locations dimension.
|
|
38
|
+
In this case, each location of 'orders' and 'downloads' indexes must be created individually.
|
|
39
|
+
by using 'download_index' and 'order_index' dimension can be assigned in order to create a Promotion Analyse
|
|
40
|
+
|
|
41
|
+
download_index; downloads_location1 this will be the location dimension of
|
|
42
|
+
parameters in order to query downloads indexes; 'location1'.
|
|
43
|
+
download_index; orders_location1 this will be the location dimension of
|
|
44
|
+
parameters in order to query orders indexes; 'location1'.
|
|
45
|
+
******* ******** *****
|
|
46
|
+
!!!
|
|
47
|
+
|
|
48
|
+
:param has_promotion_connection: has_promotion_connection if there is no products data then, no promotion analytics
|
|
49
|
+
:param host: elasticsearch host
|
|
50
|
+
:param port: elasticsearch port
|
|
51
|
+
:param download_index: elasticsearch port
|
|
52
|
+
:param order_index: elasticsearch port
|
|
53
|
+
"""
|
|
54
|
+
self.port = default_es_port if port is None else port
|
|
55
|
+
self.host = default_es_host if host is None else host
|
|
56
|
+
self.has_promotion_connection = has_promotion_connection
|
|
57
|
+
self.download_index = download_index
|
|
58
|
+
self.order_index = order_index
|
|
59
|
+
self.query_es = QueryES(port=port, host=host)
|
|
60
|
+
self.fields_promotions = ["id", "client", "promotion_id", "session_start_date",
|
|
61
|
+
"payment_amount", "discount_amount"]
|
|
62
|
+
self.promotions = pd.DataFrame()
|
|
63
|
+
self.time_periods = ["hourly", "daily"]
|
|
64
|
+
self.inorganic_orders = pd.DataFrame()
|
|
65
|
+
self.inorganic_orders_hour = pd.DataFrame()
|
|
66
|
+
self.daily_promotion_revenue = pd.DataFrame()
|
|
67
|
+
self.daily_promotion_discount = pd.DataFrame()
|
|
68
|
+
self.promotion_number_of_customer = pd.DataFrame()
|
|
69
|
+
self.analysis = ["inorganic_orders_per_promotion_per_day", "daily_organic_orders", "hourly_organic_orders",
|
|
70
|
+
"daily_inorganic_ratio", "hourly_inorganic_ratio",
|
|
71
|
+
"daily_promotion_revenue", "daily_promotion_discount", "avg_order_count_per_promo_per_cust",
|
|
72
|
+
"promotion_number_of_customer", "promotion_kpis"]
|
|
73
|
+
|
|
74
|
+
def dimensional_query(self, boolean_query=None):
|
|
75
|
+
if dimension_decision(self.order_index):
|
|
76
|
+
if boolean_query is None:
|
|
77
|
+
boolean_query = [{"term": {"dimension": self.order_index}}]
|
|
78
|
+
else:
|
|
79
|
+
boolean_query += [{"term": {"dimension": self.order_index}}]
|
|
80
|
+
return boolean_query
|
|
81
|
+
|
|
82
|
+
def get_time_period(self):
|
|
83
|
+
"""
|
|
84
|
+
converting date column of values into the time_periods (hourly weekly, monthly,..)
|
|
85
|
+
orders; total data (orders/downloads data with actions)
|
|
86
|
+
final data; data set with time periods
|
|
87
|
+
"""
|
|
88
|
+
for p in list(zip(self.time_periods,
|
|
89
|
+
[convert_str_to_hour, convert_dt_to_day_str])):
|
|
90
|
+
self.promotions[p[0]] = self.promotions["session_start_date"].apply(lambda x: p[1](x))
|
|
91
|
+
|
|
92
|
+
def get_promotions(self, end_date):
|
|
93
|
+
"""
|
|
94
|
+
1. Fetch data from orders with filter; actions.purchased: True with promotion Id
|
|
95
|
+
:param end_date: last date of data set
|
|
96
|
+
"""
|
|
97
|
+
self.query_es = QueryES(port=self.port, host=self.host)
|
|
98
|
+
self.query_es.date_queries_builder({"session_start_date": {"lt": end_date}})
|
|
99
|
+
self.query_es.query_builder(fields=None, _source=True,
|
|
100
|
+
boolean_queries=self.dimensional_query([{"term": {"actions.purchased": True}}]))
|
|
101
|
+
self.promotions = self.query_es.get_data_from_es()
|
|
102
|
+
self.promotions = pd.DataFrame(
|
|
103
|
+
[{col: r['_source'][col] for col in self.fields_promotions} for r in self.promotions])
|
|
104
|
+
|
|
105
|
+
self.promotions['payment_amount'] = self.promotions['payment_amount'].apply(lambda x: float(x))
|
|
106
|
+
self.promotions['session_start_date'] = self.promotions['session_start_date'].apply(lambda x: convert_to_date(x))
|
|
107
|
+
print(self.promotions.query("promotion_id != promotion_id"))
|
|
108
|
+
self.promotions['has_promotion'] = self.promotions['promotion_id'].apply(lambda x: True if x not in none_types else False)
|
|
109
|
+
self.get_time_period()
|
|
110
|
+
print(self.promotions.head())
|
|
111
|
+
|
|
112
|
+
def get_inorganic_orders_per_promotion_per_day(self):
|
|
113
|
+
"""
|
|
114
|
+
Orders which have promotion columns with ID not null values.
|
|
115
|
+
This process indicates total promoted order count per day per promotion_id.
|
|
116
|
+
"""
|
|
117
|
+
self.inorganic_orders = self.promotions.query("has_promotion == True").groupby(["daily", "promotion_id"]).agg(
|
|
118
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(columns={"id": "order_count"})
|
|
119
|
+
return self.inorganic_orders
|
|
120
|
+
|
|
121
|
+
def get_inorganic_orders_per_promotion_per_hour(self):
|
|
122
|
+
"""
|
|
123
|
+
Orders which have promotion columns with ID not null values.
|
|
124
|
+
This process indicates total promoted order count per hour per promotion_id.
|
|
125
|
+
"""
|
|
126
|
+
self.inorganic_orders_hour = self.promotions.query("has_promotion == True").groupby(["hourly", "promotion_id"]).agg(
|
|
127
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(columns={"id": "order_count"})
|
|
128
|
+
return self.inorganic_orders_hour
|
|
129
|
+
|
|
130
|
+
def get_organic_orders_per_day(self):
|
|
131
|
+
"""
|
|
132
|
+
Orders which have promotion columns with null values.
|
|
133
|
+
This process indicates total **NOT** promoted order count per hour per promotion_id.
|
|
134
|
+
"""
|
|
135
|
+
return self.promotions.query("has_promotion != True").groupby("daily").agg(
|
|
136
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(columns={"id": "order_count"})
|
|
137
|
+
|
|
138
|
+
def get_daily_inorganic_ratio(self):
|
|
139
|
+
"""
|
|
140
|
+
daily organic orders / daily total order count
|
|
141
|
+
"""
|
|
142
|
+
daily_inorganic_ratio = self.promotions.groupby(["daily", "promotion_id"]).agg(
|
|
143
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(columns={"id": "total_order_count"})
|
|
144
|
+
daily_inorganic_ratio = pd.merge(daily_inorganic_ratio, self.inorganic_orders, how='left', on=["daily", "promotion_id"])
|
|
145
|
+
daily_inorganic_ratio['inorganic_ratio'] = daily_inorganic_ratio['order_count'] / daily_inorganic_ratio['total_order_count']
|
|
146
|
+
return daily_inorganic_ratio
|
|
147
|
+
|
|
148
|
+
def get_hourly_inorganic_ratio(self):
|
|
149
|
+
"""
|
|
150
|
+
hourly organic orders / hourly total order count
|
|
151
|
+
"""
|
|
152
|
+
hourly_inorganic_ratio = self.promotions.groupby(["hourly", "promotion_id"]).agg(
|
|
153
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(columns={"id": "total_order_count"})
|
|
154
|
+
hourly_inorganic_ratio = pd.merge(hourly_inorganic_ratio, self.inorganic_orders_hour, how='left', on=["hourly", "promotion_id"])
|
|
155
|
+
hourly_inorganic_ratio['inorganic_ratio'] = hourly_inorganic_ratio['order_count'] / hourly_inorganic_ratio['total_order_count']
|
|
156
|
+
return hourly_inorganic_ratio
|
|
157
|
+
|
|
158
|
+
def get_daily_promotion_revenue(self):
|
|
159
|
+
"""
|
|
160
|
+
Orders which have promotion columns with null values.
|
|
161
|
+
This process indicates total **NOT** promoted payment amount per hour per promotion_id.
|
|
162
|
+
"""
|
|
163
|
+
self.daily_promotion_revenue = self.promotions.query("has_promotion == True").groupby(
|
|
164
|
+
["daily", "promotion_id"]).agg({"payment_amount": 'sum'}).reset_index().rename(
|
|
165
|
+
columns={"payment_amount": "total_revenue"})
|
|
166
|
+
return self.daily_promotion_revenue
|
|
167
|
+
|
|
168
|
+
def get_daily_promotion_discount(self):
|
|
169
|
+
"""
|
|
170
|
+
Orders which have promotion columns with null values.
|
|
171
|
+
This process indicates total **NOT** promoted discount amount per hour per promotion_id.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
if len(self.promotions.query("discount_amount == discount_amount")) != 0:
|
|
175
|
+
self.daily_promotion_discount = self.promotions.query(
|
|
176
|
+
"has_promotion == True and discount_amount == discount_amount").groupby(["daily", "promotion_id"]).agg(
|
|
177
|
+
{"discount_amount": 'sum'}).reset_index().rename(columns={"discount_amount": "total_discount"})
|
|
178
|
+
return self.daily_promotion_discount
|
|
179
|
+
|
|
180
|
+
def get_promotion_average_order_count_per_customer(self):
|
|
181
|
+
"""
|
|
182
|
+
average order count per promotion per customer
|
|
183
|
+
"""
|
|
184
|
+
average_order_count_per_cust = self.promotions.groupby(["client", "promotion_id"]).agg(
|
|
185
|
+
{"id": lambda x: len(np.unique(x))}).reset_index().rename(columns={"id": "average_order_count"})
|
|
186
|
+
return average_order_count_per_cust.groupby("promotion_id").agg({"average_order_count": "mean"}).reset_index()
|
|
187
|
+
|
|
188
|
+
def get_promotion_number_of_customer(self):
|
|
189
|
+
"""
|
|
190
|
+
average order count per promotion per customer
|
|
191
|
+
"""
|
|
192
|
+
self.promotion_number_of_customer = self.promotions.groupby("promotion_id").agg(
|
|
193
|
+
{"client": lambda x: len(np.unique(x))}).reset_index().rename(columns={"client": "client_count"})
|
|
194
|
+
return self.promotion_number_of_customer
|
|
195
|
+
|
|
196
|
+
def get_promotion_kpis(self):
|
|
197
|
+
"""
|
|
198
|
+
average daily order count per promotion
|
|
199
|
+
average daily payment amount per promotion
|
|
200
|
+
average daily discount amount per promotion
|
|
201
|
+
number of user per promotion
|
|
202
|
+
"""
|
|
203
|
+
kpi_1 = self.inorganic_orders.groupby("promotion_id").agg({"order_count": "mean"}).reset_index()
|
|
204
|
+
kpi_2 = self.daily_promotion_revenue.groupby("promotion_id").agg({"total_revenue": "mean"}).reset_index()
|
|
205
|
+
kpi_3 = self.daily_promotion_discount.groupby("promotion_id").agg({"total_discount": "mean"}).reset_index()
|
|
206
|
+
kpi_4 = self.promotion_number_of_customer
|
|
207
|
+
|
|
208
|
+
promotions = pd.DataFrame(list(self.promotions['promotion_id'].unique())).rename(columns={0: "promotion_id"})
|
|
209
|
+
return promotions.merge(kpi_1, on='promotion_id').merge(
|
|
210
|
+
kpi_2, on='promotion_id').merge(kpi_3, on='promotion_id').merge(kpi_4, on='promotion_id').fillna(0)
|
|
211
|
+
|
|
212
|
+
def execute_promotion_analysis(self, end_date=None):
|
|
213
|
+
"""
|
|
214
|
+
1. Check for promotions data available on elasticsearch orders index.
|
|
215
|
+
2. Execute each analysis separately.
|
|
216
|
+
3. Insert into 'reports' index.
|
|
217
|
+
"""
|
|
218
|
+
if self.has_promotion_connection:
|
|
219
|
+
self.get_promotions(end_date=convert_to_iso_format(current_date_to_day() if end_date is None else end_date))
|
|
220
|
+
for ins in list(zip(self.analysis, [self.get_inorganic_orders_per_promotion_per_day,
|
|
221
|
+
self.get_organic_orders_per_day,
|
|
222
|
+
self.get_inorganic_orders_per_promotion_per_hour,
|
|
223
|
+
self.get_daily_inorganic_ratio, self.get_hourly_inorganic_ratio,
|
|
224
|
+
self.get_daily_promotion_revenue, self.get_daily_promotion_discount,
|
|
225
|
+
self.get_promotion_average_order_count_per_customer,
|
|
226
|
+
self.get_promotion_number_of_customer,
|
|
227
|
+
self.get_promotion_kpis])):
|
|
228
|
+
_result = ins[1]()
|
|
229
|
+
self.insert_into_reports_index(promotion_analytics=_result,
|
|
230
|
+
pa_type=ins[0], start_date=end_date, index=self.order_index)
|
|
231
|
+
del _result
|
|
232
|
+
|
|
233
|
+
def insert_into_reports_index(self, promotion_analytics, pa_type, start_date=None, index='orders'):
|
|
234
|
+
"""
|
|
235
|
+
via query_es.py, each report can be inserted into the reports index with the given format.
|
|
236
|
+
{"id": unique report id,
|
|
237
|
+
"report_date": start_date or current date,
|
|
238
|
+
"report_name": "promotion_analytic",
|
|
239
|
+
"index": "main",
|
|
240
|
+
"report_types": {"inorganic_orders_per_promotion_per_day", "daily_organic_orders",
|
|
241
|
+
"daily_inorganic_ratio", "hourly_inorganic_ratio",
|
|
242
|
+
"daily_promotion_revenue", "daily_promotion_discount", "avg_order_count_per_promo_per_cust"
|
|
243
|
+
},
|
|
244
|
+
"data": promotion_analytic.to_dict("records") - dataframe to list of dictionary
|
|
245
|
+
}
|
|
246
|
+
:param promotion_analytic: data set, data frame
|
|
247
|
+
:param start_date: data start date
|
|
248
|
+
:param pa_type: promotion analytic type
|
|
249
|
+
:param index: dimentionality of data index orders_location1 ; dimension = location1
|
|
250
|
+
"""
|
|
251
|
+
list_of_obj = [{"id": np.random.randint(200000000),
|
|
252
|
+
"report_date": current_date_to_day().isoformat() if start_date is None else start_date,
|
|
253
|
+
"report_name": "promotion_analytic",
|
|
254
|
+
"index": get_index_group(index),
|
|
255
|
+
"report_types": {"type": pa_type},
|
|
256
|
+
"data": promotion_analytics.fillna(0).to_dict("records")}]
|
|
257
|
+
self.query_es.insert_data_to_index(list_of_obj, index='reports')
|
|
258
|
+
|
|
259
|
+
def fetch(self, promotion_analytic_name, start_date=None):
|
|
260
|
+
"""
|
|
261
|
+
This allows us to query the created Promotion Analytics.
|
|
262
|
+
promotion_analytic_name is crucial for us to collect the correct filters.
|
|
263
|
+
Example of queries;
|
|
264
|
+
- promotion_analytic_name: inorganic_orders_per_promotion_per_day,
|
|
265
|
+
- start_date: 2021-01-01T00:00:00
|
|
266
|
+
|
|
267
|
+
{'size': 10000000,
|
|
268
|
+
'from': 0,
|
|
269
|
+
'_source': True,
|
|
270
|
+
'query': {'bool': {'must': [
|
|
271
|
+
{'term': {'report_name': 'p'}},
|
|
272
|
+
{"term": {"index": "orders_location1"}}
|
|
273
|
+
{'term': {'report_types.type': 'most_ordered_products'}},
|
|
274
|
+
{'range': {'report_date': {'lt': '2021-04-01T00:00:00'}}}]}}}
|
|
275
|
+
|
|
276
|
+
- start date will be filtered from data frame. In this example; .query("daily > @start_date")
|
|
277
|
+
|
|
278
|
+
:param promotion_analytic_name: most_ordered_products.
|
|
279
|
+
:param start_date: product analytic report created date
|
|
280
|
+
:param index: index_name in order to get dimension_of data. If there is no dimension, no need to be assigned
|
|
281
|
+
:return: data frame
|
|
282
|
+
"""
|
|
283
|
+
boolean_queries, date_queries = [], []
|
|
284
|
+
boolean_queries = [{"term": {"report_types.type": promotion_analytic_name}},
|
|
285
|
+
{"term": {"index": get_index_group(self.order_index)}}]
|
|
286
|
+
|
|
287
|
+
if start_date is not None:
|
|
288
|
+
date_queries = [{"range": {"report_date": {"lt": convert_to_iso_format(start_date)}}}]
|
|
289
|
+
|
|
290
|
+
self.query_es = QueryES(port=self.port,
|
|
291
|
+
host=self.host)
|
|
292
|
+
self.query_es.query_builder(fields=None, _source=True,
|
|
293
|
+
date_queries=date_queries,
|
|
294
|
+
boolean_queries=boolean_queries)
|
|
295
|
+
_res = self.query_es.get_data_from_es(index="reports")
|
|
296
|
+
_data = pd.DataFrame()
|
|
297
|
+
if len(_res) != 0:
|
|
298
|
+
_data = pd.DataFrame(_res[0]['_source']['data'])
|
|
299
|
+
return _data
|