瀏覽代碼

Initial commit

zhusc 5 月之前
父節點
當前提交
f51ccd0436

+ 3 - 0
README.md

@@ -1,2 +1,5 @@
 # stock-operation
 
+存量运营平台
+平台介绍:本平台基于金融数据、行为数据及触达数据,设置预授信规则对客户配置额度和利率得到主动授信客户,对其行为进行全流程监控,设计多种模型生成触达名单并制定触达策略,使用多种触达手段进行营销触达,最后对触达效果进行评估并测算ROI,根据效果进一步修正规则和模型,实现存量客户高效运营。
+功能模块:(1)预授信客户额价配置(2)客户行为挖掘及监控(3)触达策略配置及监控(4)模型开发及监控(5)触达效果评估及优化(6)大模型行为交互及精准营销

+ 9 - 0
user_events/__init__.py

@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  存量运营平台-行为客户行为挖掘及监控
+"""
+
+if __name__ == "__main__":
+    pass

+ 27 - 0
user_events/analyze/BehaviorAnalyzer.py

@@ -0,0 +1,27 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  行为分析
+"""
+import time
+import random
+from collections import defaultdict
+import matplotlib.pyplot as plt
+
+# 行为分析模块
+class BehaviorAnalyzer:
+    def __init__(self, data):
+        self.data = data
+
+    def analyze(self):
+        # 分析用户行为模式,并展示各个指标的统计情况
+        behavior_count = defaultdict(lambda: defaultdict(int))
+        action_stats = defaultdict(int)
+        for item in self.data:
+            user = item['user']
+            action = item['action']
+            behavior_count[user][action] += 1
+            action_stats[action] += 1
+        return behavior_count, action_stats
+

+ 19 - 0
user_events/analyze/DetectAnomalies.py

@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  行为分析
+"""
+
+# 异常检测模块
+class AnomalyDetector:
+    def __init__(self, behavior_data):
+        self.behavior_data = behavior_data
+
+    def detect(self):
+        # 识别异常行为
+        anomalies = []
+        for user, actions in self.behavior_data.items():
+            if 'purchase' in actions and actions['purchase'] > 5:
+                anomalies.append(user)
+        return anomalies

+ 28 - 0
user_events/analyze/SegmentUsers.py

@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  行为分析
+"""
+
+# 用户分群模块
+class UserSegmentation:
+    def __init__(self, behavior_data):
+        self.behavior_data = behavior_data
+
+    def segment(self):
+        # 简单的用户分群,根据行为次数划分
+        segments = {
+            'high_activity': [],
+            'medium_activity': [],
+            'low_activity': []
+        }
+        for user, actions in self.behavior_data.items():
+            total_actions = sum(actions.values())
+            if total_actions > 10:
+                segments['high_activity'].append(user)
+            elif total_actions > 5:
+                segments['medium_activity'].append(user)
+            else:
+                segments['low_activity'].append(user)
+        return segments

+ 6 - 0
user_events/analyze/__init__.py

@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  分析
+"""

+ 3 - 8
user_events/data/__init__.py

@@ -1,14 +1,9 @@
 # -*- coding: utf-8 -*-
 """
-@author: yq
-@time: 2024/10/30
-@desc: 数据加载、加工相关
+@author: 朱三成
+@time: 2024/11/18
+@desc: 数据
 """
-from .loader.data_loader_excel import DataLoaderExcel
-from .loader.data_loader_base import DataLoaderBase
-from .loader.data_loader_mysql import DataLoaderMysql
-
-__all__ = ['DataLoaderBase', 'DataLoaderMysql', 'DataLoaderExcel']
 
 if __name__ == "__main__":
     pass

+ 24 - 0
user_events/data/collector/DataCollector.py

@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  数据采集
+"""
+
+
+import random
+
+class DataCollector:
+    def __init__(self):
+        self.data = []
+
+    def collect(self):
+        # 模拟从数据库或API收集数据,包含至少20条记录和多种行为
+        actions = ['click', 'view', 'add_to_cart', 'purchase', 'review']
+        prodoct = ['渝快贷', '', 'item3', 'item4', 'item5']
+        users = ['User{}'.format(i) for i in range(1, 21)]
+        self.data = [
+            {'user': random.choice(users), 'action': random.choice(actions), 'item': random.choice(items)}
+            for _ in range(100)
+        ]
+        return self.data

+ 9 - 0
user_events/data/collector/__init__.py

@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  数据采集
+"""
+
+if __name__ == "__main__":
+    pass

+ 0 - 9
user_events/data/insight/__init__.py

@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-@author: yq
-@time: 2024/11/1
-@desc:  数据统计分析
-"""
-
-if __name__ == "__main__":
-    pass

+ 0 - 31
user_events/data/insight/data_explore.py

@@ -1,31 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-@author: yq
-@time: 2024/11/13
-@desc: 数据探索
-"""
-import pandas as pd
-
-from commom import f_save_train_df
-
-
-class DataExplore():
-
-    def __init__(self, ):
-        pass
-
-    def distribution(self, df: pd.DataFrame) -> pd.DataFrame:
-        """
-        数据分布,缺失率,中位数,众数,偏离度等
-        """
-        pass
-
-    def save(self, df):
-        """
-        数据探索结果固化
-        """
-        f_save_train_df("distribution", df)
-
-
-if __name__ == "__main__":
-    pass

+ 0 - 9
user_events/data/loader/__init__.py

@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-@author: yq
-@time: 2024/11/1
-@desc:  数据加载相关
-"""
-
-if __name__ == "__main__":
-    pass

+ 0 - 24
user_events/data/loader/data_loader_base.py

@@ -1,24 +0,0 @@
-# -*- coding:utf-8 -*-
-"""
-@author: yq
-@time: 2024/1/2
-@desc: 数据加载基类
-"""
-import abc
-
-import pandas as pd
-
-
-class DataLoaderBase(metaclass=abc.ABCMeta):
-
-    @abc.abstractmethod
-    def get_connect(self):
-        pass
-
-    @abc.abstractmethod
-    def close_connect(self):
-        pass
-
-    @abc.abstractmethod
-    def get_data(self, *args, **kwargs) -> pd.DataFrame:
-        pass

+ 0 - 36
user_events/data/loader/data_loader_excel.py

@@ -1,36 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-@author: yq
-@time: 2024/10/31
-@desc: 
-"""
-import pandas as pd
-
-from commom import get_logger
-from .data_loader_base import DataLoaderBase
-
-logger = get_logger()
-
-
-class DataLoaderExcel(DataLoaderBase):
-    def __init__(self, ):
-        pass
-
-    def get_connect(self):
-        pass
-
-    def close_connect(self):
-        pass
-
-    def get_data(self, file_path: str, sheet_name: str = 0) -> pd.DataFrame:
-        df: pd.DataFrame = pd.read_excel(file_path, sheet_name=sheet_name, index_col=False, dtype=str)
-        columns = df.columns.to_list()
-        columns_new = []
-        for idx, column in enumerate(columns):
-            column = str(column)
-            if idx != 0 and "Unnamed:" in column:
-                columns_new.append(columns_new[-1])
-            else:
-                columns_new.append(column)
-        df.columns = columns_new
-        return df

+ 0 - 48
user_events/data/loader/data_loader_mysql.py

@@ -1,48 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-@author: yq
-@time: 2024/10/31
-@desc: 
-"""
-import pandas as pd
-import pymysql
-
-from commom import get_logger
-from entitys import DbConfigEntity
-from .data_loader_base import DataLoaderBase
-
-logger = get_logger()
-
-
-class DataLoaderMysql(DataLoaderBase):
-    def __init__(self, db_config: DbConfigEntity):
-        self.db_config = db_config
-        self.conn = None
-
-    def get_connect(self):
-        #  TODO 后续改成线程池
-        if self.conn == None:
-            self.conn = pymysql.connect(host=self.db_config.host, port=self.db_config.port, user=self.db_config.user,
-                                        passwd=self.db_config.passwd, db=self.db_config.db)
-        return self.conn
-
-    def close_connect(self):
-        if self.conn != None:
-            try:
-                self.conn.close()
-            except Exception as msg:
-                logger.error("关闭数据库失败:\n" + str(msg))
-            self.conn = None
-
-    def get_data(self, sql: str) -> pd.DataFrame:
-        cursor = self.get_connect().cursor()
-        cursor.execute(sql)
-        sql_results = cursor.fetchall()
-        column_desc = cursor.description
-        # 获取列名
-        columns = [column_desc[i][0] for i in range(len(column_desc))]
-        # 得到的data为二维元组,逐行取出,转化为列表,再转化为df
-        df = pd.DataFrame([list(i) for i in sql_results], columns=columns)
-        cursor.close()
-        self.close_connect()
-        return df

+ 0 - 39
user_events/data/process/data_process.py

@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-@author: yq
-@time: 2024/11/13
-@desc: 数据加工
-"""
-
-import pandas as pd
-
-from commom import f_save_train_df
-from entitys import DataProcessConfigEntity
-
-
-class DataProcess():
-
-    def __init__(self, data_process_config: DataProcessConfigEntity):
-        self._data_process_config = data_process_config
-
-    def data_fill(self, df: pd.DataFrame) -> pd.DataFrame:
-        """
-        数据填充
-        """
-        pass
-
-    def data_filter(self, df: pd.DataFrame) -> pd.DataFrame:
-        """
-        数据筛选,删除缺失率高的特征或样本
-        """
-        pass
-
-    def save(self, df):
-        """
-        加工结果固化
-        """
-        f_save_train_df("distribution", df)
-
-
-if __name__ == "__main__":
-    pass

+ 49 - 0
user_events/data/processor/DataProcessor.py

@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  数据处理
+"""
+
+
+# 数据预处理模块
+class DataPreprocessor:
+    def __init__(self, data):
+        self.data = data
+
+    def preprocess(self):
+        # 更多的数据预处理函数
+        processed_data = [
+            item for item in self.data
+            if 'user' in item and 'action' in item and 'item' in item
+        ]
+        processed_data = self.remove_duplicates(processed_data)
+        processed_data = self.fill_missing_values(processed_data)
+        processed_data = self.convert_data_types(processed_data)
+        return processed_data
+
+    def remove_duplicates(self, data):
+        # 去除重复数据
+        unique_data = []
+        seen = set()
+        for item in data:
+            identifier = (item['user'], item['action'], item['item'])
+            if identifier not in seen:
+                unique_data.append(item)
+                seen.add(identifier)
+        return unique_data
+
+    def fill_missing_values(self, data):
+        # 填充缺失值
+        for item in data:
+            if 'item' not in item:
+                item['item'] = 'unknown'
+        return data
+
+    def convert_data_types(self, data):
+        # 转换数据类型
+        for item in data:
+            item['user'] = str(item['user'])
+            item['action'] = str(item['action'])
+            item['item'] = str(item['item'])
+        return data

+ 3 - 3
user_events/data/process/__init__.py → user_events/data/processor/__init__.py

@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
 """
-@author: yq
-@time: 2024/11/1
+@author: zsc
+@time: 2024/11/18
 @desc:  数据处理
 """
 
 if __name__ == "__main__":
-    pass
+    pass

+ 46 - 0
user_events/data/storage/DataStorage.py

@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  数据处理
+"""
+
+import sqlite3
+
+
+class DataStorage:
+    """
+    数据存储模块:将数据存储到数据库中。
+    """
+
+    def __init__(self, db_path):
+        """
+        初始化DataStorage实例。
+
+        :param db_path: 数据库文件路径。
+        """
+        self.conn = sqlite3.connect(db_path)
+        self.create_table()
+
+    def create_table(self):
+        """
+        创建用于存储用户行为的数据库表。
+        """
+        create_table_query = '''
+            CREATE TABLE IF NOT EXISTS user_behavior (
+                user_id INTEGER,
+                action TEXT,
+                timestamp TEXT
+            )
+        '''
+        self.conn.execute(create_table_query)
+
+    def save_data(self, df):
+        """
+        将DataFrame中的数据保存到数据库表中。
+
+        :param df: 包含用户行为数据的DataFrame。
+        """
+        if df is not None:
+            # 将DataFrame追加到数据库表中
+            df.to_sql('user_behavior', self.conn, if_exists='append', index=False)

+ 9 - 0
user_events/data/storage/__init__.py

@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  数据存储
+"""
+
+if __name__ == "__main__":
+    pass

+ 62 - 0
user_events/main.py

@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  存量运营平台-行为客户行为挖掘及监控
+"""
+import time
+import random
+from collections import defaultdict
+import matplotlib.pyplot as plt
+
+# 主程序
+
+# main.py
+from data.collector  import DataCollector
+from data.processor  import DataProcessor
+from analyze import BehaviorAnalyzer
+from analyze import SegmentUsers
+from analyze import DetectAnomalies
+from monitor import Monitor
+from monitor import GenerateReport
+
+# 主函数
+def main():
+    # 实例化数据收集模块
+    collector = DataCollector.DataCollector()
+    raw_data = collector.collect()
+
+    # 实例化数据预处理模块
+    preprocessor = DataProcessor.DataPreprocessor(raw_data)
+    processed_data = preprocessor.preprocess()
+
+    # 实例化行为分析模块
+    analyzer = BehaviorAnalyzer.BehaviorAnalyzer(processed_data)
+    behavior_data, action_stats = analyzer.analyze()
+
+    # 实例化用户分群模块
+    segmenter = SegmentUsers.UserSegmentation(behavior_data)
+    user_segments = segmenter.segment()
+
+    # 实例化异常检测模块
+    detector = DetectAnomalies.AnomalyDetector(behavior_data)
+    anomalies = detector.detect()
+
+    # 实例化报告生成模块
+    generator = GenerateReport.ReportGenerator(processed_data, anomalies, user_segments, action_stats)
+    report = generator.generate()
+
+    # 打印报告摘要
+    print("Report Summary:")
+    print(f"Total Users: {report['total_users']}")
+    print(f"Total Actions: {report['total_actions']}")
+    print(f"Anomalies: {report['anomalies']}")
+    print(f"User Segments: {report['user_segments']}")
+
+    # 实例化实时监控模块
+    monitor = Monitor.RealTimeMonitor(processed_data)
+    print("Starting real-time monitoring...")
+    monitor.monitor()
+
+if __name__ == "__main__":
+    main()

+ 53 - 0
user_events/monitor/GenerateReport.py

@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  报告生成
+"""
+import time
+import random
+from collections import defaultdict
+import matplotlib.pyplot as plt
+
+# 报告生成模块(续)
+class ReportGenerator:
+    def __init__(self, data, anomalies, segments, action_stats):
+        self.data = data
+        self.anomalies = anomalies
+        self.segments = segments
+        self.action_stats = action_stats
+
+    def generate(self):
+        # 生成用户行为报告,并展示成图表形式
+        report = {
+            'total_users': len(set([item['user'] for item in self.data])),
+            'total_actions': len(self.data),
+            'anomalies': self.anomalies,
+            'user_segments': self.segments,
+            'action_stats': self.action_stats
+        }
+        self.plot_action_stats(self.action_stats)
+        self.plot_user_segments(self.segments)
+        return report
+
+    def plot_action_stats(self, action_stats):
+        # 生成行为统计图表
+        actions = list(action_stats.keys())
+        counts = list(action_stats.values())
+        plt.figure(figsize=(10, 6))
+        plt.bar(actions, counts, color='skyblue')
+        plt.xlabel('Actions')
+        plt.ylabel('Counts')
+        plt.title('Action Statistics')
+        plt.show()
+
+    def plot_user_segments(self, segments):
+        # 生成用户分群图表
+        segment_names = list(segments.keys())
+        segment_counts = [len(segments[segment]) for segment in segment_names]
+        plt.figure(figsize=(10, 6))
+        plt.bar(segment_names, segment_counts, color='lightgreen')
+        plt.xlabel('User Segments')
+        plt.ylabel('Number of Users')
+        plt.title('User Segmentation')
+        plt.show()

+ 19 - 0
user_events/monitor/Monitor.py

@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  监控告警
+"""
+import time
+
+# 实时监控模块
+class RealTimeMonitor:
+    def __init__(self, data_stream):
+        self.data_stream = data_stream
+
+    def monitor(self):
+        # 模拟实时监控数据流
+        for data in self.data_stream:
+            print(f"Monitoring: {data}")
+            # 这里可以添加实时处理逻辑
+            time.sleep(0.5)  # 模拟实时数据流的时间间隔

+ 5 - 9
user_events/monitor/__init__.py

@@ -1,13 +1,9 @@
-# -*- coding:utf-8 -*-
+# -*- coding: utf-8 -*-
 """
-@author: yq
-@time: 2022/10/24
-@desc: 指标监控
+@author: zsc
+@time: 2024/11/18
+@desc:  监控
 """
 
-from .monitor_metric import MonitorMetric
-
-__all__ = ['MonitorMetric']
-
 if __name__ == "__main__":
-    pass
+    pass

+ 0 - 41
user_events/monitor/monitor_metric.py

@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-@author: yq
-@time: 2024/11/1
-@desc: 监控报告
-"""
-import threading
-from typing import Dict
-
-from entitys import MonitorMetricConfigEntity, MetricFucEntity
-from .report_generate import Report
-
-
-class MonitorMetric():
-
-    def __init__(self, monitor_metric_config_path: str):
-        self._monitor_metric_config = MonitorMetricConfigEntity.from_config(monitor_metric_config_path)
-        self.lock = threading.Lock()
-        self._metric_value_dict: Dict[str, MetricFucEntity] = {}
-
-    @property
-    def metric_value_dict(self):
-        return self._metric_value_dict
-
-    def _update_metric_value_dict(self, key, value):
-        with self.lock:
-            self._metric_value_dict[key] = value
-
-    #  TODO 多线程计算指标
-    def calculate_metric(self, *args, **kwargs):
-        metric_dict = self._monitor_metric_config.metric_dict
-        for metric_code, metric_clazz in metric_dict.items():
-            metric_value = metric_clazz.calculate(*args, **kwargs)
-            self._update_metric_value_dict(metric_code, metric_value)
-
-    def generate_report(self):
-        Report.generate_report(self._metric_value_dict, self._monitor_metric_config.template_path)
-
-
-if __name__ == "__main__":
-    pass

+ 0 - 191
user_events/monitor/report_generate.py

@@ -1,191 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-@author: yq
-@time: 2024/11/8
-@desc: 
-"""
-import os
-from typing import Dict
-
-from docx import Document
-from docx.enum.table import WD_ALIGN_VERTICAL
-from docx.enum.text import WD_ALIGN_PARAGRAPH
-from docx.oxml import OxmlElement
-from docx.oxml.ns import qn
-from docx.shared import Inches, Cm
-
-from commom import GeneralException, f_get_datetime
-from config import BaseConfig
-from entitys import MetricFucEntity
-from enums import ResultCodesEnum, PlaceholderPrefixEnum
-
-
-class Report():
-
-    @staticmethod
-    def _set_cell_width(cell):
-        text = cell.text
-        if len(text) >= 10:
-            cell.width = Cm(2)
-        elif len(text) >= 15:
-            cell.width = Cm(2.5)
-        elif len(text) >= 25:
-            cell.width = Cm(3)
-        else:
-            cell.width = Cm(1.5)
-
-    @staticmethod
-    def _set_cell_format(cell):
-        cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
-        cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
-
-    @staticmethod
-    def _merge_cell_column(pre_cell, curr_cell):
-        if curr_cell.text == pre_cell.text:
-            column_name = curr_cell.text
-            pre_cell.merge(curr_cell)
-            pre_cell.text = column_name
-            for run in pre_cell.paragraphs[0].runs:
-                run.bold = True
-            Report._set_cell_format(pre_cell)
-            Report._set_cell_width(pre_cell)
-
-    @staticmethod
-    def _set_table_singleBoard(table):
-        # 将table 的所有单元格四个边设置为 0.5 镑, 黑色, 实线
-
-        def _set_table_boarder(table, **kwargs):
-            """
-            Set table`s border
-            Usage:
-            set_table_border(
-                cell,
-                top={"sz": 12, "val": "single", "color": "#FF0000"},
-                bottom={"sz": 12, "color": "#00FF00", "val": "single"},
-                left={"sz": 24, "val": "dashed"},
-                right={"sz": 12, "val": "dashed"},
-            )
-            """
-            borders = OxmlElement('w:tblBorders')
-            for tag in ('bottom', 'top', 'left', 'right', 'insideV', 'insideH'):
-                edge_data = kwargs.get(tag)
-                if edge_data:
-                    any_border = OxmlElement(f'w:{tag}')
-                    for key in ["sz", "val", "color", "space", "shadow"]:
-                        if key in edge_data:
-                            any_border.set(qn(f'w:{key}'), str(edge_data[key]))
-                    borders.append(any_border)
-                    table._tbl.tblPr.append(borders)
-
-        return _set_table_boarder(
-            table,
-            top={"sz": 4, "val": "single", "color": "#000000"},
-            bottom={"sz": 4, "val": "single", "color": "#000000"},
-            left={"sz": 4, "val": "single", "color": "#000000"},
-            right={"sz": 4, "val": "single", "color": "#000000"},
-            insideV={"sz": 4, "val": "single", "color": "#000000"},
-            insideH={"sz": 4, "val": "single", "color": "#000000"}
-        )
-
-    @staticmethod
-    def _get_placeholder(placeholder_prefix_enum: PlaceholderPrefixEnum, metric_code: str):
-        return "{{" + f"{placeholder_prefix_enum.value}{metric_code}" + "}}"
-
-    @staticmethod
-    def _fill_value_placeholder(doc: Document, metric_value_dict: Dict[str, MetricFucEntity]):
-        # 替换指标
-        for paragraph in doc.paragraphs:
-            text = paragraph.text
-            for metric_code, metric_fuc_entity in metric_value_dict.items():
-                placeholder = Report._get_placeholder(PlaceholderPrefixEnum.VALUE, metric_code)
-                metric_value = metric_fuc_entity.value
-                if metric_value is None:
-                    continue
-                text = text.replace(placeholder, metric_value)
-            # 段落中多个runs时执行,最后一个run改成替换好的文本,其他run置空
-            if len(paragraph.runs[:-1]) > 0:
-                for run in paragraph.runs[:-1]:
-                    run.text = ''
-                paragraph.runs[-1].text = text
-
-    @staticmethod
-    def _fill_table_placeholder(doc: Document, metric_value_dict: Dict[str, MetricFucEntity]):
-        # 替换表格
-        for paragraph in doc.paragraphs:
-            for metric_code, metric_fuc_entity in metric_value_dict.items():
-                placeholder = Report._get_placeholder(PlaceholderPrefixEnum.TABLE, metric_code)
-                metric_table = metric_fuc_entity.table
-                if metric_table is None:
-                    continue
-                if not placeholder in paragraph.text:
-                    continue
-                # 清除占位符
-                for run in paragraph.runs:
-                    run.text = run.text.replace(placeholder, "")
-                table = doc.add_table(rows=metric_table.shape[0] + 1, cols=metric_table.shape[1])
-                table.alignment = WD_ALIGN_PARAGRAPH.CENTER
-                paragraph._element.addnext(table._element)
-                # 列名
-                for column_idx, column_name in enumerate(metric_table.columns):
-                    cell = table.cell(0, column_idx)
-                    cell.text = str(column_name)
-                    for run in cell.paragraphs[0].runs:
-                        run.bold = True
-                    Report._set_cell_format(cell)
-                    Report._set_cell_width(cell)
-                    # 合并相同的列名
-                    if column_idx != 0 and BaseConfig.merge_table_column:
-                        pre_cell = table.cell(0, column_idx - 1)
-                        Report._merge_cell_column(pre_cell, cell)
-                # 值
-                for row_idx, row in metric_table.iterrows():
-                    for column_idx, value in enumerate(row):
-                        cell = table.cell(row_idx + 1, column_idx)
-                        cell.text = str(value)
-                        Report._set_cell_format(cell)
-                        Report._set_cell_width(cell)
-                        # 合并第一行数据也为列的情况
-                        if row_idx == 0:
-                            Report._merge_cell_column(table.cell(0, column_idx), cell)
-
-                Report._set_table_singleBoard(table)
-                # 禁止自动调整表格
-                if len(metric_table.columns) <= 12:
-                    table.autofit = False
-
-    @staticmethod
-    def _fill_image_placeholder(doc: Document, metric_value_dict: Dict[str, MetricFucEntity]):
-        # 替换图片
-        for paragraph in doc.paragraphs:
-            for metric_code, metric_fuc_entity in metric_value_dict.items():
-                placeholder = Report._get_placeholder(PlaceholderPrefixEnum.IMAGE, metric_code)
-                image_path = metric_fuc_entity.image_path
-                if image_path is None:
-                    continue
-                if not placeholder in paragraph.text:
-                    continue
-                if not os.path.exists(image_path):
-                    raise GeneralException(ResultCodesEnum.NOT_FOUND, message=f"文件【{image_path}】不存在")
-                # 清除占位符
-                for run in paragraph.runs:
-                    if placeholder not in run.text:
-                        continue
-                    run.text = run.text.replace(placeholder, "")
-                    run.add_picture(image_path, width=Inches(6))
-
-    @staticmethod
-    def generate_report(metric_value_dict: Dict[str, MetricFucEntity], template_path: str):
-        if os.path.exists(template_path):
-            doc = Document(template_path)
-        else:
-            raise GeneralException(ResultCodesEnum.NOT_FOUND, message=f"监控模板文件【{template_path}】不存在")
-
-        Report._fill_value_placeholder(doc, metric_value_dict)
-        Report._fill_table_placeholder(doc, metric_value_dict)
-        Report._fill_image_placeholder(doc, metric_value_dict)
-        new_path = template_path.replace(".docx", f"{f_get_datetime()}.docx")
-        doc.save(f"./{new_path}")
-
-
-if __name__ == "__main__":
-    pass

+ 48 - 0
user_events/visual/Visualizer.py

@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  可视化展示
+"""
+
+# 可视化展示模块
+import matplotlib.pyplot as plt
+
+
+class Visualizer:
+    """
+    可视化展示模块:将分析结果以图表形式展示。
+    """
+
+    def generate_statistics(self, action_counts):
+        """
+        生成统计数据图表:展示用户行为的频率分布。
+
+        :param action_counts: 包含行为频率的Series对象。
+        """
+        # 使用matplotlib生成柱状图
+        action_counts.plot(kind='bar', color='skyblue')
+        plt.title('User Action Frequency Distribution')
+        plt.xlabel('Action Types')
+        plt.ylabel('Frequency')
+        plt.xticks(rotation=45)
+        plt.tight_layout()
+        plt.show()
+
+    def draw_heatmap(self, df):
+        """
+        绘制热力图:展示用户行为的二维频率分布。
+
+        :param df: 包含用户行为数据的DataFrame。
+        """
+        # 此处可以添加绘制热力图的逻辑,例如使用seaborn库
+        pass
+
+    def plot_user_journey(self, df):
+        """
+        绘制用户旅程图:展示用户在应用中的行为路径。
+
+        :param df: 包含用户行为数据的DataFrame。
+        """
+        # 此处可以添加绘制用户旅程图的逻辑
+        pass

+ 9 - 0
user_events/visual/__init__.py

@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@author: zsc
+@time: 2024/11/18
+@desc:  可视化
+"""
+
+if __name__ == "__main__":
+    pass