13 Incheckningar 2c968856a3 ... a6f90061ec

Upphovsman SHA1 Meddelande Datum
  Sherlock a6f90061ec 修改特征字段配置文件 2 dagar sedan
  Sherlock 23c43dafc2 fix: 修复 get_product_by_id 的分页开销 2 dagar sedan
  Sherlock b273bcc9e1 fix: 统一空列表时返回空 DataFrame 而非 None 2 dagar sedan
  Sherlock 66f694e335 fix: 修复所有 IN 子句的 SQL 注入风险 2 dagar sedan
  Sherlock a8e9e502a3 chore: 删除废弃的注释代码 2 dagar sedan
  Sherlock dc63195bc8 fix: 统一 get_cust_list 和 get_product_from_order 的接口 2 dagar sedan
  Sherlock 5726320603 fix: 修复 connect_database 和 fetch 方法的错误处理 2 dagar sedan
  Sherlock 539b1851b5 fix: 修复 load_data_with_page 的空值和空结果集处理 2 dagar sedan
  Sherlock 331b6e101f fix: 修复 load_data_with_page 的 count 查询和 params 副作用 2 dagar sedan
  Sherlock e5d416fd08 docs: add database bugfix implementation plan 2 dagar sedan
  Sherlock 1d3091821a docs: 将数据库修复设计文档改为中文 2 dagar sedan
  Sherlock cc0bf03fbc docs: fix spec issues - clarify Fix 3 behavior change, Fix 4 approach, params mutation, and fix ordering 2 dagar sedan
  Sherlock 54c69bed9f docs: add database bugfix design spec 2 dagar sedan

+ 56 - 74
database/dao/mysql_dao.py

@@ -1,5 +1,5 @@
 from database import MySqlDatabaseHelper
-from sqlalchemy import text
+from sqlalchemy import text, bindparam
 import pandas as pd
 
 class MySqlDao:
@@ -91,87 +91,82 @@ class MySqlDao:
     
     def get_product_by_id(self, city_uuid, product_id):
         """根据city_uuid 和 product_id 从表中获取拼柜信息"""
-        query = f"""
+        query = text(f"""
             SELECT *
             FROM {self._product_tablename}
             WHERE city_uuid = :city_uuid
             AND product_code = :product_id
-        """
+        """)
         params = {"city_uuid": city_uuid, "product_id": product_id}
-        data = self.db_helper.load_data_with_page(query, params)
-        
-        return data
+        result = self.db_helper.fetch_one(query, params)
+        return pd.DataFrame([dict(result._mapping)] if result else [])
     
     def get_cust_by_ids(self, city_uuid, cust_id_list):
         """根据零售户列表查询其信息"""
         if not cust_id_list:
-            return None
-        
-        cust_id_str = ",".join([f"'{cust_id}'" for cust_id in cust_id_list])
-        query = f"""
-            SELECT *
-            FROM {self._cust_tablename}
-            WHERE BA_CITY_ORG_CODE = :city_uuid
-            AND BB_RETAIL_CUSTOMER_CODE IN ({cust_id_str})
-        """
-        params = {"city_uuid": city_uuid}
-        data = self.db_helper.load_data_with_page(query, params)
-        
+            return pd.DataFrame()
+
+        query = text(f"""
+        SELECT *
+        FROM {self._cust_tablename}
+        WHERE BA_CITY_ORG_CODE = :city_uuid
+        AND BB_RETAIL_CUSTOMER_CODE IN :ids
+    """).bindparams(bindparam("ids", expanding=True))
+        params = {"city_uuid": city_uuid, "ids": list(cust_id_list)}
+        data = pd.DataFrame(self.db_helper.fetch_all(query, params))
+
         return data
     
     def get_shop_by_ids(self, city_uuid, cust_id_list):
         """根据零售户列表查询其信息"""
         if not cust_id_list:
-            return None
-        
-        cust_id_str = ",".join([f"'{cust_id}'" for cust_id in cust_id_list])
-        query = f"""
-            SELECT *
-            FROM {self._shopping_tablename}
-            WHERE city_uuid = :city_uuid
-            AND cust_code IN ({cust_id_str})
-        """
-        params = {"city_uuid": city_uuid}
-        data = self.db_helper.load_data_with_page(query, params)
-        
+            return pd.DataFrame()
+
+        query = text(f"""
+        SELECT *
+        FROM {self._shopping_tablename}
+        WHERE city_uuid = :city_uuid
+        AND cust_code IN :ids
+    """).bindparams(bindparam("ids", expanding=True))
+        params = {"city_uuid": city_uuid, "ids": list(cust_id_list)}
+        data = pd.DataFrame(self.db_helper.fetch_all(query, params))
+
         return data
     
     def get_product_by_ids(self, city_uuid, product_id_list):
         """根据product_code列表查询其信息"""
         if not product_id_list:
-            return None
-        
-        product_id_str = ",".join([f"'{product_id}'" for product_id in product_id_list])
-        query = f"""
-            SELECT *
-            FROM {self._product_tablename}
-            WHERE city_uuid = :city_uuid
-            AND product_code IN ({product_id_str})
-        """
-        params = {"city_uuid": city_uuid}
-        data = self.db_helper.load_data_with_page(query, params)
-        
+            return pd.DataFrame()
+
+        query = text(f"""
+        SELECT *
+        FROM {self._product_tablename}
+        WHERE city_uuid = :city_uuid
+        AND product_code IN :ids
+    """).bindparams(bindparam("ids", expanding=True))
+        params = {"city_uuid": city_uuid, "ids": list(product_id_list)}
+        data = pd.DataFrame(self.db_helper.fetch_all(query, params))
+
         return data
     
     def get_order_by_product_ids(self, city_uuid, product_ids):
         """获取指定香烟列表的所有售卖记录"""
         if not product_ids:
-            return None
-        
-        product_ids_str = ",".join([f"'{product_code}'" for product_code in product_ids])
-        query = f"""
-            SELECT *
-            FROM {self._order_tablename}
-            WHERE city_uuid = :city_uuid
-            AND product_code IN ({product_ids_str})
-        """
-        params = {"city_uuid": city_uuid}
-        data = self.db_helper.load_data_with_page(query, params)
-        
+            return pd.DataFrame()
+
+        query = text(f"""
+        SELECT *
+        FROM {self._order_tablename}
+        WHERE city_uuid = :city_uuid
+        AND product_code IN :ids
+    """).bindparams(bindparam("ids", expanding=True))
+        params = {"city_uuid": city_uuid, "ids": list(product_ids)}
+        data = pd.DataFrame(self.db_helper.fetch_all(query, params))
+
         cust_list = self.get_cust_list(city_uuid)
         cust_index = cust_list.set_index("BB_RETAIL_CUSTOMER_CODE")
         data = data.join(cust_index, on="cust_code", how="inner")
-        
+
         return data
     
     def get_order_by_product(self, city_uuid, product_id):
@@ -252,31 +247,18 @@ class MySqlDao:
         query = f"SELECT DISTINCT product_code FROM {self._order_tablename} WHERE city_uuid = :city_uuid"
         params = {"city_uuid": city_uuid}
         
-        data = pd.DataFrame(self.db_helper.fetch_all(text(query), params))
-        
+        data = self.db_helper.load_data_with_page(query, params)
+
         return data
-    
-    # def get_product_from_order(self, city_uuid):
-    #     query = f"SELECT cust_code, product_code FROM {self._order_tablename} WHERE city_uuid = :city_uuid"
-    #     params = {"city_uuid": city_uuid}
-        
-    #     data = pd.DataFrame(self.db_helper.fetch_all(text(query), params))
-        
-    #     cust_list = self.get_cust_list(city_uuid)
-    #     cust_index = cust_list.set_index("BB_RETAIL_CUSTOMER_CODE")
-    #     data = data.join(cust_index, on="cust_code", how="inner")
-    #     data = data["product_code"]
-        
-    #     return data
-    
+
     def get_cust_list(self, city_uuid):
         query = f"SELECT DISTINCT BB_RETAIL_CUSTOMER_CODE FROM {self._cust_tablename} WHERE BA_CITY_ORG_CODE = :city_uuid"
         params = {"city_uuid": city_uuid}
         
-        data = pd.DataFrame(self.db_helper.fetch_all(text(query), params))
-        
+        data = self.db_helper.load_data_with_page(query, params)
+
         return data
-    
+
     def data_preprocess(self, data: pd.DataFrame):
         """数据预处理"""
         data.drop(["cust_uuid", "longitude", "latitude", "range_radius"], axis=1, inplace=True)

+ 33 - 25
database/db/mysql.py

@@ -34,46 +34,52 @@ class MySqlDatabaseHelper:
         # 创建数据库连接
         try:
             conn = "mysql+pymysql://" + self._user + ":" + self._passwd + "@" + self._host + ":" + str(self._port) + "/" + self._dbname
+
+            # 通过连接池创建engine
+            self.engine = create_engine(
+                conn,
+                pool_size=20, # 设置连接池大小
+                max_overflow=30, # 超过连接池大小时的额外连接数
+                pool_recycle=1800, # 回收连接时间
+                pool_pre_ping=True, # 防止断开连接
+                isolation_level="READ COMMITTED" # 降低隔离级别
+            )
         except Exception as e:
-            raise ConnectionAbortedError(f"failed to create connection string: {e}")
-        
-        # 通过连接池创建engine
-        self.engine = create_engine(
-            conn,
-            pool_size=20, # 设置连接池大小
-            max_overflow=30, # 超过连接池大小时的额外连接数
-            pool_recycle=1800, # 回收连接时间
-            pool_pre_ping=True, # 防止断开连接
-            isolation_level="READ COMMITTED" # 降低隔离级别
-        )
-        
+            raise ConnectionAbortedError(f"failed to create connection: {e}")
+
         self._DBSession = sessionmaker(bind=self.engine)
     
     def load_data_with_page(self, query, params, page_size=100000):
         """分页查询数据"""
         data = pd.DataFrame()
-        count_query = text(query.replace("SELECT *", "SELECT COUNT(*)"))
+        # 用子查询包裹原始查询来计数,避免字符串替换
+        count_query = text(f"SELECT COUNT(*) FROM ({query}) AS _count_subq")
         query += " LIMIT :limit OFFSET :offset"
         query = text(query)
-    
+
         # 获取总行数
-        total_rows = self.fetch_one(count_query, params)[0]
+        result = self.fetch_one(count_query, params)
+        total_rows = result[0] if result is not None else 0
+
+        if total_rows == 0:
+            return data
 
         page = 1
-        with tqdm(total=total_rows, desc="Loading data", unit="rows") as pbar:  # 初始化进度条
+        with tqdm(total=total_rows, desc="Loading data", unit="rows") as pbar:
             while True:
-                offset = (page - 1) * page_size  # 计算偏移量
-                params["limit"] = page_size
-                params["offset"] = offset
+                offset = (page - 1) * page_size
+                # 复制 params 避免修改调用方的字典
+                page_params = dict(params)
+                page_params["limit"] = page_size
+                page_params["offset"] = offset
 
-                df = pd.DataFrame(self.fetch_all(query, params))
+                df = pd.DataFrame(self.fetch_all(query, page_params))
                 if df.empty:
                     break
                 data = pd.concat([data, df], ignore_index=True)
-            
-                # 更新进度条
-                pbar.update(len(df))  # 更新进度条的行数
-            
+
+                pbar.update(len(df))
+
                 page += 1
         return data
         
@@ -87,6 +93,7 @@ class MySqlDatabaseHelper:
         except SQLAlchemyError as e:
             session.rollback()
             print(f"error: {e}")
+            raise
         finally:
             session.close()
             
@@ -96,10 +103,11 @@ class MySqlDatabaseHelper:
         try:
             result = session.execute(query, params or {}).fetchone()
             return result
-            
+
         except SQLAlchemyError as e:
             session.rollback()
             print(f"error: {e}")
+            raise
         finally:
             session.close()
             

+ 374 - 0
docs/superpowers/plans/2026-03-15-database-bugfix.md

@@ -0,0 +1,374 @@
+# 数据库操作 Bug 修复实现计划
+
+> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**目标:** 修复数据库操作层的 6 个 bug,消除 SQL 注入风险,改进错误处理
+
+**架构:** 外科手术式修复,不改变现有架构。修复顺序:修复 2 → 修复 1/3 → 修复 6 → 修复 4/5
+
+**技术栈:** SQLAlchemy, pandas, pymysql
+
+---
+
+## Chunk 1: mysql.py 修复
+
+### 任务 1: 修复 load_data_with_page 的 count 查询和 params 副作用(修复 2)
+
+**文件:**
+- 修改: `database/db/mysql.py:52-78`
+
+- [ ] **步骤 1: 修改 load_data_with_page 方法**
+
+将第 52-78 行的 `load_data_with_page` 方法替换为:
+
+```python
+def load_data_with_page(self, query, params, page_size=100000):
+    """分页查询数据"""
+    data = pd.DataFrame()
+    # 用子查询包裹原始查询来计数,避免字符串替换
+    count_query = text(f"SELECT COUNT(*) FROM ({query}) AS _count_subq")
+    query += " LIMIT :limit OFFSET :offset"
+    query = text(query)
+
+    # 获取总行数
+    total_rows = self.fetch_one(count_query, params)[0]
+
+    page = 1
+    with tqdm(total=total_rows, desc="Loading data", unit="rows") as pbar:
+        while True:
+            offset = (page - 1) * page_size
+            # 复制 params 避免修改调用方的字典
+            page_params = dict(params)
+            page_params["limit"] = page_size
+            page_params["offset"] = offset
+
+            df = pd.DataFrame(self.fetch_all(query, page_params))
+            if df.empty:
+                break
+            data = pd.concat([data, df], ignore_index=True)
+
+            pbar.update(len(df))
+
+            page += 1
+    return data
+```
+
+- [ ] **步骤 2: 提交修复 2**
+
+```bash
+git add database/db/mysql.py
+git commit -m "fix: 修复 load_data_with_page 的 count 查询和 params 副作用
+
+- 用子查询包裹原始查询来计数,不再依赖字符串替换
+- 每次分页前复制 params 避免修改调用方的字典"
+```
+
+### 任务 2: 修复 connect_database 和 fetch 方法的错误处理(修复 1 和 3)
+
+**文件:**
+- 修改: `database/db/mysql.py:33-48, 81-104`
+
+- [ ] **步骤 1: 修改 connect_database 方法**
+
+将第 33-48 行的 `connect_database` 方法替换为:
+
+```python
+def connect_database(self):
+    # 创建数据库连接
+    try:
+        conn = "mysql+pymysql://" + self._user + ":" + self._passwd + "@" + self._host + ":" + str(self._port) + "/" + self._dbname
+
+        # 通过连接池创建engine
+        self.engine = create_engine(
+            conn,
+            pool_size=20, # 设置连接池大小
+            max_overflow=30, # 超过连接池大小时的额外连接数
+            pool_recycle=1800, # 回收连接时间
+            pool_pre_ping=True, # 防止断开连接
+            isolation_level="READ COMMITTED" # 降低隔离级别
+        )
+    except Exception as e:
+        raise ConnectionAbortedError(f"failed to create connection: {e}")
+
+    self._DBSession = sessionmaker(bind=self.engine)
+```
+
+- [ ] **步骤 2: 修改 fetch_all 方法**
+
+将第 81-91 行的 `fetch_all` 方法替换为:
+
+```python
+def fetch_all(self, query, params=None):
+    """执行SQL查询并返回所有结果"""
+    session = self._DBSession()
+    try:
+        results = session.execute(query, params or {}).fetchall()
+        return results
+    except SQLAlchemyError as e:
+        session.rollback()
+        print(f"error: {e}")
+        raise
+    finally:
+        session.close()
+```
+
+- [ ] **步骤 3: 修改 fetch_one 方法**
+
+将第 93-104 行的 `fetch_one` 方法替换为:
+
+```python
+def fetch_one(self, query, params=None):
+    """执行SQL查询并返回单条结果"""
+    session = self._DBSession()
+    try:
+        result = session.execute(query, params or {}).fetchone()
+        return result
+
+    except SQLAlchemyError as e:
+        session.rollback()
+        print(f"error: {e}")
+        raise
+    finally:
+        session.close()
+```
+
+- [ ] **步骤 4: 提交修复 1 和 3**
+
+```bash
+git add database/db/mysql.py
+git commit -m "fix: 修复 connect_database 和 fetch 方法的错误处理
+
+修复 1: 将 create_engine 移入 try 块,确保连接失败被捕获
+修复 3: 在 fetch_all/fetch_one 的 except 块中添加 raise
+
+注意:修复 3 是行为破坏性变更,调用方现在会收到异常而非 None"
+```
+
+---
+
+## Chunk 2: mysql_dao.py 修复
+
+### 任务 3: 修复 get_cust_list 和 get_product_from_order 的接口不一致(修复 6)
+
+**文件:**
+- 修改: `database/dao/mysql_dao.py:251-278`
+
+- [ ] **步骤 1: 修改 get_product_from_order 方法**
+
+将第 251-257 行的 `get_product_from_order` 方法替换为:
+
+```python
+def get_product_from_order(self, city_uuid):
+    query = f"SELECT DISTINCT product_code FROM {self._order_tablename} WHERE city_uuid = :city_uuid"
+    params = {"city_uuid": city_uuid}
+
+    data = self.db_helper.load_data_with_page(query, params)
+
+    return data
+```
+
+- [ ] **步骤 2: 修改 get_cust_list 方法**
+
+将第 272-278 行的 `get_cust_list` 方法替换为:
+
+```python
+def get_cust_list(self, city_uuid):
+    query = f"SELECT DISTINCT BB_RETAIL_CUSTOMER_CODE FROM {self._cust_tablename} WHERE BA_CITY_ORG_CODE = :city_uuid"
+    params = {"city_uuid": city_uuid}
+
+    data = self.db_helper.load_data_with_page(query, params)
+
+    return data
+```
+
+- [ ] **步骤 3: 提交修复 6**
+
+```bash
+git add database/dao/mysql_dao.py
+git commit -m "fix: 统一 get_cust_list 和 get_product_from_order 的接口
+
+- 改用 load_data_with_page 替代直接调用 fetch_all
+- 与其他查询方法保持一致"
+```
+
+### 任务 4: 修复所有 IN 子句的 SQL 注入风险(修复 4)
+
+**文件:**
+- 修改: `database/dao/mysql_dao.py:2, 105-175`
+
+- [ ] **步骤 1: 添加 bindparam 导入**
+
+将第 2 行从:
+```python
+from sqlalchemy import text
+```
+
+修改为:
+```python
+from sqlalchemy import text, bindparam
+```
+
+- [ ] **步骤 2: 修改 get_cust_by_ids 方法**
+
+将第 105-120 行的 `get_cust_by_ids` 方法替换为:
+
+```python
+def get_cust_by_ids(self, city_uuid, cust_id_list):
+    """根据零售户列表查询其信息"""
+    if not cust_id_list:
+        return None
+
+    query = text(f"""
+        SELECT *
+        FROM {self._cust_tablename}
+        WHERE BA_CITY_ORG_CODE = :city_uuid
+        AND BB_RETAIL_CUSTOMER_CODE IN :ids
+    """).bindparams(bindparam("ids", expanding=True))
+    params = {"city_uuid": city_uuid, "ids": list(cust_id_list)}
+    data = pd.DataFrame(self.db_helper.fetch_all(query, params))
+
+    return data
+```
+
+- [ ] **步骤 3: 修改 get_shop_by_ids 方法**
+
+将第 122-137 行的 `get_shop_by_ids` 方法替换为:
+
+```python
+def get_shop_by_ids(self, city_uuid, cust_id_list):
+    """根据零售户列表查询其信息"""
+    if not cust_id_list:
+        return None
+
+    query = text(f"""
+        SELECT *
+        FROM {self._shopping_tablename}
+        WHERE city_uuid = :city_uuid
+        AND cust_code IN :ids
+    """).bindparams(bindparam("ids", expanding=True))
+    params = {"city_uuid": city_uuid, "ids": list(cust_id_list)}
+    data = pd.DataFrame(self.db_helper.fetch_all(query, params))
+
+    return data
+```
+
+- [ ] **步骤 4: 修改 get_product_by_ids 方法**
+
+将第 139-154 行的 `get_product_by_ids` 方法替换为:
+
+```python
+def get_product_by_ids(self, city_uuid, product_id_list):
+    """根据product_code列表查询其信息"""
+    if not product_id_list:
+        return None
+
+    query = text(f"""
+        SELECT *
+        FROM {self._product_tablename}
+        WHERE city_uuid = :city_uuid
+        AND product_code IN :ids
+    """).bindparams(bindparam("ids", expanding=True))
+    params = {"city_uuid": city_uuid, "ids": list(product_id_list)}
+    data = pd.DataFrame(self.db_helper.fetch_all(query, params))
+
+    return data
+```
+
+- [ ] **步骤 5: 修改 get_order_by_product_ids 方法**
+
+将第 156-175 行的 `get_order_by_product_ids` 方法替换为:
+
+```python
+def get_order_by_product_ids(self, city_uuid, product_ids):
+    """获取指定香烟列表的所有售卖记录"""
+    if not product_ids:
+        return None
+
+    query = text(f"""
+        SELECT *
+        FROM {self._order_tablename}
+        WHERE city_uuid = :city_uuid
+        AND product_code IN :ids
+    """).bindparams(bindparam("ids", expanding=True))
+    params = {"city_uuid": city_uuid, "ids": list(product_ids)}
+    data = pd.DataFrame(self.db_helper.fetch_all(query, params))
+
+    cust_list = self.get_cust_list(city_uuid)
+    cust_index = cust_list.set_index("BB_RETAIL_CUSTOMER_CODE")
+    data = data.join(cust_index, on="cust_code", how="inner")
+
+    return data
+```
+
+- [ ] **步骤 6: 提交修复 4**
+
+```bash
+git add database/dao/mysql_dao.py
+git commit -m "fix: 修复所有 IN 子句的 SQL 注入风险
+
+- 用 bindparam(expanding=True) 替代字符串拼接
+- 修复方法: get_cust_by_ids, get_shop_by_ids, get_product_by_ids, get_order_by_product_ids
+- 改用 fetch_all 直接查询,跳过分页(结果集大小由输入列表决定)"
+```
+
+### 任务 5: 修复 get_product_by_id 的分页开销(修复 5)
+
+**文件:**
+- 修改: `database/dao/mysql_dao.py:92-103`
+
+- [ ] **步骤 1: 修改 get_product_by_id 方法**
+
+将第 92-103 行的 `get_product_by_id` 方法替换为:
+
+```python
+def get_product_by_id(self, city_uuid, product_id):
+    """根据city_uuid 和 product_id 从表中获取拼柜信息"""
+    query = text(f"""
+        SELECT *
+        FROM {self._product_tablename}
+        WHERE city_uuid = :city_uuid
+        AND product_code = :product_id
+    """)
+    params = {"city_uuid": city_uuid, "product_id": product_id}
+    result = self.db_helper.fetch_one(query, params)
+    return pd.DataFrame([dict(result._mapping)] if result else [])
+```
+
+- [ ] **步骤 2: 提交修复 5**
+
+```bash
+git add database/dao/mysql_dao.py
+git commit -m "fix: 修复 get_product_by_id 的分页开销
+
+- 改用 fetch_one 查询单条记录
+- 返回单行 DataFrame 保持接口一致"
+```
+
+---
+
+## 验证
+
+- [ ] **步骤 1: 检查所有提交**
+
+```bash
+git log --oneline -5
+```
+
+预期:看到 5 个修复提交(修复 2, 修复 1+3, 修复 6, 修复 4, 修复 5)
+
+- [ ] **步骤 2: 检查修改的文件**
+
+```bash
+git diff HEAD~5 --stat
+```
+
+预期:
+```
+database/db/mysql.py       | 修改行数
+database/dao/mysql_dao.py  | 修改行数
+2 files changed
+```
+
+- [ ] **步骤 3: 最终确认**
+
+所有 6 个修复已完成,按照依赖顺序执行。

+ 85 - 0
docs/superpowers/specs/2026-03-15-database-bugfix-design.md

@@ -0,0 +1,85 @@
+# 数据库操作 Bug 修复设计
+
+日期:2026-03-15
+范围:`database/db/mysql.py`、`database/dao/mysql_dao.py`
+方案:外科手术式 bug 修复,不改架构
+
+## 问题清单
+
+### mysql.py
+
+1. `connect_database`:try/except 只包了连接字符串拼接,`create_engine` 在 try 块外,真正的连接失败不会被捕获。
+2. `load_data_with_page`:用 `query.replace("SELECT *", "SELECT COUNT(*)")` 构建 count 查询,查询不以 `SELECT *` 开头时会出错。另外会直接修改调用方传入的 `params` 字典(添加 `limit`、`offset` 键),产生副作用。
+3. `fetch_all` / `fetch_one`:捕获 `SQLAlchemyError` 后只打印错误、静默返回 `None`,调用方无法感知失败。
+
+### mysql_dao.py
+
+4. `get_cust_by_ids`、`get_shop_by_ids`、`get_product_by_ids`、`get_order_by_product_ids`:`IN (...)` 子句通过字符串拼接构建,存在 SQL 注入风险。
+5. `get_product_by_id`:查询单条记录却走了分页逻辑,有不必要的开销。
+6. `get_cust_list`、`get_product_from_order`:直接调用 `fetch_all(text(query))`,与其他方法统一使用 `load_data_with_page` 的风格不一致。依赖修复 2 先完成。
+
+## 修复方案
+
+### mysql.py
+
+**修复 1 — `connect_database`**
+将 `create_engine(...)` 移入 try 块,确保连接失败时被捕获并抛出 `ConnectionAbortedError`。
+
+**修复 2 — `load_data_with_page`**
+用子查询包裹原始查询来构建 count 查询,不再依赖字符串替换:
+```sql
+SELECT COUNT(*) FROM (<原始查询>) AS _count_subq
+```
+无论原始查询的 SELECT 子句是什么都能正确计数。同时在修改 params 前先复制,避免副作用:
+```python
+params = dict(params)  # 避免修改调用方的字典
+```
+
+**修复 3 — `fetch_all` / `fetch_one`**
+在 except 块打印错误后加 `raise`,让异常向上传播。
+
+注意:这是一个行为破坏性变更。当前调用方在失败时收到 `None`,修复后会收到未处理的异常。这是有意为之——显式异常比静默失败更容易排查问题。
+
+### mysql_dao.py
+
+**修复 4 — IN 子句 SQL 注入**
+`load_data_with_page` 将 query 参数作为普通字符串处理(字符串拼接后再包装成 `text()`),与预构建的 `text()` 对象不兼容。
+
+因此,受影响的四个方法改为直接调用 `fetch_all`,配合 `bindparam(expanding=True)` 参数化:
+```python
+from sqlalchemy import bindparam, text
+
+query = text("""
+    SELECT * FROM table
+    WHERE city_uuid = :city_uuid
+    AND col IN :ids
+""").bindparams(bindparam("ids", expanding=True))
+params = {"city_uuid": city_uuid, "ids": list(id_list)}
+data = pd.DataFrame(self.db_helper.fetch_all(query, params))
+```
+这些方法的结果集大小由输入列表决定,跳过分页是安全的。
+
+**修复 5 — `get_product_by_id`**
+改用 `fetch_one` 查单条,返回单行 DataFrame 与其他方法保持一致:
+```python
+result = self.db_helper.fetch_one(text(query), params)
+return pd.DataFrame([dict(result._mapping)] if result else [])
+```
+
+**修复 6 — `get_cust_list` / `get_product_from_order`**
+将直接调用 `fetch_all(text(query))` 改为 `load_data_with_page(query, params)`,统一接口风格。需要修复 2 先完成(子查询方式能正确处理 `SELECT DISTINCT`)。
+
+## 修复顺序
+
+由于存在依赖关系,按以下顺序执行:
+1. 修复 2(load_data_with_page 子查询 count)
+2. 修复 1、修复 3(相互独立,可同时进行)
+3. 修复 6(依赖修复 2)
+4. 修复 4、修复 5(相互独立)
+
+## 不在范围内
+
+- 不改架构(不做读写分离,不引入 ORM 模型)
+- 不改 Redis 层
+- 不改 DAO 方法签名
+- 不在 DAO 调用方添加额外错误处理

+ 535 - 0
models/rank/data/config_new.py

@@ -0,0 +1,535 @@
+class CustConfig:
+    FEATURES_COLUMNS = [
+        "cust_code",                    # 零售户编码
+        "cust_name",
+        "busi_place_area_section",      # 营业面积区间
+        "rent_section",                 # 租金区间
+        "rent_price_section",           # 租金单价区间
+        "busi_open_section",            # 营业开始时间区间
+        "busi_close_section",           # 营业结束时间区间
+        "is_chain_storename",           # 是否连锁
+        "criterion_codename",           # 守法经营情况
+        "market_info_codename",         # 市场采集点情况
+        "tag_codename",                 # 卷烟价格执行情况
+        "cooperate_codename",           # 配合程度
+        "store_appearance_name",        # 店面形象
+        "position_codename",            # 商圈名称
+        "sub_position_codename",        # 次级商圈名称
+        "zone_appraise_name",           # 地段评价
+        "choose_road_name",             # 路段评价
+        "choose_address_name",          # 选址
+        "area_position_type_name",      # 区域位置划分
+        "area_func_type_name",          # 区域功能划分
+        "community_func_type_name",     # 社区功能划分
+        "rate_pay_type_name",           # 纳税性质
+        "order_cycle_type_name",        # 订货周期
+        "is_modern_terminalname",       # 是否现代终端
+        "modern_terminal_name",         # 现代终端类型
+        "cooperate_type_name",          # 加盟类型
+        "terminal_star_name",           # 终端星级
+        "star_terminal_name",           # 星级终端类型
+        "appearance_span_section",      # 门面跨度区间
+        "upholster_name",               # 店内装潢名称
+        "shop_feature_name",            # 门店特色名称
+        "shop_char_type_name",          # 经营特色名称
+        "has_taste_name",               # 是否卷烟品吸区
+        "show_area_section",            # 卷烟成列面积区间
+        "sign_status_name",             # 是否有店招门头(店招门头状态)
+        "shopsunny_vi_name",            # 现代终端VI门头名称
+        "header_name",                  # 门头标识
+        "counter_status_name",          # 地柜状态
+        "counter_number",               # 地柜个数
+        "counter_put_type_name",        # 地柜陈列样式
+        "back_counter_status_name",     # 背柜状态
+        "back_counter_put_type_name",   # 背柜陈列样式
+        "back_counter_style_name",      # 背柜样式
+        "back_counter_number",          # 背柜个数
+        "back_counter_has_show_name",   # 背柜条烟陈列区状态
+        "legal_person_gender",          # 法人性别
+        "legal_education_name",         # 法人文化程度
+        "legal_is_cpc_member",          # 法人是否为党员
+        "operator_person_gender",       # 经营者性别
+        "operator_education_name",      # 经营者文化程度
+        "operator_is_cpc_member",       # 经营者是否为党员
+        "market_type_name",             # 市场类型名称
+        "busi_place_codename",          # 经营业态名称
+        "sub_busi_codename",            # 业态细分名称
+        "sub_market_type_name",         # 城乡分类名称
+        "creditclass_name",             # 信用等级名称
+    ]
+
+    ONEHOT_CAT = {
+        "busi_place_area_section":      ["0", "10(含)㎡以下", "10-20(含)㎡", "20-30(含)㎡", "30-40(含)㎡", "40-50(含)㎡", "50-60(含)㎡", 
+                                         "60-70(含)㎡", "70-80(含)㎡", "80-90(含)㎡", "90-100(含)㎡", "100㎡以上"],
+        "rent_section":                 ["0", "500(含)元以下", "500-1000(含)元", "1000-1500(含)元", "1500-2000(含)元", "2000-2500(含)元", 
+                                         "2500-3000(含)元", "3000-3500(含)元", "3500-4000(含)元", "4000-4500(含)元", "4500-5000(含)元", "5000元以上"],
+        "rent_price_section":           ["0", "500(含)元/㎡以下", "500-1000(含)元/㎡", "1000-1500(含)元/㎡", "1500-2000(含)元/㎡", "2000-2500(含)元/㎡", 
+                                         "2500-3000(含)元/㎡", "3000-3500(含)元/㎡", "3500-4000(含)元/㎡", "4000-4500(含)元/㎡", "4500-5000(含)元/㎡", "5000元/㎡以上"],
+        "busi_open_section":            ["未知区间", "0:00~1:00", "1:00~2:00", "2:00~3:00", "3:00~4:00", "4:00~5:00", "5:00~6:00", "6:00~7:00", "7:00~8:00", "8:00~9:00", 
+                                         "9:00~10:00", "10:00~11:00", "11:00~12:00", "12:00~13:00", "13:00~14:00", "14:00~15:00", "15:00~16:00", "16:00~17:00", "17:00~18:00", 
+                                         "18:00~19:00", "19:00~20:00", "20:00~21:00", "21:00~22:00", "22:00~23:00", "23:00~0:00"],
+        "busi_close_section":           ["未知区间", "0:00~1:00", "1:00~2:00", "2:00~3:00", "3:00~4:00", "4:00~5:00", "5:00~6:00", "6:00~7:00", "7:00~8:00", "8:00~9:00", 
+                                         "9:00~10:00", "10:00~11:00", "11:00~12:00", "12:00~13:00", "13:00~14:00", "14:00~15:00", "15:00~16:00", "16:00~17:00", "17:00~18:00", 
+                                         "18:00~19:00", "19:00~20:00", "20:00~21:00", "21:00~22:00", "22:00~23:00", "23:00~0:00"],
+        "is_chain_storename":           ["是", "否"],
+        "criterion_codename":           ["1年以内无违法违规情况", "1-3年无违法违规", "3年以上无违法违规情况", "违规"],
+        "market_info_codename":         ["未采集", "很好", "较好", "一般", "差"],
+        "tag_codename":                 ["很好", "较好", "一般", "差"],
+        "cooperate_codename":           ["好", "较好", "一般"],
+        "store_appearance_name":        ["好", "较好", "一般", "差"],
+        "position_codename":            ["交通枢纽区", "商业娱乐区", "办公区", "院校学区", "旅游景区", "集贸区", "居民区", "工业区", "农林渔牧区", "其他"],
+        "sub_position_codename":        ["交通枢纽区-高铁站", "交通枢纽区-车站", "交通枢纽区", "商业娱乐区", "办公区", "院校学区", "旅游景区", "集贸区", "居民区", "工业区", "农林渔牧区", "其他"],
+        "zone_appraise_name":           ["一类", "二类", "三类", "四类", "五类"],
+        "choose_road_name":             ["特别繁华", "繁华", "一般", "偏僻"],
+        "choose_address_name":          ["客流密集区", "要道", "社区", "其它"],
+        "area_position_type_name":      ["城区(市、区县、中心辖区)", "镇区1(有建制的乡镇政府所在地)", "镇区2(撤乡并镇取消的原乡政府所在地)", "镇区3(城乡结合部)", 
+                                         "农村1(经济发展较好的农村)", "农村2(经济发展较差的农村)", "农村3(经济发展一般的农村)", "农村4(新农村)"],
+        "area_func_type_name":          ["商贸中心(CBD)", "商贸功能区", "集贸地", "旅游景点", "学区(大专院校、培训机构)", "产业园区2(成熟)", "产业园区1(新兴、在建)", "大型厂、矿区"],
+        "community_func_type_name":     ["高档居住楼宇(成熟)", "高档居住楼宇(新兴、在建)", "一般居住楼宇(成熟)", "一般居住楼宇(新兴、在建)", 
+                                         "商住多用楼宇(成熟)", "商住多用楼宇(新兴、在建)", "政务办公楼宇"],
+        "rate_pay_type_name":           ["一般纳税人", "小规模纳税人", "小规模纳税人(个体)"],
+        "order_cycle_type_name":        ["一周一访", "两周一访", "四周一访", "六周一访", "一月两访", "一月一访", "暂失去联系", "暂不供货"],
+        "is_modern_terminalname":       ["是", "否"],
+        "modern_terminal_name":         ["直营终端", "合作终端", "加盟终端", "一般现代终端", "普通终端", "无法识别"],
+        "cooperate_type_name":          ["品牌加盟", "冠名加盟", "无"],
+        "terminal_star_name":           ["五星终端", "四星终端", "三星终端", "二星终端", "一星终端", "其他", "无"],
+        "star_terminal_name":           ["星级终端", "整改星级终端", "一般终端", "非星级终端", "未分类"],
+        "appearance_span_section":      ["8米以上", "7-8(含)米", "6-7(含)米", "5-6(含)米", "4-5(含)米", "3-4(含)米", "2-3(含)米", "2(含)米以下", "0"],
+        "upholster_name":               ["全面打造", "参标", "非标"],
+        "shop_feature_name":            ["临街橱窗,消费体验区", "临街橱窗", "消费体验区", "其它", "临街橱窗,其它", "消费体验区,临街橱窗", "其它,临街橱窗", 
+                                         "临街橱窗,消费体验区,其它", "其它,消费体验区", "消费体验区,其它,临街橱窗", "其它,临街橱窗,消费体验区", "消费体验区,其它", 
+                                         "其它,消费体验区,临街橱窗", "消费体验区,临街橱窗,其它", "临街橱窗,其它,消费体验区"],
+        "shop_char_type_name":          ['酒,茶,糖,其它', '糖,其它', '酒,茶,糖,特产', '酒,茶', '酒,糖,特产', '酒', '酒,茶,糖,特产,其它', '其它', '酒,茶,糖', 
+                                         '茶,酒', '酒,茶,特产,糖', '茶,酒,特产', '糖,特产', '茶,糖', '特产,糖', '糖,茶,酒', '酒,茶,其它', '酒,特产', '特产,其它',
+                                         ' 糖', '酒,茶,糖,服务,其它', '茶,酒,糖,其它', '服务', '服务,酒,其它', '酒,其它', '服务,茶,糖', '彩票', '餐饮', '餐饮,服务,其它,茶', 
+                                         '酒,糖,其它', '酒,服务,茶', '糖,特产,其它', '服务,其它', '茶,酒,其它', '糖,酒', '酒,糖', '茶,酒,糖,特产', '酒,茶,特产', '其它,糖', 
+                                         '酒,糖,茶,特产', '茶', '酒,糖,服务,其它', '特产', '糖,酒,茶,特产', '餐饮,其它', '酒,糖,特产,其它', '酒,茶,特产,其它', '其它,茶', 
+                                         '茶,糖,特产,酒', '酒,服务,其它', '其它,服务', '茶,糖,特产', '茶,特产,餐饮,服务,其它', '茶,其它', '酒,茶,糖,特产,服务', '其它,特产,糖,酒', 
+                                         '其它,酒', '酒,特产,其它', '其它,酒,服务', '糖,茶', '其它,特产,糖,茶,酒', '酒,茶,彩票', '特产,服务,其它', '酒,糖,茶', '餐饮,服务', 
+                                         '特产,酒', '酒,茶,服务', '酒,其它,特产', '其它,酒,茶,糖', '茶,特产,酒', '茶,酒,糖', '酒,特产,服务', '酒,服务', '彩票,糖', '酒,茶,糖,服务', 
+                                         '茶,糖,酒', '餐饮,酒', '酒,茶,糖,其它,服务', '茶,特产', '酒,茶,糖,其它,特产', '茶,糖,其它,酒', '酒,特产,服务,其它', '酒,茶,特产,糖,其它,服务',
+                                         '糖,其它,酒', '酒, 餐饮,服务', '酒,茶,糖,特产,服务,其它', '特产,酒,茶', '酒,茶,其它,糖', '茶,糖,其它', '茶,特产,其它', 
+                                         '酒,茶,糖,餐饮,服务,其它', '糖,特产,茶,酒', '酒,茶,特产,服务', '酒,茶,糖,特产,餐饮', '服务,特产,酒', '酒,茶,其它,特产', 
+                                         '酒,茶,糖,特产,餐饮,服务,其它', '茶,彩票', '茶,糖,酒,特产', '特产,茶,酒', '特产,茶', '糖,特产,酒,茶', '特产,糖,茶,酒', '服务,茶,酒', 
+                                         '其它, 酒,茶', '酒,茶,糖,特产,服务,餐饮,其它', '酒,糖,茶,特产,其它', '茶,酒,特产,其它', '酒,茶,服务,其它', '服务,餐饮,酒', '茶,其它,特产', 
+                                         '糖,特产,茶', '茶,酒,特产,糖', '酒,茶,糖,餐饮,服务', '酒,茶,糖,彩票', '服务,酒', '糖,酒,其它', '酒,彩票,茶', '其它,特产', '糖,服务', 
+                                         '茶,酒,糖,特产,其它', '酒,其它,茶', '酒,糖,餐饮', '酒,茶,糖,特产,彩票,餐饮,服务,其它', '特产,糖,茶', '其它,特产,酒,茶', '其它,酒,茶,特产', 
+                                         '酒,糖,特产,服务', '茶,糖,服务', '糖,其它,酒,茶', '茶,酒,特产,服务', '酒,茶,特产,服务,其它', '糖,特产,酒', '酒,糖,特产,茶', '特产,餐饮,服务',
+                                         '餐饮,服务,其它', '其它,茶,酒', '其它,酒,糖', '其它,糖,酒', '酒,茶,特产,餐饮,服务', '茶,糖,特产,其它', '其它,酒,茶,糖,特产', 
+                                         '酒,茶,特产,糖,其它', '服务,酒,茶,糖', '酒,其它, 糖', '特产,茶,酒,糖', '服务,酒,糖', '糖,酒,茶,其它', '酒,特产,糖', '糖,茶,酒,其它', 
+                                         '糖,其它,特产', '糖,茶,其它', '其它,糖,茶,酒', '服务,酒,茶,糖,特产,其它', '茶,特产,酒,其它', '酒,茶,服务,特产', '酒,糖,其它,特产', 
+                                         '特产,酒,其它', '茶,糖,特产,服务', '餐饮,特产', '糖,酒,特产', '糖,酒,餐饮', '酒,特产,茶', '其它,茶,糖,酒', '酒,茶,糖,特产,彩票', '服务,特产',
+                                         '特产,服务', '糖,酒,茶', '酒,糖,特产,服务,其它', '糖,茶,酒,特产', '特产,茶,其它', '酒,特产,糖,茶', '茶,其它,糖', '餐饮,酒, 茶',
+                                         '茶,特产,糖,酒', '彩票,其它', '酒,特产,餐饮,服务,其它', '酒,茶,糖,特产,餐饮,服务', '特产,彩票', '特产,糖,酒,茶', ' 茶,糖,酒,其它',
+                                         '酒,茶,糖,餐饮', '酒,糖,餐饮,服务,特产', '茶,糖,餐饮,其它', '酒,茶,特产,其它,服务', '彩票,糖,酒', '酒,其它,茶,糖', '其它,糖,茶', '特产,酒,糖',
+                                         '特产,糖,餐饮,服务', '酒,餐饮,服务,其它', '特产,糖,茶,其它', '糖,茶,特产', '酒,茶,糖,特产,彩票,餐饮,服务', '酒,茶,糖,特产,彩票,其它',
+                                         '其它,糖,酒,茶', '其它,糖,茶,酒,特产', '酒,糖,服务', '酒,餐饮', '糖,茶,酒,特产,其它', '其它,餐饮', '茶,糖,特产,其它,酒',
+                                         '酒,茶,特产,餐饮,其它', '服务,糖,酒', '服务,餐饮', '特产,糖,其它', '特产,酒,茶,糖,其它', '特产,酒,糖,其它', '酒,茶,餐饮,其它',
+                                         '其它,服务,餐饮', '茶,酒,其它,服务', '茶,其它,特产,酒', '酒,特产,茶,糖,服务,其它', '酒,茶,糖,特产,其它,餐饮', '酒,其它,服务',
+                                         '酒,特产,茶,糖', '特产,酒,茶,糖,服务,其它', '酒,糖,茶,其它', '糖,其它,茶', '服务,茶,酒,其它', '酒,茶,特产,餐饮,糖', '茶,酒,餐饮,服务,其它',
+                                         '酒,特产,茶,其它', '餐饮,糖,其它', '酒,茶,餐饮,服务', '彩票,特产', '茶,酒,糖,服务', '茶,酒,服务', '酒,茶,糖,特产,其它,服务', '服务,酒,其它,茶',
+                                         '茶,特产,服务,其它', '茶,服务,特产', '糖,特产,酒,其它', '酒,餐饮,其它', '酒,糖,餐饮,其它', '酒,特产,彩票,服务', '服务,糖,茶', '服务,糖,其它', 
+                                         '茶,特产,糖', '特产,糖,酒', '酒,糖,其它,服务', '茶,糖,彩票', '特产,酒,茶,糖', '特产,茶,酒,其它', '茶,特产, 其它,酒,糖', '其它,特产,糖,酒,茶', 
+                                         '茶,糖,酒,彩票', '其它,茶,服务', '服务,特产,糖,茶,酒', '餐饮,酒,茶,糖', '特产,酒,服务,其它', '酒,茶,餐饮', '特产,糖,餐饮', '特产,服务,糖', 
+                                         '彩票,酒,茶', '其它,特产,糖', '酒,茶,其它,服务,特产', '茶,糖,特产,酒,其它', '服务,酒,糖,特产,其它', '酒,茶,其它,服务', '酒,茶,服务,餐饮,其它',
+                                         '茶,糖,彩票,餐饮,服务,其它', '餐饮,酒,其它', '其它,茶,糖', '特产,其它,酒,茶,糖', '茶,服务', '酒,茶,糖,特产,彩票,餐饮,其它', '特产,服务,茶,酒',
+                                         '服务,其它,酒', '餐 饮,糖,酒', '服务,酒,糖,其它,特产', '其它,茶,特产', '茶,特产,服务', '茶,特产,餐饮,服务', '酒,茶,其它,餐饮', '茶,餐饮', 
+                                         ' 茶,酒,特产,糖,其它', '茶,酒,糖,特产,彩票', '彩票,酒,茶,糖', '服务,糖,特产,其它', '服务,酒,茶', '其它,茶,糖,特产', '茶,酒,彩票', 
+                                         '特产,茶,糖,其它', '酒,茶,糖,特产,彩票,服务,其它,餐饮', '特产,茶,糖', '酒,糖,茶,服务', '茶,其它,酒', '茶,酒,特产,彩票', '酒,茶,特产,彩票', 
+                                         '茶,服务,其它', '茶,酒,糖,服务,特产', '糖,酒,服务,其它', '其它,茶,酒,糖', '酒,茶,彩票,糖,服务,其它', '茶,糖,酒,其它,服务', '服务,茶,酒,特产', 
+                                         '糖,其它,服务', '糖,茶,餐饮', '茶,糖,特产,餐饮,服务', '其它,服务,酒,茶', '茶,酒,糖,服务,其它', '特产,糖,茶,酒,其它', '糖,茶,特产,其它', 
+                                         '其它,茶,酒,糖,特产', '酒,餐饮,茶', '服务,酒,茶,特产,糖,其它', '糖,酒,茶,服务', '糖,酒,特产,其它', '酒,特产,餐饮,服务', '糖,其它,酒,特产', 
+                                         '酒,茶,特产,餐饮', '酒,茶,糖,彩票,服务', '彩票,餐饮,服务', '服务,酒,特产', '酒,服务,餐饮', '茶,糖,酒,特产,餐饮', '酒,其它,糖,茶', 
+                                         '餐饮,特产,服务,其它', '糖,特产,服务', '酒,茶,特产,糖,服务', '特产,酒,服务', '糖,其它,茶,酒,特产', '特产,酒,茶,服务', '餐饮,其它,服务', 
+                                         '酒,茶,糖,彩票,服务,其它', '其它,特产,酒', '特产,餐饮,服务,茶,糖', '服务,酒,茶,特产,其它', '糖,餐饮,酒,其它', '茶,餐饮,服务', 
+                                         '其 它,酒,茶,糖,特产,服务', '服务,酒,茶,糖,特产', '其它,糖,酒,服务', '其它,特产,糖,茶', '服务,其它,糖,茶', '茶,糖,酒,特产,其它', '彩票,茶', 
+                                         '茶,酒,其它,糖', '茶,餐饮,酒', '其它,糖,酒,特产', '酒,茶,糖,服务,特产', '茶,特产,其它,服务', '糖,茶,酒, 服务', '酒,茶,其它,服务,餐饮,特产,糖',
+                                         '特产,其它,茶', '其它,特产,茶,酒', '酒,糖,特产,餐饮,服务,其它', '特产,餐饮', '特 产,其它,糖', '其它,糖,特产,茶,酒', '服务,糖,特产,茶,酒', 
+                                         '特产,其它,酒', '特产,糖,茶,酒,服务', '酒,特产,餐饮,其它', '特 产,其它,服务,糖,酒,茶', '酒,特产,茶,服务', '茶,糖,酒,服务', '其它,彩票', 
+                                         '餐饮,服务,糖', '酒,彩票,其它', '糖,特产,其它, 茶', '糖,茶,酒,特产,餐饮', '彩票,茶,酒', '其它,服务,酒,茶,糖,特产', '酒,特产,餐饮,茶', 
+                                         '酒,茶,彩票,其它', '茶,特产,酒,糖', '服务,餐饮,其它', '服务,特产,糖', '酒,糖,特产,其它,服务', '餐饮,茶,酒', '酒,茶,糖,特产,服务,彩票,其它', 
+                                         '茶,酒,餐饮', '糖,茶,酒,其它,特产', '茶,酒,彩票,其它', '服务,其它,酒,糖', '糖,服务,其它', '酒,特产,糖,茶,其它', '特产,其它,酒,茶,糖, 服务', 
+                                         '糖,茶,特产,餐饮,服务', '酒,茶,餐饮,服务,其它', '其它,特产,服务', '特产,酒,茶,糖,服务,餐饮', '酒,糖,茶,餐饮,服务,其它', '服务,酒,茶,糖,其它', 
+                                         '酒,特产,其它,糖,茶', '其它,餐饮,服务', '其它,酒,茶,糖,特产,彩票', '茶,酒,服务,餐饮', '糖, 特产,服务,其它', '茶,糖,餐饮', '酒,糖,其它,茶', 
+                                         '其它,茶,糖,特产,酒', '茶,酒,其它,特产', '糖,特产,茶,酒,其它', '酒,茶,特产,其它,糖', '酒,茶,服务,餐饮', '茶,酒,服务,其它', '服务,其它,茶,酒', 
+                                         '酒,特产,彩票', '特产,其它,酒,糖', '服务,糖,酒,茶', '其它,酒,茶,服务', '服务,茶', '酒,特产,糖,其它', '特产,其它,酒,茶', '糖,特产,茶,其它', 
+                                         '酒,茶,特产,糖,餐饮,服务,其它', '服务,茶,糖,酒,特产,其它', '服务,糖,特产', '特产,餐饮,其它', '糖,茶,酒,餐饮', '特产,茶,服务,其它', 
+                                         '酒,特产,餐饮', '糖,特产,其它,酒', '服务,酒,茶,其它', '糖,其它,茶,酒', '茶,糖,特产,服务,其它', '餐饮,酒,服务,其它', '酒,茶,彩票,餐饮,服务', 
+                                         '糖,服务,酒', '酒,茶,其它,特产,糖', '酒,糖,特产,茶,其它', '其它,特产,茶,酒,糖', '酒,糖,其它,餐饮', '餐饮,服务,酒,茶,其它', 
+                                         '特产,茶,糖,酒,其它', '茶,特产,酒,糖,服务', '特产,服务,茶', '其它,糖,特产', '茶,特产,糖,其它', '其它,酒,餐饮', '酒,服务,糖,其它', 
+                                         '餐饮,糖,特产', '特产,酒,茶,餐饮', '其它,糖,特产,茶', '酒,茶,服务,特产,其它', '酒,特产,服务,糖', '茶,酒,餐 饮,其它', '酒,餐饮,糖,其它', 
+                                         '其它,服务,特产,糖,酒', '酒,茶,糖,特产,彩票,餐饮', '服务,其它,特产,糖,茶', '酒,茶,糖,其它, 服务,餐饮', '糖,特产,其它,服务', '糖,茶,特产,酒', 
+                                         '糖,餐饮', '服务,彩票', '茶,酒,餐饮,服务', '餐饮,茶', '茶,糖,酒,餐饮', '酒,茶,糖,特产,餐饮,其它', '茶,酒,特产,餐饮', '茶,餐饮,其它', 
+                                         '酒,服务,其它,茶', '其它,酒,特产', '特产,糖,服务,其它', '糖,酒,服务', '糖,服务,餐饮', '茶,特产,其它,酒', '特产,酒,糖,茶', '服务,特产,其它', 
+                                         '糖,茶,酒,特产,服务', '特产,其它,茶, 糖,酒', '茶,酒,糖,特产,服务,其它', '酒,茶,特产,服务,糖', '酒,服务,茶,特产', '餐饮,糖,茶,酒', 
+                                         '酒,糖,彩票,其它', '糖,茶, 酒,特产,其它,服务', '特产,茶,酒,糖,其它', '餐饮,特产,酒,茶', '服务,餐饮,酒,茶', '酒,茶,其它,糖,特产', 
+                                         '其它,糖,茶,服务', '茶,酒,糖,特产,服务', '糖,酒,彩票', '服务,特产,茶,酒', '特产,糖,服务', '酒,其它,茶,特产', '服务,茶,酒,糖,特产,其它', 
+                                         ' 酒,特产,糖,茶,餐饮,服务', '糖,酒,其它,服务', '其它,服务,酒,茶,糖', '茶,酒,彩票,特产', '酒,茶,糖,特产,彩票,服务', '服务, 酒,茶,特产', 
+                                         '服务,茶,糖,特产', '酒,茶,服务,其它,糖,特产', '酒,其它,糖,特产', '其它,糖,酒,茶,特产', '服务,其它,特产', ' 酒,茶,糖,彩票,餐饮', 
+                                         '茶,糖,彩票,餐饮', '酒,茶,糖,特产,服务,餐饮', '糖,茶,酒,服务,其它', '餐饮,其它,特产,酒', '酒,糖,特 产,餐饮,彩票', '服务,酒,糖,其它', 
+                                         '酒,茶,特产,服务,其它,糖', '特产,服务,酒,茶,糖', '服务,其它,餐饮', '酒,糖,茶,特产,服务,其它', '茶,特产,服务,餐饮', '茶,餐饮,服务,酒', 
+                                         '彩票,特产,糖,茶,酒,其它,服务', '糖,茶,其它,特产', '糖,餐饮,酒,茶', '特 产,酒,茶,其它', '糖,特产,服务,酒', '餐饮,茶,酒,其它', 
+                                         '其它,服务,酒,糖,特产', '其它,服务,酒', '酒,其它,餐饮,服务', '其它,糖,茶,特产', '茶,糖,特产,彩票', '酒,茶,彩票,特产', '糖,特产,餐饮', 
+                                         '酒,茶,特产,糖,服务,其它', '特产,糖,餐饮,其它', '餐 饮,其它,茶', '服务,糖', '酒,茶,糖,特产,餐饮,其它,服务', '茶,酒,特产,服务,其它', 
+                                         '茶,特产,餐饮', '酒,茶,糖,彩票,其它', ' 糖,彩票', '其它,糖,特产,酒,茶', '服务,特产,糖,酒', '酒,服务,其它,特产', '茶,糖,其它,服务', 
+                                         '餐饮,糖,酒,其它', '酒,茶,特 产,彩票,餐饮,服务', '糖,茶,酒,餐饮,服务,其它', '茶,酒,特产,其它,服务', '其它,服务,餐饮,特产', '酒,其它,特产,服务',
+                                         '酒, 彩票', '餐饮,酒,糖,其它', '茶,服务,其它,酒', '酒,茶,餐饮,特产', '其它,糖,茶,酒,服务', '彩票,服务,特产,酒', '茶,糖,特产, 服务,餐饮', 
+                                         '服务,糖,茶,酒,特产', '其它,服务,特产,糖,茶,酒', '茶,特产,其它,糖', '茶,其它,糖,特产', '茶,酒,糖,餐饮,服务, 其它', '服务,特产,糖,其它', 
+                                         '糖,特产,彩票,茶', '酒,茶,服务,其它,餐饮', '餐饮,茶,特产', '酒,茶,特产,餐饮,服务,其它', '餐 饮,彩票', '餐饮,特产,其它', '特产,彩票,糖,茶,酒', 
+                                         '茶,糖,餐饮,服务', '糖,酒,其它,特产', '茶,服务,酒,糖', '其它,彩票,酒', '酒,糖,茶,其它,服务', '服务,特产,酒,茶,糖', '餐饮,特产,糖,其它,酒', 
+                                         '特产,其它,糖,酒,茶', '糖,茶,酒,彩票', '酒,糖,茶,特产,服务', '服务,茶,特产', '糖,餐饮,其它', '酒,糖,餐饮,服务,其它', '酒,茶,餐饮,服务,特产', 
+                                         '酒,糖,茶,餐饮,其它', '其它,特产,茶,糖', '其它,特产,茶,糖,酒', '茶,餐饮,特产,糖', '酒,糖,彩票', '糖,特产,彩票', '服务,特产,糖,茶,酒,其它', 
+                                         '其它,服务,特产,糖', '糖,茶,酒,餐饮,服务', '特产,茶,糖,服务', '糖,茶,餐饮,其它', '其它,糖,服务', '其它,服务,糖,特产', '茶,糖,特产, 餐饮,酒', 
+                                         '特产,服务,糖,茶,酒', '糖,特产,其它,酒,茶', '茶,餐饮,服务,其它', '服务,餐饮,特产,糖,茶,酒', '茶,糖,特产,服务, 酒', '酒,糖,特产,其它,茶', 
+                                         '茶,酒,糖,其它,特产', '服务,其它,糖', '酒,茶,糖,服务,餐饮', '酒,糖,餐饮,服务', '酒,特产,茶,糖,其它', '茶,其它,服务', '酒,茶,特产,彩票,餐饮', 
+                                         '服务,糖,茶,酒', '酒,服务,餐饮,其它', '服务,酒,其它,特产', '彩票,糖,茶, 酒', '其它,茶,酒,糖,服务', '餐饮,糖', '其它,特产,茶', '酒,服务,特产', 
+                                         '酒,服务,茶,糖,特产,其它', '糖,茶,服务', '酒,糖,茶,服务,其它', '餐饮,特产,酒', '特产,糖,服务,餐饮', '特产,酒,其它,茶,糖', '特产,茶,服务', 
+                                         '糖,茶,特产,服务', '茶,特产,彩票', '其它,服务,糖,特产,茶,酒', '彩票,茶,糖', '糖,酒,茶,特产,其它', '其它,酒,糖,特产', '服务,其它,酒,茶,糖,特产',
+                                         '酒,糖, 服务,特产,其它', '特产,茶,餐饮,服务,其它', '茶,餐饮,特产', '酒,茶,餐饮,其它,服务', '酒,服务,其它,彩票', '特产,酒,茶,糖, 服务', 
+                                         '特产,茶,彩票,其它', '酒,茶,特产,彩票,服务', '服务,其它,酒,茶', '糖,茶,其它,酒', '酒,茶,服务,其它,特产', '酒,特产,茶,糖,服务', 
+                                         '彩票,其它,茶,酒', '特产,其它,服务', '酒,特产,其它,服务', '糖,茶,酒,其它,服务', '彩票,服务,酒,其它', '服务,糖,茶,酒,特产,其它', 
+                                         '其它,服务,特产', '酒,茶,特产,彩票,其它', '服务,其它,茶,酒,糖', '彩票,酒', '彩票,服务', '茶,特产, 餐饮,其它', '餐饮,茶,糖,酒', '服务,茶,其它',
+                                         '服务,酒,茶,糖,其它,特产', '特产,其它,糖,茶', '服务,茶,糖,酒', '酒,茶,餐饮,服务,其它,特产', '酒,其它,糖,茶,特产', '茶,服务,糖,特产', 
+                                         '茶,酒,糖,彩票,特产,其它', '彩票,服务,其它', '特产,茶,糖,酒', '糖,其它,餐饮', '糖,餐饮,服务', '酒,茶,糖,服务,其它,特产', 
+                                         '茶,糖,餐饮,服务,其它', '酒,糖,其它,茶,特产', '糖,茶,特产,服 务,彩票', '特产,糖,其它,酒,茶', '茶,糖,特产,餐饮,其它,服务', 
+                                         '其它,特产,酒,茶,糖', '糖,特产,酒,服务,其它', '服务,茶,酒, 糖,特产', '餐饮,茶,糖', '特产,糖,酒,茶,其它', '酒,糖,特产,餐饮', 
+                                         '酒,茶,糖,餐饮,特产', '酒,餐饮,茶,糖', '服务,餐饮,糖', '糖,特产,服务,餐饮,其它', '其它,彩票,特产,服务', '糖,茶,特产,其它,酒', 
+                                         '其它,酒,特产,糖,茶', '酒,茶,糖,彩票,特产', '糖, 茶,酒,特产,彩票,其它,服务', '茶,其它,服务,酒', '服务,餐饮,酒,茶,其它', '其它,特产,酒,糖', 
+                                         '茶,酒,糖,特产,服务,餐饮,其它', '餐饮,服务,特产', '酒,糖,茶,特产,彩票', '酒,服务,茶,其它', '酒,其它,糖,服务', '酒,其它,特产,茶', 
+                                         '特产,糖,茶,餐饮', ' 酒,其它,茶,糖,特产', '酒,茶,糖,特产,彩票,服务,其它', '酒,糖,特产,餐饮,服务', '糖,特产,酒,服务', '酒,糖,特产,彩票,其它', 
+                                         '其它,特产,服务,茶', '酒,茶,糖,特产,服务,其它,餐饮', '餐饮,服务,酒', '酒,茶,特产,其它,餐饮', '茶,糖,彩票,其它', '彩票,酒,糖', 
+                                         '糖,彩票,服务,其它,酒', '服务,其它,酒,茶,糖', '茶,彩票,特产', '特产,服务,餐饮', '酒,茶,糖,餐饮,其它', '特产,服务, 酒,糖', '酒,特产,其它,茶', 
+                                         '餐饮,酒,茶,其它', '特产,糖,服务,酒,茶', '酒,餐饮,特产', '特产,酒,糖,服务', '特产,服务,其它, 茶,酒,糖', '特产,茶,酒,餐饮,服务', 
+                                         '其它,酒,茶,糖,服务', '茶,酒,糖,特产,餐饮,服务', '茶,糖,酒,特产,服务', '服务,茶,糖,其它', '餐饮,其它,酒', '酒,糖,服务,特产', 
+                                         '特产,服务,其它,酒', '餐饮,酒,茶,服务', '其它,餐饮,酒', '酒,糖,茶,特产,其它,服务', '特产,服务,茶,酒,糖', '其它,茶,特产,糖,酒', 
+                                         '其它,服务,特产,糖,茶', '酒,糖,服务,餐饮', '茶,糖,服务,餐饮', '特产,服务,酒', '茶,服务,酒', '餐饮,服务,其它,糖,茶', 
+                                         '酒,茶,糖,特产,餐饮,服务,其它,彩票', '茶,酒,糖,餐饮,其它', '酒,糖,特产,彩票, 餐饮,服务,其它', '糖,彩票,酒', '服务,特产,糖,茶', 
+                                         '服务,餐饮,特产', '酒,茶,糖,其它,餐饮', '其它,茶,特产,彩票', '酒,糖,茶,特产,餐饮', '酒,茶,糖,特产,服务,彩票', '其它,酒,特产,服务', 
+                                         '彩票,酒,特产', '特产,酒,茶,其它,糖', '茶,酒,糖,餐饮', '茶,彩票,特产,糖', '服务,其它,茶,糖,特产,酒', '酒,茶,糖,服务,其它,彩票', '彩票,酒,其它',
+                                         '其它,餐饮,服务,糖', '服务,其它,特 产,糖,茶,酒', '特产,彩票,餐饮,服务,其它,茶,糖', '茶,糖,餐饮,酒', '酒,服务,特产,茶', '彩票,餐饮', 
+                                         '其它,酒,糖,服务', '餐 饮,服务,酒,茶', '其它,茶,酒,特产', '特产,其它,酒,糖,茶', '特产,糖,茶,酒,服务,其它', '酒,特产,茶,其它,服务', 
+                                         '酒,茶,服务,其它,糖', '餐饮,酒,特产', '茶,糖,其它,特产,酒', '酒,特产,彩票,餐饮', '彩票,糖,茶', '酒,服务,餐饮,糖,其它', '彩票,餐饮,酒,茶', 
+                                         '糖,茶,彩票', '特产,餐饮,服务,其它,茶,酒', '其它,服务,特产,茶,酒', '酒,餐饮,服务,其它,糖,茶', '茶,餐饮,其它,服务', '特产,糖,其它,酒', 
+                                         '服务,糖,酒,其它', '糖,特产,其它,茶,酒', '餐饮,特产,茶', '茶,糖,其它,特产', '餐饮,服务,其它,酒', '其它,茶,酒,服务', '糖,特产,酒,餐饮', 
+                                         '糖,特产,茶,酒,服务', '茶,彩票,餐饮,服务,其它', '服务,其它,糖,特产', '茶,酒,糖,特产, 餐饮', '酒,茶,餐饮,服务,糖', '茶,其它,糖,酒', 
+                                         '酒,茶,彩票,服务', '餐饮,酒,茶,糖,其它', '糖,酒,特产,茶', '餐饮,其它,茶,酒,糖', '特产,糖,酒,餐饮,彩票,服务', '糖,酒,茶,服务,其它', 
+                                         '服务,糖,特产,其它,酒', '糖,特产,茶,餐饮', '茶,酒,糖,彩票', '餐饮,酒,茶,特产', '餐饮,服务,酒,特产', '服务,其它,特产,糖', '茶,服务,特产,其它', 
+                                         '其它,酒,特产,茶,糖', '其它,服务,餐饮,酒', '酒,茶,服务,特产,糖', '酒,糖,茶,其它,特产', '酒,餐饮,茶,特产', '服务,其它,糖,酒,茶', 
+                                         '茶,其它,酒,特产', '酒,糖,特产,彩票', '糖,其它,酒,茶,服务', '其它,糖,茶,酒,餐饮', '特产,其它,服务,酒', '餐饮,特产,糖', '其它,糖,服务,酒', 
+                                         '酒,服务,糖', '酒,茶,特产,服务,糖,其它', '服务,茶,酒,糖', '茶,糖,特产,餐饮', '其它,餐饮,服务,特产,茶,酒', '酒,特产,服务,餐饮', '其它,酒,糖,茶',
+                                         '酒,茶,其它,服务,糖', '酒,彩票,茶,服务,其它', '餐饮,服务,特产,其它', '糖,酒,特产,服务', '其它,酒,茶,糖,特产,餐 饮', '茶,酒,特产,糖,服务', 
+                                         '酒,茶,其它,餐饮,服务', '其它,茶,糖,酒,特产', '特产,餐饮,酒,茶,服务,其它', '特产,彩票,餐饮,服务,其它', '特产,糖,酒,服务', 
+                                         '服务,茶,酒,特产,其它', '酒,服务,特产,其它', '其它,服务,酒,茶,特产', '茶,其它,酒,糖', '糖, 服务,特产', '服务,其它,茶', '糖,服务,其它,茶,酒', 
+                                         '其它,特产,服务,糖,茶', '糖,服务,茶', '糖,特产,彩票,餐饮', '酒,茶,服务,糖', '服务,茶,酒,特产,糖,其它', '茶,糖,酒,其它,特产', '彩票,服务,酒', 
+                                         '茶,糖,特产,彩票,餐饮,服务,其它', '其它,特产,糖, 服务', '茶,酒,糖,特产,其它,服务', '特产,茶,餐饮,服务', '酒,糖,特产,服务,餐饮', '特产,糖,彩票',
+                                         '餐饮,其它,酒,茶', '酒,特产,服务,茶,其它', '酒,茶,糖,餐饮,其它,服务', '酒,其它,彩票', '茶,餐饮,酒,糖', '酒,茶,糖,服务,餐饮,其它', 
+                                         '彩票,酒,服务', '茶,特产,糖,服务', '其它,特产,糖,服务,酒', '餐饮,特产,茶,酒', '茶,酒,特产,糖,餐饮', '其它,彩票,特产', '彩票,特产,茶', 
+                                         '茶,糖,服务,其它', '酒,茶,特产,服务,餐饮', '服务,餐饮,特产,糖,茶', '其它,服务,酒,糖', '其它,糖,茶,特产,酒', '酒,茶,糖,特 产,其它,彩票', 
+                                         '糖,彩票,其它', '服务,酒,糖,特产', '酒,茶,其它,彩票', '其它,服务,餐饮,特产,茶,酒', '酒,茶,餐饮,糖', '糖, 特产,餐饮,服务', 
+                                         '糖,茶,酒,特产,服务,其它', '其它,服务,特产,酒,茶', '酒,茶,餐饮,特产,服务', '酒,特产,其它,糖', '服务,酒,特产,其它', '特产,服务,餐饮,糖,茶,酒', 
+                                         '酒,特产,服务,其它,糖', '服务,特产,酒,茶', '茶,糖,特产,酒,服务', '服务,餐饮,酒,茶,糖,其它', '茶,酒,糖,餐饮,服务', '糖,特产,酒,茶,餐饮', 
+                                         '彩票,酒,糖,特产,其它', '酒,茶,服务,糖,特产', '茶,酒,服务,特产', '糖,彩票,茶', '特产,酒,其它,服务', '酒,其它,餐饮', '茶,酒,特产,其它,糖', 
+                                         '服务,酒,糖,茶', '特产,糖,茶,酒,彩票,餐饮,其它', '特产,茶,其它,糖', '酒,彩票,服务,其它', '特产,茶,酒,糖,彩票', '餐饮,服务,酒,茶,特产', 
+                                         '其它,糖,特产,酒', '餐饮,特产,酒,其它,茶', '特产,糖,茶,酒,餐饮,其它', '其它,服务,茶,酒', '茶,糖,特产,餐饮,服务,其它', '特产,餐饮,酒', 
+                                         '特产,彩票,餐饮,服务,其它,糖', '特产,服务,其它,酒,茶', '糖,茶,特产,酒,其它', '酒,糖,茶,特产,餐饮,服务,其它', '其它,糖,茶,特产,服务', 
+                                         '特产,酒,餐饮', '茶,服务,糖,酒', '特产,彩票,服务', '酒,服务,彩票', '糖,彩票,特产,茶,酒', '特产,其它,糖,酒', '酒,茶,彩票,服务,其它', 
+                                         '酒,茶,特产,其它,服务,餐饮', '茶,糖,服务,特产', '糖,茶,酒,服务,餐饮', '酒,特产,其它,茶,糖', '酒,特产,糖,茶,其它, 餐饮', '餐饮,特产,糖,茶,酒', 
+                                         '酒,茶,特产,彩票,糖', '酒,其它,服务,餐饮', '服务,餐饮,茶,酒', '酒,糖,茶,特产,餐饮,其它', ' 酒,糖,茶,服务,餐饮,其它', '酒,服务,茶,糖', 
+                                         '酒,餐饮,其它,服务', '服务,其它,糖,茶,酒', '酒,其它,特产,糖', '茶,特产,餐饮, 酒', '彩票,茶,酒,其它', '特产,酒,茶,服务,其它', 
+                                         '糖,服务,餐饮,其它', '糖,特产,酒,茶,其它', '酒,餐饮,糖,茶', '服务,其它, 特产,酒,茶,糖', '酒,茶,糖,餐饮,服务,其它,特产', '服务,特产,茶', 
+                                         '特产,糖,茶,酒,彩票,餐饮,服务', '酒,彩票,餐饮', '糖,茶, 酒,餐饮,其它', '酒,糖,特产,餐饮,其它', '酒,餐饮,糖,特产', '餐饮,茶,糖,特产', 
+                                         '彩票,酒,茶,糖,特产', '餐饮,茶,酒,糖,特产', '茶,糖,特产,其它,服务', '酒,糖,茶,餐饮', '其它,特产,酒,糖,茶', '茶,糖,其它,餐饮', 
+                                         '彩票,酒,茶,其它', '彩票,其它,特产', '茶,特产,酒,糖,其它'],
+        "has_taste_name":               ["是", "否"],
+        "show_area_section":            ["8㎡以上", "7.5-8(含)㎡", "7-7.5(含)㎡", "6.5-7(含)㎡", "6-6.5(含)㎡", "5.5-6(含)㎡", "5-5.5(含)㎡", 
+                                         "4.5-5(含)㎡", "4-4.5(含)㎡", "3.5-4(含)㎡", "3-3.5(含)㎡", "2.5-3(含)㎡", "2-2.5(含)㎡", "1.5-2(含)㎡",
+                                         "1-1.5(含)㎡", "0.5-1(含)㎡", "0.5(含)㎡以下", "0"],
+        "sign_status_name":             ["有", "计划中", "无"],
+        "shopsunny_vi_name":            ["直营终端", "合作终端", "加盟终端", "一般现代终端", "普通终端", "无"],
+        "header_name":                  ["连锁形象标识", "单店形象标识", "无标识"],
+        "counter_status_name":          ["有", "计划中", "无"],
+        "counter_number":               ["15", "9", "8", "7", "6", "5", "4", "3", "2", "1", "0"],
+        "counter_put_type_name":        ["独立陈列", "混杂陈列", "无陈列"],
+        "back_counter_status_name":     ["有", "计划中", "无"],
+        "back_counter_put_type_name":   ["独立陈列", "混杂陈列", "无陈列"],
+        "back_counter_style_name":      ["条烟展示板面", "条包混合展示板面", "无条烟展示板面"],
+        "back_counter_number":          ["25", "20", "18", "16", "15", "14", "13", "12", "11", "10", "9", "8", "7", "6", "5", "4", "3", "2", "1", "0"],
+        "back_counter_has_show_name":   ["有", "无"],
+        "legal_person_gender":          ["男", "女"],
+        "legal_education_name":         ["大学以上", "大学", "大专", "中专", "高中", "初中", "小学"],
+        "legal_is_cpc_member":          ["是", "否"],
+        "operator_person_gender":       ["男", "女"],
+        "operator_education_name":      ["大学以上", "大学", "大专", "中专", "高中", "初中", "小学"],
+        "operator_is_cpc_member":       ["是", "否"],
+        "market_type_name":             ["城网", "农网"],
+        "busi_place_codename":          ["烟草专业店", "商场", "超市", "便利店", "娱乐服务类", "其他"],
+        "sub_busi_codename":            ["烟草专卖店", "烟草专柜", "烟酒商店", "其他烟草专业店", "现代连锁便利店", 
+                                         "现代单体便利店", "超市", "传统便利店", "商场", "宾馆(酒店)", "餐馆", "休闲娱乐店", "其他", "未细分"],
+        "sub_market_type_name":         ["主城区", "镇中心区", "城乡结合区", "镇乡结合区", "乡中心区", "村庄", "特殊区域", "未分类"],
+        "creditclass_name":             ["AAA", "AA", "A", "C", "D"]
+    }
+
+    CLEANING_RULES = {
+        "busi_place_area_section":      {"method": "fillna", "opt": "fill", "value": "0", "type": "str"},
+        "rent_section":                 {"method": "fillna", "opt": "fill", "value": "0", "type": "str"},
+        "rent_price_section":           {"method": "fillna", "opt": "fill", "value": "0", "type": "str"},
+        "busi_open_section":            {"method": "fillna", "opt": "fill", "value": "未知区间", "type": "str"},
+        "busi_close_section":           {"method": "fillna", "opt": "fill", "value": "未知区间", "type": "str"},
+        "is_chain_storename":           {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "criterion_codename":           {"method": "fillna", "opt": "fill", "value": "1-3年无违法违规", "type": "str"},
+        "market_info_codename":         {"method": "fillna", "opt": "fill", "value": "未采集", "type": "str"},
+        "tag_codename":                 {"method": "fillna", "opt": "fill", "value": "一般", "type": "str"},
+        "cooperate_codename":           {"method": "fillna", "opt": "fill", "value": "一般", "type": "str"},
+        "store_appearance_name":        {"method": "fillna", "opt": "fill", "value": "一般", "type": "str"},
+        "position_codename":            {"method": "fillna", "opt": "fill", "value": "其他", "type": "str"},
+        "sub_position_codename":        {"method": "fillna", "opt": "fill", "value": "其他", "type": "str"},
+        "zone_appraise_name":           {"method": "fillna", "opt": "fill", "value": "五类", "type": "str"},
+        "choose_road_name":             {"method": "fillna", "opt": "fill", "value": "一般", "type": "str"},
+        "choose_address_name":          {"method": "fillna", "opt": "fill", "value": "其它", "type": "str"},
+        "area_position_type_name":      {"method": "fillna", "opt": "fill", "value": "城区(市、区县、中心辖区)", "type": "str"},
+        "area_func_type_name":          {"method": "fillna", "opt": "fill", "value": "集贸地", "type": "str"},
+        "community_func_type_name":     {"method": "fillna", "opt": "fill", "value": "一般居住楼宇(成熟)", "type": "str"},
+        "rate_pay_type_name":           {"method": "fillna", "opt": "fill", "value": "一般纳税人", "type": "str"},
+        "order_cycle_type_name":        {"method": "fillna", "opt": "fill", "value": "一月一访", "type": "str"},
+        "is_modern_terminalname":       {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "modern_terminal_name":         {"method": "fillna", "opt": "fill", "value": "无法识别", "type": "str"},
+        "cooperate_type_name":          {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "terminal_star_name":           {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "star_terminal_name":           {"method": "fillna", "opt": "fill", "value": "未分类", "type": "str"},
+        "appearance_span_section":      {"method": "fillna", "opt": "fill", "value": "0", "type": "str"},
+        "upholster_name":               {"method": "fillna", "opt": "fill", "value": "非标", "type": "str"},
+        "shop_feature_name":            {"method": "fillna", "opt": "fill", "value": "其它", "type": "str"},
+        "shop_char_type_name":          {"method": "fillna", "opt": "fill", "value": "其它", "type": "str"},
+        "has_taste_name":               {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "show_area_section":            {"method": "fillna", "opt": "fill", "value": "0", "type": "str"},
+        "sign_status_name":             {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "shopsunny_vi_name":            {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "header_name":                  {"method": "fillna", "opt": "fill", "value": "无标识", "type": "str"},
+        "counter_status_name":          {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "counter_number":               {"method": "fillna", "opt": "fill", "value": "0", "type": "str"},
+        "counter_put_type_name":        {"method": "fillna", "opt": "fill", "value": "无陈列", "type": "str"},
+        "back_counter_status_name":     {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "back_counter_put_type_name":   {"method": "fillna", "opt": "fill", "value": "无陈列", "type": "str"},
+        "back_counter_style_name":      {"method": "fillna", "opt": "fill", "value": "无条烟展示板面", "type": "str"},
+        "back_counter_number":          {"method": "fillna", "opt": "fill", "value": "0", "type": "str"},
+        "back_counter_has_show_name":   {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "legal_person_gender":          {"method": "fillna", "opt": "fill", "value": "男", "type": "str"},
+        "legal_education_name":         {"method": "fillna", "opt": "fill", "value": "初中", "type": "str"},
+        "legal_is_cpc_member":          {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "operator_person_gender":       {"method": "fillna", "opt": "fill", "value": "男", "type": "str"},
+        "operator_education_name":      {"method": "fillna", "opt": "fill", "value": "初中", "type": "str"},
+        "operator_is_cpc_member":       {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "market_type_name":             {"method": "fillna", "opt": "fill", "value": "农网", "type": "str"},
+        "busi_place_codename":          {"method": "fillna", "opt": "fill", "value": "其他", "type": "str"},
+        "sub_busi_codename":            {"method": "fillna", "opt": "fill", "value": "其他", "type": "str"},
+        "sub_market_type_name":         {"method": "fillna", "opt": "fill", "value": "未分类", "type": "str"},
+        "creditclass_name":             {"method": "fillna", "opt": "fill", "value": "C", "type": "str"},
+    }
+
+class ProductConfig:
+    FEATURES_COLUMNS = [
+        "product_code",                  # 商品编码
+        "factory_name",                  # 产地(工业公司名称)
+        "brand_name",                    # 品牌名称
+        "is_low_tar",                    # 低焦油卷烟
+        "is_medium",                     # 中支烟
+        "is_tiny",                       # 细支烟
+        "is_coarse",                     # 粗支烟(同时非中非细)
+        "is_exploding_beads",            # 爆珠烟
+        "is_abnormity",                  # 异形包装
+        "is_cig",                        # 雪茄烟
+        "is_chuangxin",                  # 创新品类
+        "direct_retail_price",           # 卷烟建议零售价
+        "tbc_total_length",              # 烟支总长度
+        "product_style",                 # 包装类型
+
+        "flavor_type_std",                              # 香型_规范
+        "main_color_std",                               # 主色调_规范
+        "carton_packaging_std",                         # 条装包装_规范
+        "packaging_type_std",                           # 包装类型_规范
+        "opening_method_std",                           # 打开方式_规范
+        "cigarette_paper_std",                          # 卷烟纸_规范
+        "cigarette_holder_length_std",                  # 烟嘴长度_规范
+        "flavor_feature_std",                           # 口味特征_规范
+        "new_product_connotation_std",                  # 新品希望传递的品牌内涵_规范
+        "core_competitive_advantage_std",               # 同类核心竞争优势_规范
+        "target_consumers_std",                         # 目标消费者_规范
+        "consumption_scenario_std",                     # 消费场景_规范
+        "core_selling_points_std",                      # 核心卖点_规范
+        "product_features_std",                         # 产品特征_规范
+        "regional_market_preference_match_std",         # 区域市场偏好匹配度_规范
+        "cigarette_holder_std",                         # 烟嘴_规范
+        "target_competitors_std"                        # 目标竞品_规范
+    ]
+
+    FEATURE_ENUM_MAP = {
+        "factory_name":                    ["安徽中烟", "澳门云福卷烟厂", "北欧烟草集团", "博格集团", "重庆中烟", "川渝中烟", "菲利普莫里斯亚洲", 
+                                            "福建中烟", "甘肃工业", "广东中烟", "广西中烟", "贵州中烟", "海南红塔", "河北中烟", "河南中烟", 
+                                            "黑龙江工业", "红塔辽宁烟草", "湖北中烟", "湖南中烟", "吉林工业", "家源开发股份有限公司", 
+                                            "嘉莱赫国际有限公司", "江苏中烟", "江西中烟", "凯德控股有限公司", "力量雪茄烟草有限公司", 
+                                            "南洋兄弟烟草股份", "内蒙古昆明卷烟", "日本烟草(香港)有限公司", "三宝麟国际集团", "厦门调拨站", 
+                                            "山东中烟", "山西昆明烟草", "陕西中烟", "上海烟草(集团)公司", "上海烟草公司", "深圳工业", "四川中烟", 
+                                            "特富意烟草(国际)", "雪茄客烟草国际贸易有限公司", "耀莱雪茄控股有限公司", "引领国际有限公司", 
+                                            "英飞烽香港有限公司", "英美烟草中国有限公司", "云南中烟", "浙江中烟", "中茄国际贸易有限公司", 
+                                            "中烟英美烟草国际有限公司", "株式会社 KT&G", "无"],
+        "brand_name":                      ["万宝路", "555", "骆驼(国外)", "大华", "娇子", "大青山", "龙凤呈祥", "黄鹤楼", "真龙", "七匹狼", 
+                                            "芙蓉王", "双喜(广)", "贵烟", "钓鱼台", "红双喜(南洋)", "云烟", "蒙特", "富恩特", "拉·加莱拉", "苏烟", 
+                                            "丹纳曼", "黄山", "南京", "利群", "金桥", "泰山", "好日子", "石林", "美登", "红河", "嘉辉", "七星", 
+                                            "都彭", "天下秀", "长城", "高希霸", "钻石", "金圣", "王冠雪茄", "黄金叶", "中南海", "长白山", "红旗渠", 
+                                            "建牌", "大卫杜夫", "罗密欧", "茂大", "红金龙", "天子", "熊猫", "双喜(深)", "大前门", "兰州", 
+                                            "红双喜(沪)", "雄狮", "广州", "红玫王", "黄果树", "红塔山", "福", "小熊猫", "爱喜", "蒙特利", "玉溪", 
+                                            "都宝", "麦克纽杜", "卡里罗", "中华", "牡丹(沪)", "阿里山", "顺百利", "白沙", "羊城", "白云", 
+                                            "特美思", "国宾", "帕特加", "比德奥", "冬虫夏草", "威龙(湛江)", "香格里拉", "红梅", "延安", 
+                                            "特富意", "石狮", "金香港", "好猫", "登喜路", "乐迪", "林海灵芝", "椰树", "北京", "大红鹰", "大丰收", 
+                                            "红双喜(武汉)", "五叶神", "狮", "优民", "将军", "遵义", "恒大", "飞马", "红三环", "芙蓉", "工字", 
+                                            "古田", "狮牌", "君力", "哈尔滨", "梦都", "香梅(阜阳)", "哈德门", "梅州", "红山茶", "猴王", "沙龙", 
+                                            "潘趣", "狮子牌", "上海", "红玫", "醒宝", "广州湾", "百乐门", "关塔那摩", "威斯", "五一", "寿百年", 
+                                            "人民大会堂", "土楼", "三沙", "西湖", "光明", "阿诗玛", "宝亨", "恭贺新禧", "长寿", "茶花", "迎客松", 
+                                            "龙烟", "金澳门", "宝岛", "多米尼加之花", "国喜", "金驼", "君特欧", "上游", "幸福", "春城", "吉庆", 
+                                            "黄山松", "黄金龙", "紫气东来", "彼亚赛", "银辉", "潮牌", "庐山", "三峡", "壹支笔", "双叶", "无"],
+        "is_low_tar":                      ["是", "否"],
+        "is_medium":                       ["是", "否"],
+        "is_tiny":                         ["是", "否"],
+        "is_coarse":                       ["是", "否"],
+        "is_exploding_beads":              ["是", "否"],
+        "is_abnormity":                    ["是", "否"],
+        "is_cig":                          ["是", "否"],
+        "is_chuangxin":                    ["是", "否"],
+        "direct_retail_price":             ["0-5", "5-10", "10-15", "15-20", "20-26", "26-30", "30-40",
+                                            "40-50", "50-65", "65-80", "80-100", "100以上", "5-9.9", "10-19.9",
+                                            "20-29.9", "30-39.9", "40-49.9", "50-59.9", "60-69.9", "70-79.9", "80-89.9",
+                                            "90-99.9", "100-109.9", "110-119.9", "120-129.9", "130-139.9", "140-149.9",
+                                            "150-199.9", "200-249.9", "250-499.9", "500以上"],
+        "tbc_total_length":                ["小于79", "80-88", "89-100", "大于120"],
+        "product_style":                   ["条盒硬盒", "条包硬盒", "条盒软盒", "条包软盒", "铁盒", "其他"],
+
+
+        "flavor_type_std": [
+            "醇香", "清甜香", "清香", "果香", "焦甜香", "烤烟", "原香", "荷香", 
+            "本香", "草本香", "甜香", "混合", "薄荷", "陈皮香", "奶甜香", 
+            "浓香", "松香", "药香", "高香", "咖香", "茶香", "外香", "酒香", "无"
+        ],
+        "main_color_std": [
+            "红色", "黄色", "白色", "蓝色", "金色", "黑色", "紫色", "绿色", "灰色", 
+            "青色", "金黄色", "明黄色", "深蓝色", "淡黄色", "米黄色", "咖啡色", 
+            "橙色", "褐色", "浅绿色", "青绿色", "香槟金色", "咖色", "墨绿色", 
+            "银色", "咖红色", "红金色", "蓝金色", "湖蓝色", "金红色", "米白色", 
+            "浅咖色", "蓝白色", "黑白灰色", "浅蓝色", "深棕色", "粉绿色", "米色", 
+            "蓝黑色", "深绿色", "矿青色", "铜青色", "孔雀绿色", "褐红色", "玫瑰红色", 
+            "玫瑰金色", "银灰色", "藏蓝色", "星空蓝色", "深咖啡色", "灰白色", 
+            "青黛色", "正红色", "银绿色", "绛紫色", "海边玻璃蓝色", "乳白色", 
+            "紫蓝色", "橙蓝色", "深红色", "马尔斯绿色", "无"
+        ],
+        "carton_packaging_std": [
+            "常规", "木盒", "硬盒", "标细", "中支", "全开式", "纵开式", "细支", "常规(异形)", "无"
+        ],
+        "packaging_type_std": [
+            "硬包", "软包", "硬盒", "双中支", "软包硬化", "无"
+        ],
+        "opening_method_std": [
+            "常规", "侧开", "全开式", "提拔式", "无"
+        ],
+        "cigarette_paper_std": [
+            "常规", "白色", "功能型", "横罗纹", "竖纹", "全麻", "横纹", "特殊工艺", 
+            "颜色", "黑色", "高克重", "半麻", "螺纹", "棕色", "横螺纹", "味道", 
+            "含麻", "烟草本香卷烟纸", "五星中空滤嘴", "黄色", "无"
+        ],
+        "cigarette_holder_length_std": [
+            "20(不含)-25", "25(不含)-30", "30(不含)-100", "无"
+        ],
+        "flavor_feature_std": [
+            "醇厚", "细腻", "烟草本香", "舒适", "清香", "纯净", "柔和", "甜润", 
+            "清甜", "浓郁", "绵长", "顺滑", "低刺激", "回甘", "满足感强", "轻松", 
+            "饱满", "果香", "焦甜", "干净", "劲头适中", "劲头大", "清新", "花香", 
+            "甘润", "自然", "醇和", "无"
+        ],
+        "new_product_connotation_std": [
+            "品味", "经典", "时尚", "成功", "探索", "文化", "品质", "传承", 
+            "自然", "创新", "尊贵", "健康", "喜庆", "活力", "年轻", "科技", "匠心", "无"
+        ],
+        "core_competitive_advantage_std": [
+            "口感好", "性价比高", "技术创新", "品牌文化", "舒适度高", "香气独特", 
+            "经典传承", "工艺先进", "品质优", "原料优质", "低焦低害", "包装设计", 
+            "满足感强", "无"
+        ],
+        "target_consumers_std": [
+            "普通消费者", "年轻群体", "高端消费者", "中年群体", "商务人士", "追求品质", 
+            "中端消费者", "男女皆宜", "白领", "男性为主", "追求时尚", "追求性价比", 
+            "企业家", "中老年群体", "公务员", "无"
+        ],
+        "consumption_scenario_std": [
+            "日常自吸", "商务接待", "休闲娱乐", "礼品消费", "宴会用烟", "夜场消费", 
+            "婚庆用烟", "节日送礼", "无"
+        ],
+        "core_selling_points_std": [
+            "包装设计", "独特风味", "工艺创新", "原料精选", "品牌文化", "口感舒适", 
+            "身份象征", "满足感强", "性价比高", "低焦减害", "无"
+        ],
+        "product_features_std": [
+            "追求品质", "性价比", "新奇体验", "经典传承", "低焦健康", "时尚潮流", 
+            "高端奢华", "商务风格", "无"
+        ],
+        "regional_market_preference_match_std": [
+            "高GDP地区", "娱乐发达城市", "一线城市", "旅游城市", "二线城市", 
+            "三四线城市", "经济发达城市", "新兴产业城市", "传统工业城市", "无"
+        ],
+        "cigarette_holder_std": [
+            "常规", "爆珠", "复合滤棒", "中空", "视觉设计", "加香", "镂空", "甜味", 
+            "颗粒", "印花", "活性炭", "无"
+        ],
+        "target_competitors_std": [
+            "荷花、黄鹤楼、芙蓉王、南京", "芙蓉王(硬中支)", "黄鹤楼、南京、双喜", 
+            "中华", "钻石(荷花)", "利群(新版)", "中华(双中支)", "芙蓉王(硬)", 
+            "健牌、七星", "高价卷烟", "南京(炫赫门)", "贵烟(跨越)", "中华(软)、中华(双中支)", 
+            "黄鹤楼(视窗)", "中华(金细支)", "芙蓉王(硬细支)", "玉溪(软)", "黄金叶(商鼎)", 
+            "煊赫门", "荷花", "南京(雨花石)", "天子(中支)", "云烟(细支云龙)", 
+            "黄金叶(商鼎)、七匹狼(纯境)", "双喜(硬经典1906)", "七匹狼(纯境)", "云烟(紫)", 
+            "利群(西子阳光)", "中华(金中支)", "七匹狼(软灰)", "荷花细支", "紫云", 
+            "黄鹤楼、芙蓉王、南京", "和天下", "蓝利群", "硬中华", "软中华", 
+            "硬冰爵、硬8°、大观园冰爆", "万宝路、爱喜、箭牌、555", "无"
+        ]
+    }
+    CLEANING_RULES = {
+        "factory_name":                             {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "brand_name":                               {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "is_low_tar":                               {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "is_medium":                                {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "is_tiny":                                  {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "is_coarse":                                {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "is_exploding_beads":                       {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "is_abnormity":                             {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "is_cig":                                   {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "is_chuangxin":                             {"method": "fillna", "opt": "fill", "value": "否", "type": "str"},
+        "direct_retail_price":                      {"method": "fillna", "opt": "fill", "value": "0-5", "type": "str"},
+        "tbc_total_length":                         {"method": "fillna", "opt": "fill", "value": "小于79", "type": "str"},
+        "product_style":                            {"method": "fillna", "opt": "fill", "value": "其他", "type": "str"},
+
+        "flavor_type_std":                          {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "main_color_std":                           {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "carton_packaging_std":                     {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "packaging_type_std":                       {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "opening_method_std":                       {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "cigarette_paper_std":                      {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "cigarette_holder_length_std":              {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "flavor_feature_std":                       {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "new_product_connotation_std":              {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "core_competitive_advantage_std":           {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "target_consumers_std":                     {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "consumption_scenario_std":                 {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "core_selling_points_std":                  {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "product_features_std":                     {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "regional_market_preference_match_std":     {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "cigarette_holder_std":                     {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+        "target_competitors_std":                   {"method": "fillna", "opt": "fill", "value": "无", "type": "str"},
+    }
+
+class OrderConfig:
+    FEATURE_COLUMNS = [
+        "cust_code",                        # 商户编码
+        "product_code",                     # 品规编码
+        "price_tier",
+        "order_number_stability",           # 订购次数稳定性
+        "order_quantity_stability",         # 订购量稳定性
+        "order_ratio_stability",            # 订购占比稳定性
+        "real_demand_stability"             # 真实需求稳定性
+    ]