yq vor 4 Monaten
Ursprung
Commit
217a860890

+ 8 - 1
.gitignore

@@ -57,4 +57,11 @@ docs/_build/
 
 # PyBuilder
 target/
-
+/.idea/
+/logs
+/cache
+*/image
+*/~$*
+*.ipynb
+/flagged
+.gradio

+ 11 - 0
commom/__init__.py

@@ -0,0 +1,11 @@
+# -*- coding:utf-8 -*-
+"""
+@author: yq
+@time: 2021/11/9
+@desc: 
+"""
+from .llm_call import call_llm, f_file_upload
+from .user_exceptions import GeneralException
+from .utils import f_get_date, f_get_datetime, f_get_save_path
+
+__all__ = ['GeneralException', 'f_get_date', 'f_get_datetime', 'f_get_save_path', 'call_llm', 'f_file_upload']

+ 69 - 0
commom/llm_call.py

@@ -0,0 +1,69 @@
+# -*- coding:utf-8 -*-
+"""
+@author: isaacqyang
+@time: 2023/9/19
+@desc: 
+"""
+import time
+
+import requests
+
+from config import BaseConfig
+from .logger import get_logger
+
+logger_error = get_logger("error")
+
+
+def f_file_upload(file_path: str):
+    req_head = {
+        "Authorization": BaseConfig.token,
+    }
+    with open(file_path, 'rb') as file:
+        res = requests.post(BaseConfig.file_upload_url, headers=req_head, files={'file': file})
+    res_json = res.json()
+    print(res_json)
+    return res_json['data']['id']
+
+
+def call_llm(prompt: str, content_type="text"):
+    req_head = {
+        "Authorization": BaseConfig.token,
+        "Content-Type": "application/json",
+    }
+    req_data = {
+        "bot_id": BaseConfig.bot_id,
+        "user_id": "test",
+        "stream": False,
+        "auto_save_history": True,
+        "additional_messages": [
+            {
+                "role": "user",
+                "content": prompt,
+                "content_type": content_type
+            }
+        ]
+    }
+
+    res_create = requests.post(" https://api.coze.cn/v1/conversation/create",
+                               headers=req_head)
+    coversition_id = res_create.json()["data"]["id"]
+    res_chat = requests.post(f" https://api.coze.cn/v3/chat?conversation_id={coversition_id}",
+                             headers=req_head, json=req_data)
+    chat_id = res_chat.json()["data"]["id"]
+    while True:
+        res_retrieve = requests.get(
+            f" https://api.coze.cn/v3/chat/retrieve?chat_id={chat_id}&conversation_id={coversition_id}",
+            headers=req_head)
+        # print(res_retrieve.json()["data"]["status"])
+        status = res_retrieve.json()["data"]["status"]
+        if status == "completed":
+            res_message = requests.get(
+                f" https://api.coze.cn/v3/chat/message/list?chat_id={chat_id}&conversation_id={coversition_id}",
+                headers=req_head)
+            res_json = res_message.json()
+            print(res_json)
+            data = res_json['data']
+            for msg in data:
+                if msg["type"] == "answer":
+                    return msg["content"]
+        time.sleep(1)

+ 58 - 0
commom/logger.py

@@ -0,0 +1,58 @@
+# -*- coding:utf-8 -*-
+"""
+@author: isaacqyang
+@time: 2022/8/29
+@desc:
+"""
+
+import datetime
+import logging
+import logging.handlers
+import os
+import threading
+import time
+from os.path import dirname, realpath
+
+import pytz
+
+
+def my_time(*args):
+    return time.strptime(datetime.datetime.now(pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S"),
+                         "%Y-%m-%d %H:%M:%S")
+
+
+_instance_lock = threading.Lock()
+logger_map = {}
+
+
+def get_logger(logger_name: str = None) -> logging.Logger:
+    if logger_name is None:
+        logger_name = "mylog"
+    if logger_name in logger_map.keys():
+        return logger_map.get(logger_name)
+    with _instance_lock:
+        if logger_name in logger_map.keys():
+            return logger_map.get(logger_name)
+        _logger = logging.Logger(logger_name)
+        _logger.setLevel(logging.INFO)
+        formatter = logging.Formatter(
+            "[%(asctime)s] [%(levelname)s] [%(threadName)s] [%(filename)s] [func:%(funcName)s line:%(lineno)d]\n %(message)s")
+
+        formatter.converter = my_time
+
+        log_path = os.path.join(dirname(dirname(realpath(__file__))), "logs")
+        filename = os.path.join(log_path, f"{logger_name}.log")
+
+        if not os.path.exists(dirname(filename)):
+            os.makedirs(dirname(filename))
+        handler = logging.handlers.TimedRotatingFileHandler(filename, when="MIDNIGHT", interval=7, backupCount=4,
+                                                            encoding="utf8", atTime=datetime.time(0, 0, 0, 0))
+        console_handler = logging.StreamHandler()
+        print(f"日志路径:{filename}")
+        handler.setFormatter(formatter)
+        console_handler.setFormatter(formatter)
+        _logger.addHandler(handler)
+        _logger.addHandler(console_handler)
+        logger_map[logger_name] = _logger
+
+        return _logger

+ 16 - 0
commom/user_exceptions.py

@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+"""
+@author: yq
+@time: 2024/11/8
+@desc: 自定义异常
+"""
+from enums import ResultCodesEnum
+
+
+class GeneralException(Exception):
+    def __init__(self, result_codes_enum: ResultCodesEnum, *args, message: str = "", **kwargs):
+        self.message = message
+        self.result_codes_enum = result_codes_enum
+
+    def __str__(self):
+        return f"codes: {self.result_codes_enum.code} {self.result_codes_enum.message} message: {self.message}"

+ 30 - 0
commom/utils.py

@@ -0,0 +1,30 @@
+# -*- coding:utf-8 -*-
+"""
+@author: yq
+@time: 2023/12/28
+@desc:  各种工具类
+"""
+
+import datetime
+import os
+
+import pytz
+
+
+def f_get_date(offset: int = 0, connect: str = "-") -> str:
+    current_date = datetime.datetime.now(pytz.timezone("Asia/Shanghai")).date() + datetime.timedelta(days=offset)
+    return current_date.strftime(f"%Y{connect}%m{connect}%d")
+
+
+def f_get_datetime(offset: int = 0, connect: str = "_") -> str:
+    current_date = datetime.datetime.now(pytz.timezone("Asia/Shanghai")) + datetime.timedelta(days=offset)
+    return current_date.strftime(f"%Y{connect}%m{connect}%d{connect}%H{connect}%M{connect}%S")
+
+
+base_dir = os.path.join(".", "cache", f"{f_get_datetime()}")
+os.makedirs(base_dir, exist_ok=True)
+
+
+def f_get_save_path(file_name: str, sub_path=""):
+    os.makedirs(os.path.join(base_dir, sub_path), exist_ok=True)
+    return os.path.join(base_dir, sub_path, file_name)

+ 10 - 0
config/__init__.py

@@ -0,0 +1,10 @@
+# -*- coding:utf-8 -*-
+"""
+@author: isaacqyang
+@time: 2022/10/24
+@desc: 
+"""
+
+from .base_config import BaseConfig
+
+__all__ = ['BaseConfig']

+ 16 - 0
config/base_config.py

@@ -0,0 +1,16 @@
+# -*- coding:utf-8 -*-
+"""
+@author: isaacqyang
+@time: 2022/10/24
+@desc: 
+"""
+
+class BaseConfig:
+    bot_url = "https://api.coze.cn/open_api/v2/chat"
+    # bot_url = "https://api.coze.cn/v3/chat"
+    token = "Bearer pat_HNBYQOWE5h4r1tzXi8S2PuY4ddoVRH3DpTbE3NsYBjtcWHTYw5ffrVmKPh26hSLW"
+    bot_id = "7397344489205153807"
+    file_upload_url = "https://api.coze.cn/v1/files/upload"
+
+
+

+ 9 - 0
enums/__init__.py

@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@author: yq
+@time: 2024/10/30
+@desc: 枚举值
+"""
+from .result_codes_enum import ResultCodesEnum
+
+__all__ = ['ResultCodesEnum']

+ 25 - 0
enums/result_codes_enum.py

@@ -0,0 +1,25 @@
+# -*- coding:utf-8 -*-
+"""
+@author: yq
+@time: 2023/9/18
+@desc: 结果状态枚举值
+"""
+
+from enum import Enum
+
+
+class ResultCodesEnum(Enum):
+
+    SUCCESS = {"code": "200", "message": "success"}
+    SYSTEM_ERROR = {"code": "1001", "message": "system error"}
+    TIME_OUT = {"code": "1002", "message": "time out"}
+    ILLEGAL_PARAMS = {"code": "1003", "message": "illegal params"}
+    NOT_FOUND = {"code": "1004", "message": "not found"}
+
+    @property
+    def message(self):
+        return self.value['message']
+
+    @property
+    def code(self):
+        return self.value['code']

+ 9 - 0
prompt/__init__.py

@@ -0,0 +1,9 @@
+# -*- coding:utf-8 -*-
+"""
+@author: isaacqyang
+@time: 2023/9/19
+@desc: 
+"""
+from .prompt import f_get_prompt_parse_node, f_get_prompt_parse_flow
+
+__all__ = ['f_get_prompt_parse_node','f_get_prompt_parse_flow']

+ 58 - 0
prompt/prompt.py

@@ -0,0 +1,58 @@
+# -*- coding:utf-8 -*-
+"""
+@author: isaacqyang
+@time: 2023/9/19
+@desc: 
+"""
+
+prompt_parse_node = """
+# 角色
+你是一个专业的Python代码生成器,能够根据给定的规则集名称和规则,用Python语言生成功能函数代码。
+
+```
+示例:
+规则集名称:通用规则
+规则集:
+规则1: 变量:年龄 age 逻辑:年龄小于18或大于等于65 输出:1
+规则2: 变量:欠税总额 qsze 逻辑:欠税总额大于500 输出:3
+规则3: 变量:非银行机构未结清贷款机构数 fyjgwjqs 逻辑:非银行机构未结清贷款机构数大于10 输出:2
+输出:
+def handle_general_rules(data:dict):
+    # 通用规则
+    age = data.get("age")
+    qsze = data.get("qsze")
+    fyjgwjqs = data.get("fyjgwjqs")
+    if age is not None:
+        if age < 18 or age >= 65:
+            return 1
+    if qsze is not None:
+        if qsze > 500:
+            return 3
+    if fyjgwjqs is not None:
+        if fyjgwjqs > 10:
+            return 2
+```
+
+待处理规则:
+规则集名称:{rules_name}
+规则集:
+{rules}
+请根据```内的示例以及待处理规则中的变量、计算逻辑及输出,用Python语言生成功能函数代码。
+返回结果要求:
+1、只返回功能函数代码,不要多余的输出。
+2、代码逻辑应严格按照待处理要求中的条件,不要自行添加多余的逻辑。
+3、代码的语法要符合python的语法规范,返回的代码应该是可执行的。
+"""
+
+prompt_parse_flow = """
+# 角色
+请把图中的流程图转换为python代码
+"""
+
+
+def f_get_prompt_parse_node(rules_name: str, rules: str):
+    return prompt_parse_node.replace("{rules_name}", rules_name).replace("{rules}", rules)
+
+
+def f_get_prompt_parse_flow():
+    return prompt_parse_flow

+ 95 - 0
prompt/prompt_example.txt

@@ -0,0 +1,95 @@
+示例1:
+规章制度内容:
+第五章  罚则第二十条 员工未经批准擅自休假、无正当理由超假或延长因公外出时间的,按旷工处理。第二十一条 员工开虚假证明休病假或不能提供医院证明材料的,其休假时间一律按旷工处理。经核实,员工提供虚假证明材料请病假的,视为严重违纪,单位有权单方解除劳动合同。
+问题和答案对:
+1、{"问题":"如果员工没有得到批准就休假,或者没有合理的理由就超假,会有什么后果?","答案":"员工未经批准擅自休假、无正当理由超假或延长因公外出时间的,按旷工处理。"}
+2、{"问题":"员工如果使用虚假的病假证明,公司会怎么处理?","答案":" 员工开虚假证明休病假或不能提供医院证明材料的,其休假时间一律按旷工处理。经核实,员工提供虚假证明材料请病假的,视为严重违纪,单位有权单方解除劳动合同。"}
+
+示例2:
+规章制度内容:
+第二十一条 车辆事故及损失处理。(一)驾驶员违反交通规则被罚款,一律不予报销。(二)若发生交通事故,应及时报告车辆管理部门,写出事故经过及损失情况报告。造成责任事故,须由驾驶员写出书面检查,并视情节进行赔偿,对责任人给予相应处理。赔偿比例系指扣除保险公司赔付后实际产生的车辆损失和赔偿金额。1.全责的赔偿 50%;扣发责任人日常绩效至事故处理完毕。2.主要责任的赔偿 40%;扣发责任人日常绩效的 50%至事故处理完毕。3.双方同责的赔偿 30%;扣发责任人日常绩效的 30%至事故处理完毕。4.次要责任的赔偿 20%;扣发责任人月考核工资的20%至事故处理完毕。(三)若对方负全责或意外事故,修车期间驾驶员不承担因修车给本行造成的间接损失;其余情况,修车时间内驾驶员应承担因修车造成的间接损失。
+问题和答案对:
+1、{"问题":"驾驶员违反交通规则被罚款怎么处理?","答案":"驾驶员违反交通规则被罚款,一律不予报销"}
+2、{"问题":"若发生交通事故应该怎么做?","答案":"若发生交通事故,应及时报告车辆管理部门,写出事故经过及损失情况报告。造成责任事故,须由驾驶员写出书面检查。"}
+3、{"问题":"使用公司车辆发生责任事故时的赔偿比例?","答案":"赔偿比例系指扣除保险公司赔付后实际产生的车辆损失和赔偿金额。1.全责的赔偿 50%;扣发责任人日常绩效至事故处理完毕。2.主要责任的赔偿 40%;扣发责任人日常绩效的 50%至事故处理完毕。3.双方同责的赔偿 30%;扣发责任人日常绩效的 30%至事故处理完毕。4.次要责任的赔偿 20%;扣发责任人月考核工资的20%至事故处理完毕。"}
+4、{"问题":"对方负全责或意外事驾驶员需要赔偿吗?","答案":"若对方负全责或意外事故,修车期间驾驶员不承担因修车给本行造成的间接损失;其余情况,修车时间内驾驶员应承担因修车造成的间接损失"}
+
+待处理的规章制度内容:
+"""
+第三章  车辆编制第八条 原则上配备的商务接待车不超过 2 辆,业务用车、机要通信车、应急保障车可视情况配备。第九条 根据业务发展规模和公司财务承受状况,公司车辆编制一年一定。第十条 以资抵债形成的车辆,原则上不得自用;如确需自用,在符合车辆编制标准的前提下,可按新购车辆管理的要求和程序报批。
+"""
+
+生成要求:
+1、输出格式要严格按照上面示例的格式。
+2、生成的问题要贴近实际人类的提问风格。
+3、生成的问题不能是固定的文本开头,或者是固定的模式提问。
+4、生成的问题要有很强的随机性。
+5、生成的问题要确保多样性。
+6、生成的问题要和"""内的规章制度内容相关。
+7、和问题相关的答案必须是"""内的规章制度内容。
+8、尽可能的生成多的问题。
+
+请根据示例、"""内的规章制度内容和8条生成要求的内容,生成问题和答案对。
+
+[2024-08-08 14:19:49,159] [INFO] [MainThread] [prompt.py] [func:parse_gen_ques_response_text line:105]
+ 1、{"问题":"公司商务接待车一般配备几辆?","答案":"原则上配备的商务接待车不超过 2 辆"}
+2、{"问题":"公司车辆编制多久确定一次?","答案":"根据业务发展规模和公司财务承受状况,公司车辆编制一年一定"}
+3、{"问题":"以资抵债形成的车辆能否自用?","答案":"以资抵债形成的车辆,原则上不得自用;如确需自用,在符合车辆编制标准的前提下,可按新购车辆管理的要求和程序报批"}
+4、{"问题":"业务用车可以随意配备吗?","答案":"业务用车、机要通信车、应急保障车可视情况配备"}
+5、{"问题":"不符合车辆编制标准时,以资抵债形成的车辆能自用吗?","答案":"以资抵债形成的车辆,原则上不得自用;如确需自用,在符合车辆编制标准的前提下,可按新购车辆管理的要求和程序报批"}
+6、{"问题":"公司财务状况对车辆编制有影响吗?","答案":"根据业务发展规模和公司财务承受状况,公司车辆编制一年一定"}
+7、{"问题":"应急保障车是怎么配备的?","答案":"业务用车、机要通信车、应急保障车可视情况配备"}
+[2024-08-08 14:19:57,082] [INFO] [MainThread] [prompt.py] [func:parse_call_ans_response_text line:123]
+ =====
+   - 👀 问题: 公司商务接待车一般配备几辆?
+   - 🌟 答案: 原则上配备的商务接待车不超过 2 辆,业务用车、机要通信车、应急保障车可视情况配备。
+=====
+[2024-08-08 14:19:57,082] [INFO] [MainThread] [main.py] [func:<module> line:102]
+
+示例1:
+规章制度内容:
+第五章  罚则第二十条 员工未经批准擅自休假、无正当理由超假或延长因公外出时间的,按旷工处理。第二十一条 员工开虚假证明休病假或不能提供医院证明材料的,其休假时间一律按旷工处理。经核实,员工提供虚假证明材料请病假的,视为严重违纪,单位有权单方解除劳动合同。
+问题:
+如果员工没有得到批准就休假,或者没有合理的理由就超假,会有什么后果?
+答案:
+员工未经批准擅自休假、无正当理由超假或延长因公外出时间的,按旷工处理。
+判断结果:
+{"结果":"正确", "原因":""}
+
+
+示例2:
+规章制度内容:
+第二十一条 车辆事故及损失处理。(一)驾驶员违反交通规则被罚款,一律不予报销。(二)若发生交通事故,应及时报告车辆管理部门,写出事故经过及损失情况报告。造成责任事故,须由驾驶员写出书面检查,并视情节进行赔偿,对责任人给予相应处理。赔偿比例系指扣除保险公司赔付后实际产生的车辆损失和赔偿金额。1.全责的赔偿 50%;扣发责任人日常绩效至事故处理完毕。2.主要责任的赔偿 40%;扣发责任人日常绩效的 50%至事故处理完毕。3.双方同责的赔偿 30%;扣发责任人日常绩效的 30%至事故处理完毕。4.次要责任的赔偿 20%;扣发责任人月考核工资的20%至事故处理完毕。(三)若对方负全责或意外事故,修车期间驾驶员不承担因修车给本行造成的间接损失;其余情况,修车时间内驾驶员应承担因修车造成的间接损失。
+问题:
+使用公司车辆发生责任事故时的赔偿比例?
+答案:
+1.全责的赔偿 50%;扣发责任人日常绩效至事故处理完毕。2.主要责任的赔偿 30%;扣发责任人日常绩效的 50%至事故处理完毕。3.双方同责的赔偿 30%;扣发责任人日常绩效的 30%至事故处理完毕。4.次要责任的赔偿 20%;扣发责任人月考核工资的20%至事故处理完毕。
+判断结果:
+{"结果":"错误", "原因":"主要责任的赔偿应该是40%"}
+
+规章制度内容:
+"""
+第三章  车辆编制第八条 原则上配备的商务接待车不超过 2 辆,业务用车、机要通信车、应急保障车可视情况配备。第九条 根据业务发展规模和公司财务承受状况,公司车辆编制一年一定。第十条 以资抵债形成的车辆,原则上不得自用;如确需自用,在符合车辆编制标准的前提下,可按新购车辆管理的要求和程序报批。
+"""
+
+问题:
+"""
+公司商务接待车一般配备几辆?
+"""
+
+答案:
+"""
+ 原则上配备的商务接待车不超过 2 辆,业务用车、机要通信车、应急保障车可视情况配备。
+=====
+"""
+
+输出要求:
+1、输出格式要严格按照上面示例的格式,"结果"字段的取值只能是"正确"或者"错误"。
+2、必须判断答案是否严格遵从规章制度内容。
+3、如果错误需要给出错误的原因。
+
+
+请根据示例、"""内的规章制度内容、"""内的问题和"""内的答案和输出要求,判断答案是否正确。
+
+[2024-08-08 14:20:01,285] [INFO] [MainThread] [prompt.py] [func:parse_judge_ans_response_text line:130]
+ {"结果":"正确", "原因":""}

+ 15 - 0
requirements.txt

@@ -0,0 +1,15 @@
+pymysql==1.0.2
+python-docx==0.8.11
+xlrd==1.2.0
+scorecardpy==0.1.9.7
+dataframe_image==0.1.14
+matplotlib==3.3.4
+numpy==1.19.5
+pandas==1.1.5
+scikit-learn==0.24.2
+pyhive==0.7.0
+thrift==0.21.0
+thrift-sasl==0.4.3
+seaborn==0.11.2
+contextvars==2.4
+tqdm==4.64.1

+ 31 - 0
start.sh

@@ -0,0 +1,31 @@
+#!/bin/bash
+
+source activate easy_ml
+
+PATH_APP=$(pwd)
+
+function get_pid() {
+  APP_PID=$(ps -ef | grep "python $PATH_APP/app.py" | grep -v grep | awk '{print $2}')
+}
+
+function kill_app() {
+  if [ -n $APP_PID ]; then
+    for v in $APP_PID; do
+      echo $(date +%F%n%T) "开始杀死已有进程: $v"
+      kill -9 $v
+    done
+  fi
+}
+
+function start_app() {
+  echo $(date +%F%n%T) "开始启动 app..."
+  PYTHONIOENCODING=utf-8 nohup python $PATH_APP/app.py > $PATH_APP/nohup.out 2>&1 &
+  sleep 3
+  echo $(tail -50 $PATH_APP/nohup.out)
+  echo "启动完成..."
+  echo "日志请查看 $PATH_APP/nohup.out"
+}
+
+get_pid
+kill_app
+start_app

+ 84 - 0
strategy_parse.py

@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+"""
+@author: yq
+@time: 2024/12/18
+@desc: 策略流节点解析
+"""
+import json
+import re
+import time
+
+import pandas as pd
+from PIL import Image
+from openpyxl import load_workbook
+
+from commom import f_get_save_path, call_llm, f_file_upload, GeneralException
+from enums import ResultCodesEnum
+from prompt import f_get_prompt_parse_node, f_get_prompt_parse_flow
+
+
+def _f_parse_flow(ws):
+    image = ws._images[0]
+    img = Image.open(image.ref).convert("RGB")
+    save_path = f_get_save_path("流程图.png")
+    img.save(save_path)
+    time.sleep(1)
+    file_id = f_file_upload(save_path)
+    prompt = f_get_prompt_parse_flow()
+    prompt = [
+        {
+            "type": "text",
+            "text": prompt
+        },
+        {
+            "type": "image",
+            "file_id": file_id
+        }
+    ]
+    prompt = json.dumps(prompt, ensure_ascii=False)
+    print(prompt)
+    llm_answer = call_llm(prompt, "object_string")
+    print(llm_answer)
+    save_path = f_get_save_path("flow.py")
+    with open(save_path, mode="w", encoding="utf8") as f:
+        f.write(llm_answer)
+
+
+def _f_parse_node(df: pd.DataFrame, sheet_name):
+    rules = ""
+    for idx, row in df.iterrows():
+        var_name = row["变量"]
+        var_name = var_name.replace("\n", " ")
+        rule_content = row["逻辑"]
+        rule_out = row["输出"]
+        rules = f"{rules}规则{idx + 1}: 变量:{var_name} 逻辑:{rule_content} 输出:{rule_out}\n"
+
+    prompt = f_get_prompt_parse_node(sheet_name, rules)
+    print(prompt)
+    llm_answer = call_llm(prompt)
+    llm_answer = re.findall(r"```python\n(.*)\n```", llm_answer, flags=re.DOTALL)[0]
+    func_name = re.findall(r"def (.*)\(data", llm_answer)[0]
+    save_path = f_get_save_path(f"{func_name}.py", "node")
+    print(llm_answer)
+    with open(save_path, mode="w", encoding="utf8") as f:
+        f.write(llm_answer)
+
+
+def f_parse_strategy(file_path):
+    wb = load_workbook(file_path)
+    excel = pd.ExcelFile(file_path)
+    sheet_names = excel.sheet_names
+    if "流程图" not in sheet_names:
+        GeneralException(ResultCodesEnum.NOT_FOUND, message=f"sheet【流程图】不存在")
+    for sheet_name in sheet_names:
+        if sheet_name == "流程图":
+            _f_parse_flow(wb["流程图"])
+            continue
+        df = excel.parse(sheet_name=sheet_name)
+        _f_parse_node(df, sheet_name)
+    wb.close()
+    excel.close()
+
+
+if __name__ == "__main__":
+    f_parse_strategy("./cache/策略节点配置demo.xlsx")