import copy from typing import Any, Dict, List, Union from service.tasks.analytics.modules import MODULES from service.tasks.analytics.modules.params import ( BRACKETS_AGE, BRACKETS_ITEM_COUNT, BRACKETS_RECENCY, MAP_DATA_DAY, MAP_DATA_GROUP, MAP_DATA_ITEM, RFM_MODEL_ATTRIBUTE_LABELS, SORTED_SUPERFAN_LEVELS, SUPERFAN_MODEL_ATTRIBUTE_LABELS, ) from service.utils.module_tools import class_dict from .area_chart import AreaChart from .bar_chart import BarChart from .calendar_chart import CalendarChart from .chart import Chart from .group_chart import GroupChart from .line_chart import LineChart from .pareto_chart import ParetoChart from .pie_chart import PieChart from .radar_chart import RadarChart from .scatter_chart import ScatterChart from .stack_chart import StackChart from .stack_chart_100 import StackChart100 from .table_chart import Table __all__ = [ "AreaChart", "BarChart", "CalendarChart", "Chart", "GroupChart", "LineChart", "ParetoChart", "PieChart", "RadarChart", "ScatterChart", "StackChart", "StackChart100", "Table", "get_global_chart_configurations", "get_chart", "prep_chart_conf", ] CHARTS = class_dict(__name__, Chart) CHART_CONF: List[Dict[str, Union[str, int, Dict[str, Any]]]] = [ { "type": "PieChart", "label": "Gender", "group_tag": "demographics", "module": "AttributeValueCount", "inputs": {"field_name": "enrUniqueGender"}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "BarChart", "label": "Top Countries", "group_tag": "geolocation", "module": "AttributeValueCount", "inputs": { "field_name": "enrCountry", "SORT_BY_VALUE": "DESC", "LIMIT": 10, "WHERE": "!='unknown'", }, "options": {"horizontal": True, "showLeftAxis": True}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "BarChart", "label": "Top States/Regions", "group_tag": "geolocation", "module": "AttributeValueCount", "inputs": { "field_name": "enrAdminAreaLvl1", "SORT_BY_VALUE": "DESC", "LIMIT": 10, "WHERE": "!='unknown'", }, "options": {"horizontal": True, "showLeftAxis": True}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "BarChart", "label": "Age Groups", "group_tag": "demographics", "module": "AttributeValueCount", "inputs": {"field_name": "enrAge", "brackets": BRACKETS_AGE}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "StackChart100", "label": "Gender Distribution by Age Groups", "group_tag": "demographics", "module": "AttributeGroupValuePercent", "inputs": { "field_name": ["enrAge", "enrUniqueGender"], "brackets_apply_to": 1, "brackets": BRACKETS_AGE, }, "options": { "horizontal": True, "showLeftAxis": True, "showBottomAxis": False, "spacingOuter": 0, "showGrid": False, "textLabel": True, # "valueFormat": "{value}%", }, "output_maps": {"data": MAP_DATA_GROUP, "item": MAP_DATA_ITEM}, }, # RFM { "type": "BarChart", "label": "Fans by RFM Classification", "group_tag": "rfm", "module": "AttributeValueCount", "inputs": { "field_name": "enrUserRFM", "SORT_BY_VALUE": "DESC", "WHERE": "!='unknown'", }, "options": {"horizontal": True, "showLeftAxis": True}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "ParetoChart", "label": "Segments Total Spend, as Cumulative Share from Total Money Spent by Fans", "group_tag": "rfm", "description": "This graph shows which RFM segments contribute most for overall sales figures. This could help you understand which segments to focus on.\nFor example with limited marketing resources it makes sense to spend only on the segments that deliver the biggest share of monetary volume.", "module": "AttributeSumBySegment", "inputs": { "field_name": "enrUserRFM", "segment_attribute": "enrUserRFM", "target_attribute": "enrPurchaseMonetary", }, "options": {"horizontal": True, "axisLeft": True, "valueFormat": "${value}"}, "output_maps": {"data": MAP_DATA_ITEM}, }, # {'type': 'AreaChart', # 'label': 'RFM Score', # 'group_tag': 'rfm', # 'module': 'AttributeValueCount', # 'inputs': {'field_name': 'enrRFMScore', 'SORT_BY_NUM_LABEL': 'ASC'}, # 'output_maps': { # 'data': MAP_DATA_ITEM # } # }, { "type": "StackChart100", "label": "Age Group Distribution by Segment", "group_tag": "rfm", "module": "AttributeGroupValuePercent", "inputs": { "field_name": ["enrUserRFM", "enrAge"], "brackets_apply_to": 2, "brackets": BRACKETS_AGE, }, "options": { "horizontal": True, "showLeftAxis": True, "showBottomAxis": False, "spacingOuter": 0, "showGrid": False, "textLabel": True, # "valueFormat": "{value}%", }, "output_maps": {"data": MAP_DATA_GROUP, "item": MAP_DATA_ITEM}, }, { "type": "StackChart100", "label": "Gender Distribution by Segment", "group_tag": "rfm", "module": "AttributeGroupValuePercent", "inputs": {"field_name": ["enrUserRFM", "enrUniqueGender"]}, "options": { "horizontal": True, "showLeftAxis": True, "showBottomAxis": False, "spacingOuter": 0, "showGrid": False, "textLabel": True, # "valueFormat": "{value}%", }, "output_maps": {"data": MAP_DATA_GROUP, "item": MAP_DATA_ITEM}, }, { "type": "StackChart100", "label": "Transactions Grouped by Monetary Value, Distribution by Segment", "group_tag": "rfm", "module": "AttributeGroupValuePercent", "inputs": {"field_name": ["enrUserRFM", "enrMonetaryBins"]}, "options": { "horizontal": True, "showLeftAxis": True, "showBottomAxis": False, "spacingOuter": 0, "showGrid": False, "textLabel": True, # "valueFormat": "{value}%", }, "output_maps": {"data": MAP_DATA_GROUP, "item": MAP_DATA_ITEM}, }, { "type": "StackChart100", "label": "Transactions Grouped by Recency (by Days Before the Most Recent Known Transaction Date), Distribution by Segment", "group_tag": "rfm", "module": "AttributeGroupValuePercent", "inputs": { "field_name": ["enrUserRFM", "enrTransactionRecency"], "brackets_apply_to": 2, "brackets": BRACKETS_RECENCY, }, "options": { "horizontal": True, "showLeftAxis": True, "showBottomAxis": False, "spacingOuter": 0, "showGrid": False, "textLabel": True, # "valueFormat": "{value}%", }, "output_maps": {"data": MAP_DATA_GROUP, "item": MAP_DATA_ITEM}, }, { "type": "StackChart100", "label": "Transactions Grouped by Frequency (nr of items bought), Distribution by Segment", "group_tag": "rfm", "module": "AttributeGroupValuePercent", "inputs": { "field_name": ["enrUserRFM", "enrPurchaseQuantity"], "brackets_apply_to": 2, "brackets": BRACKETS_ITEM_COUNT, }, "options": { "horizontal": True, "showLeftAxis": True, "showBottomAxis": False, "spacingOuter": 0, "showGrid": False, "textLabel": True, # "valueFormat": "{value}%", }, "output_maps": {"data": MAP_DATA_GROUP, "item": MAP_DATA_ITEM}, }, { "type": "LineChart", "label": "Transactions # by Recency (by Days Before the Most Recent Known Transaction Date), by Segment", "group_tag": "rfm", "curve": "basis", # 'basis' or 'linear' "dynamic": { "values": { "module": "AttributeValues", "inputs": {"field_name": "enrUserRFM"}, "output_map": {"value": "value"}, }, "template": { "label": "{value}", "module": "AttributeValuePercentile", "inputs": { "field_name": "enrTransactionRecency", "FILTER": {"attribute": "enrUserRFM", "where": "='{value}'"}, }, "output_maps": {"data": MAP_DATA_ITEM}, }, }, }, { "type": "LineChart", "label": "Transactions # by Frequency, by Segment", "group_tag": "rfm", "curve": "basis", # 'basis' or 'linear' "dynamic": { "values": { "module": "AttributeValues", "inputs": {"field_name": "enrUserRFM"}, "output_map": {"value": "value"}, }, "template": { "label": "{value}", "module": "AttributeValuePercentile", "inputs": { "field_name": "enrTransactionFrequency", "FILTER": {"attribute": "enrUserRFM", "where": "='{value}'"}, }, "output_maps": {"data": MAP_DATA_ITEM}, }, }, }, { "type": "LineChart", "label": "Transactions # by Monetary Value, by Segment", "group_tag": "rfm", "curve": "basis", # 'basis' or 'linear' "dynamic": { "values": { "module": "AttributeValues", "inputs": {"field_name": "enrUserRFM"}, "output_map": {"value": "value"}, }, "template": { "label": "{value}", "module": "AttributeValuePercentile", "inputs": { "field_name": "enrPurchaseMonetary", "FILTER": {"attribute": "enrUserRFM", "where": "='{value}'"}, }, "output_maps": {"data": MAP_DATA_ITEM}, }, }, }, { "type": "Table", "label": "RFM Score Components", "group_tag": "rfm", "module": "AttributeRFMModelScoreComponents", "inputs": { "field_name": ["enrUserRFM"], "attributes": ( "enrTransactionRecency", "enrTransactionFrequency", "enrTransactionMonetary", ), "sort_column": "group", "attribute_labels": RFM_MODEL_ATTRIBUTE_LABELS, }, }, # ## Purchase intelligence { "type": "BarChart", "label": "Transactions # per Day Of Week", "group_tag": "purchase_intelligence", "module": "AttributeValueCount", # We create "Sunday" from label, but sort by "7". Values from https://www.postgresql.org/docs/9.6/functions-formatting.html # We do a little hack here with the RANGE, where we take a random week just to get a range between Monday and Sunday (both included) "inputs": { "field_name": "enrPurchaseDate", "LABEL_TO_DATE": "Day", "LABEL_TO_DATE_AUX": "ID", "SORT_BY_AUXILIARY": "ASC", "RANGE": ["'2010-01-18'", "'2010-01-24'", "INTERVAL '1 day'"], }, "options": { # "axisBottomAngle": -45, }, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "BarChart", "label": "Transactions # per Hour Of Day", "group_tag": "purchase_intelligence", "module": "AttributeValueCount", "inputs": { "field_name": "enrHourOfDay", "SORT_BY_NUM_LABEL": "ASC", "RANGE": [0, 23, 1], }, "options": {"axisBottomProps": {"numTicks": 24}}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "AreaChart", "label": "Transactions # per Day Of Month", "group_tag": "purchase_intelligence", "module": "AttributeValueCount", "inputs": { "field_name": "enrDayOfMonth", "SORT_BY_NUM_LABEL": "ASC", "RANGE": [1, 31, 1], }, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "AreaChart", "label": "Transactions # by Full Timeline", "group_tag": "purchase_intelligence", "module": "AttributeValueCount", "inputs": { "field_name": "enrPurchaseDate", "SORT_BY_LABEL": "ASC", "LABEL_TO_DATE": "YYYY-MM-DD", }, "options": { "axisBottomAngle": -45, }, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "BarChart", "label": "Top Cities", "group_tag": "geolocation", "module": "AttributeValueCount", "inputs": { "field_name": "enrLocality", "SORT_BY_VALUE": "DESC", "LIMIT": 10, "WHERE": "!='unknown'", }, "options": {"horizontal": True, "showLeftAxis": True}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "AreaChart", "label": "Transactions # by Recency (by Days Before the Most Recent Known Transaction Date), Full Timeline", "group_tag": "purchase_intelligence", "module": "AttributeValueCount", # TODO! AttributeValueCountHistogram "inputs": {"field_name": "enrTransactionRecency", "SORT_BY_NUM_LABEL": "ASC"}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "AreaChart", "label": "Transactions # by Monetary Value", "group_tag": "purchase_intelligence", "module": "AttributeValueCount", "inputs": {"field_name": "enrPurchaseMonetary", "SORT_BY_NUM_LABEL": "ASC"}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "CalendarChart", "label": "Transactions # per Day on Full Timeline", "group_tag": "purchase_intelligence", "module": "AttributeValueCount", "inputs": { "field_name": "enrPurchaseDate", "SORT_BY_LABEL": "ASC", "LABEL_TO_DATE": "YYYY-MM-DD", }, "output_maps": {"data": MAP_DATA_DAY}, }, # ML insight graphs { "type": "BarChart", "label": "Fans by ML Clusters", "group_tag": "ml", "module": "AttributeValueCount", "inputs": { "field_name": "enrMLCluster", "SORT_BY_VALUE": "DESC", "WHERE": "!='unknown'", }, "options": {"horizontal": True, "showLeftAxis": True}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "StackChart100", "label": "Age Group Distribution by Cluster", "group_tag": "ml", "module": "AttributeGroupValuePercent", "inputs": { "field_name": ["enrMLCluster", "enrAge"], "brackets_apply_to": 2, "brackets": BRACKETS_AGE, }, "output_maps": {"data": MAP_DATA_GROUP, "item": MAP_DATA_ITEM}, "options": { "horizontal": True, "showLeftAxis": True, "showBottomAxis": False, "spacingOuter": 0, "showGrid": False, "textLabel": True, # "valueFormat": "{value}%", }, }, { "type": "LineChart", "label": "Transactions # by Monetary Value, by Cluster", "group_tag": "ml", "curve": "basis", # 'basis' or 'linear' "dynamic": { "values": { "module": "AttributeValues", "inputs": {"field_name": "enrMLCluster"}, "output_map": {"value": "value"}, }, "template": { "label": "{value}", "module": "AttributeValuePercentile", "inputs": { "field_name": "enrPurchaseMonetary", "FILTER": {"attribute": "enrMLCluster", "where": "='{value}'"}, }, "output_maps": {"data": MAP_DATA_ITEM}, }, }, }, { "type": "LineChart", "label": "Transactions # by Recency (by Days Before the Most Recent Known Transaction Date), by Cluster", "group_tag": "ml", "curve": "basis", # 'basis' or 'linear' "dynamic": { "values": { "module": "AttributeValues", "inputs": {"field_name": "enrMLCluster"}, "output_map": {"value": "value"}, }, "template": { "label": "{value}", "module": "AttributeValuePercentile", "inputs": { "field_name": "enrTransactionRecency", "FILTER": {"attribute": "enrMLCluster", "where": "='{value}'"}, }, "output_maps": {"data": MAP_DATA_ITEM}, }, }, }, { "type": "LineChart", "label": "Transactions # by Frequency, by Cluster", "group_tag": "ml", "curve": "basis", # 'basis' or 'linear' "dynamic": { "values": { "module": "AttributeValues", "inputs": {"field_name": "enrMLCluster"}, "output_map": {"value": "value"}, }, "template": { "label": "{value}", "module": "AttributeValuePercentile", "inputs": { "field_name": "enrTransactionFrequency", "FILTER": {"attribute": "enrMLCluster", "where": "='{value}'"}, }, "output_maps": {"data": MAP_DATA_ITEM}, }, }, }, # Superfans { "type": "BarChart", "label": "Superfans", "group_tag": "superfans", "module": "AttributeValueCount", "inputs": { "field_name": "enrSuperfan", "sort_list": SORTED_SUPERFAN_LEVELS, "sort_column": "label", }, "options": {"horizontal": True, "showLeftAxis": True}, "output_maps": {"data": MAP_DATA_ITEM}, }, { "type": "StackChart100", "label": "Age Group Distribution by Superfans", "group_tag": "superfans", "module": "AttributeGroupValuePercent", "inputs": { "field_name": ["enrSuperfan", "enrAge"], "brackets_apply_to": 2, "brackets": BRACKETS_AGE, }, "output_maps": {"data": MAP_DATA_GROUP, "item": MAP_DATA_ITEM}, "options": { "horizontal": True, "showLeftAxis": True, "showBottomAxis": False, "spacingOuter": 0, "showGrid": False, "textLabel": True, # "valueFormat": "{value}%", }, }, { "type": "StackChart100", "label": "Gender Distribution by Superfans", "group_tag": "superfans", "module": "AttributeGroupValuePercent", "inputs": {"field_name": ["enrSuperfan", "enrUniqueGender"]}, "options": { "horizontal": True, "showLeftAxis": True, "showBottomAxis": False, "spacingOuter": 0, "showGrid": False, "textLabel": True, # "valueFormat": "{value}%", }, "output_maps": {"data": MAP_DATA_GROUP, "item": MAP_DATA_ITEM}, }, { "type": "Table", "label": "Superfan Score Components", "group_tag": "superfans", "module": "AttributeSuperfanModelScoreComponents", "inputs": { "field_name": ["enrSuperfan"], "binary_attributes": ( "superfan_fanclub_member", "superfan_vip_status", "superfan_marketing_optin", "superfan_presave_status", "superfan_paid_status", ), "numeric_attributes": ( "enrPurchaseQuantity", "userTracksPlayed", "profile_followers", "superfan_media_follows_count", "superfan_unique_newsletter_signups", ), "sort_list": SORTED_SUPERFAN_LEVELS, "sort_column": "group", "attribute_labels": SUPERFAN_MODEL_ATTRIBUTE_LABELS, }, }, # TODO! This needs fix. Only stackchart type supports grouped inputs right now. # {'type': 'BarChart', # 'label': 'Average Score per Superfan Segment.', # 'group_tag': 'superfans', # 'module': 'AttributeGroupAverage', # 'inputs': {'field_name': ['enrSuperfan', 'enrSuperfanScore']}, # 'options': { # "horizontal": True, # "showLeftAxis": True, # "showBottomAxis": False, # "spacingOuter": 0, # "showGrid": False, # "textLabel": True # }, # 'output_maps': { # 'data': MAP_DATA_GROUP, # 'item': MAP_DATA_ITEM # } # }, # {'type': 'LineChart', # 'label': 'Transaction Recency by Superfan', # 'group_tag': 'superfans', # 'curve': 'basis', # 'basis' or 'linear' # 'lines': [ # {'label': 'Regular', # 'module': 'AttributeValueCount', # 'inputs': {'field_name': 'enrTransactionRecency', 'SORT_BY_NUM_LABEL': 'ASC', 'RANGE': True, 'FILTER': {'attribute': 'enrSuperfan', 'where': "='Regular'"}}, # 'output_maps': {'data': MAP_DATA_ITEM} # }, # {'label': 'Superfan', # 'module': 'AttributeValueCount', # 'inputs': {'field_name': 'enrTransactionRecency', 'SORT_BY_NUM_LABEL': 'ASC', 'RANGE': True, 'FILTER': {'attribute': 'enrSuperfan', 'where': "='Superfan'"}}, # 'output_maps': {'data': MAP_DATA_ITEM} # }, # ], # }, ] # Generate somewhat stable id's for every chart conf. # Later this can be just auto-incremented id, when stored in table. for _cid, _conf in enumerate(CHART_CONF): _conf["id"] = _cid def get_global_chart_configurations(): return copy.deepcopy(CHART_CONF) def get_chart(chart_conf): ct = chart_conf["type"] if ct in CHARTS: return CHARTS[ct](chart_conf) return None def get_chart_class(name): if name in CHARTS: return CHARTS[name] return None def get_chart_series(chart_conf, available_fields_list, schema, kwargs): if "type" in chart_conf: return get_chart_class(chart_conf["type"]).get_series( chart_conf, available_fields_list, schema, kwargs ) else: return [] def prep_chart_conf(chart_conf, available_fields_list, schema, kwargs): series = get_chart_series(chart_conf, available_fields_list, schema, kwargs) if series: for conf in series: attribute_ids = ( ( MODULES[conf["module"]].populate_attribute_ids( conf, available_fields_list ) ) if "module" in conf else None ) if attribute_ids: inputs = conf["inputs"] inputs["schema"] = schema inputs.update(attribute_ids) inputs.update(kwargs) else: return False else: return False return True