Selaa lähdekoodia

完成qa管理模块的开发,基本完成测试。

wangxq 1 viikko sitten
vanhempi
commit
3f74546e04

+ 1 - 2
citu_app.py

@@ -2075,8 +2075,7 @@ def qa_feedback_add():
         return jsonify(success_response(
             response_text="反馈记录创建成功",
             data={
-                "feedback_id": feedback_id,
-                "message": "Feedback record created successfully"
+                "feedback_id": feedback_id
             }
         ))
         

+ 4 - 0
common/messages.py

@@ -7,6 +7,7 @@ class MessageTemplate:
     # 客户端错误场景 (4xx)
     BAD_REQUEST = "请求参数错误"
     VALIDATION_FAILED = "参数验证失败"
+    NOT_FOUND = "资源未找到"
     
     # 服务端错误场景 (5xx)
     INTERNAL_ERROR = "系统内部错误"
@@ -32,6 +33,9 @@ class ErrorType:
     MISSING_REQUIRED_PARAMS = "missing_required_params"
     INVALID_PARAMS = "invalid_params"
     
+    # 资源相关错误
+    RESOURCE_NOT_FOUND = "resource_not_found"
+    
     # 系统错误
     DATABASE_ERROR = "database_error"
     NETWORK_ERROR = "network_error" 

+ 394 - 0
common/qa_feedback_manager.py

@@ -0,0 +1,394 @@
+"""
+QA反馈数据管理器 - 复用Vanna连接版本
+用于处理用户对问答结果的点赞/点踩反馈,并将反馈转化为训练数据
+"""
+import app_config
+from sqlalchemy import create_engine, text, MetaData, Table, Column, Integer, String, Boolean, DateTime, func
+from sqlalchemy.exc import OperationalError, ProgrammingError
+from datetime import datetime
+from typing import List, Dict, Any, Optional, Tuple
+import logging
+
+class QAFeedbackManager:
+    """QA反馈数据管理器 - 复用Vanna连接版本"""
+    
+    def __init__(self, vanna_instance=None):
+        """初始化数据库连接
+        
+        Args:
+            vanna_instance: 可选的vanna实例,用于复用其数据库连接
+        """
+        self.engine = None
+        self.vanna_instance = vanna_instance
+        self._init_database_connection()
+        self._ensure_table_exists()
+    
+    def _init_database_connection(self):
+        """初始化数据库连接"""
+        try:
+            # 方案1: 优先尝试复用vanna连接
+            if self.vanna_instance and hasattr(self.vanna_instance, 'engine'):
+                self.engine = self.vanna_instance.engine
+                print(f"[QAFeedbackManager] 复用Vanna数据库连接")
+                return
+            
+            # 方案2: 创建新的连接(原有方式)
+            db_config = app_config.APP_DB_CONFIG
+            connection_string = (
+                f"postgresql://{db_config['user']}:{db_config['password']}"
+                f"@{db_config['host']}:{db_config['port']}/{db_config['dbname']}"
+            )
+            # 使用连接池配置
+            self.engine = create_engine(
+                connection_string, 
+                echo=False,
+                pool_size=5,          # 连接池大小
+                max_overflow=10,      # 最大溢出连接数
+                pool_timeout=30,      # 获取连接超时
+                pool_recycle=3600     # 连接回收时间(1小时)
+            )
+            
+            # 测试连接
+            with self.engine.connect() as conn:
+                conn.execute(text("SELECT 1"))
+            
+            print(f"[QAFeedbackManager] 数据库连接成功: {db_config['host']}:{db_config['port']}/{db_config['dbname']}")
+            
+        except Exception as e:
+            print(f"[ERROR] QAFeedbackManager数据库连接失败: {e}")
+            raise
+    
+    def _ensure_table_exists(self):
+        """检查并创建qa_feedback表"""
+        create_table_sql = """
+        CREATE TABLE IF NOT EXISTS qa_feedback (
+            id SERIAL PRIMARY KEY,
+            question TEXT NOT NULL,
+            sql TEXT NOT NULL,
+            is_thumb_up BOOLEAN NOT NULL,
+            user_id VARCHAR(64) NOT NULL,
+            create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+            is_in_training_data BOOLEAN DEFAULT FALSE,
+            update_time TIMESTAMP
+        );
+        """
+        
+        # 创建索引SQL
+        create_indexes_sql = [
+            "CREATE INDEX IF NOT EXISTS idx_qa_feedback_user_id ON qa_feedback(user_id);",
+            "CREATE INDEX IF NOT EXISTS idx_qa_feedback_create_time ON qa_feedback(create_time);",
+            "CREATE INDEX IF NOT EXISTS idx_qa_feedback_is_thumb_up ON qa_feedback(is_thumb_up);",
+            "CREATE INDEX IF NOT EXISTS idx_qa_feedback_is_in_training ON qa_feedback(is_in_training_data);"
+        ]
+        
+        try:
+            with self.engine.connect() as conn:
+                with conn.begin():
+                    # 创建表
+                    conn.execute(text(create_table_sql))
+                    
+                    # 创建索引
+                    for index_sql in create_indexes_sql:
+                        conn.execute(text(index_sql))
+                    
+            print("[QAFeedbackManager] qa_feedback表检查/创建成功")
+            
+        except Exception as e:
+            print(f"[ERROR] qa_feedback表创建失败: {e}")
+            raise
+    
+    def add_feedback(self, question: str, sql: str, is_thumb_up: bool, user_id: str = "guest") -> int:
+        """添加反馈记录
+        
+        Args:
+            question: 用户问题
+            sql: 生成的SQL
+            is_thumb_up: 是否点赞
+            user_id: 用户ID
+            
+        Returns:
+            新创建记录的ID
+        """
+        insert_sql = """
+        INSERT INTO qa_feedback (question, sql, is_thumb_up, user_id, create_time)
+        VALUES (:question, :sql, :is_thumb_up, :user_id, :create_time)
+        RETURNING id
+        """
+        
+        try:
+            with self.engine.connect() as conn:
+                with conn.begin():
+                    result = conn.execute(text(insert_sql), {
+                        'question': question,
+                        'sql': sql,
+                        'is_thumb_up': is_thumb_up,
+                        'user_id': user_id,
+                        'create_time': datetime.now()
+                    })
+                    feedback_id = result.fetchone()[0]
+                
+            print(f"[QAFeedbackManager] 反馈记录创建成功, ID: {feedback_id}")
+            return feedback_id
+            
+        except Exception as e:
+            print(f"[ERROR] 添加反馈记录失败: {e}")
+            raise
+    
+    def query_feedback(self, page: int = 1, page_size: int = 20, 
+                      is_thumb_up: Optional[bool] = None,
+                      create_time_start: Optional[str] = None,
+                      create_time_end: Optional[str] = None,
+                      is_in_training_data: Optional[bool] = None,
+                      sort_by: str = "create_time",
+                      sort_order: str = "desc") -> Tuple[List[Dict], int]:
+        """查询反馈记录
+        
+        Args:
+            page: 页码 (从1开始)
+            page_size: 每页大小
+            is_thumb_up: 是否点赞筛选
+            create_time_start: 创建时间开始
+            create_time_end: 创建时间结束
+            is_in_training_data: 是否已加入训练数据
+            sort_by: 排序字段
+            sort_order: 排序方向 (asc/desc)
+            
+        Returns:
+            (记录列表, 总数)
+        """
+        # 构建WHERE条件
+        where_conditions = []
+        params = {}
+        
+        if is_thumb_up is not None:
+            where_conditions.append("is_thumb_up = :is_thumb_up")
+            params['is_thumb_up'] = is_thumb_up
+        
+        if create_time_start:
+            where_conditions.append("create_time >= :create_time_start")
+            params['create_time_start'] = create_time_start
+        
+        if create_time_end:
+            where_conditions.append("create_time <= :create_time_end")
+            params['create_time_end'] = create_time_end
+        
+        if is_in_training_data is not None:
+            where_conditions.append("is_in_training_data = :is_in_training_data")
+            params['is_in_training_data'] = is_in_training_data
+        
+        where_clause = "WHERE " + " AND ".join(where_conditions) if where_conditions else ""
+        
+        # 验证排序参数
+        valid_sort_fields = ['id', 'create_time', 'update_time', 'user_id']
+        if sort_by not in valid_sort_fields:
+            sort_by = 'create_time'
+        
+        if sort_order.lower() not in ['asc', 'desc']:
+            sort_order = 'desc'
+        
+        # 计算OFFSET
+        offset = (page - 1) * page_size
+        
+        # 查询数据
+        query_sql = f"""
+        SELECT id, question, sql, is_thumb_up, user_id, create_time, 
+               is_in_training_data, update_time
+        FROM qa_feedback
+        {where_clause}
+        ORDER BY {sort_by} {sort_order.upper()}
+        LIMIT :limit OFFSET :offset
+        """
+        
+        # 查询总数
+        count_sql = f"""
+        SELECT COUNT(*) as total
+        FROM qa_feedback
+        {where_clause}
+        """
+        
+        try:
+            with self.engine.connect() as conn:
+                # 查询数据
+                params.update({'limit': page_size, 'offset': offset})
+                result = conn.execute(text(query_sql), params)
+                records = []
+                
+                for row in result:
+                    records.append({
+                        'id': row.id,
+                        'question': row.question,
+                        'sql': row.sql,
+                        'is_thumb_up': row.is_thumb_up,
+                        'user_id': row.user_id,
+                        'create_time': row.create_time.isoformat() if row.create_time else None,
+                        'is_in_training_data': row.is_in_training_data,
+                        'update_time': row.update_time.isoformat() if row.update_time else None
+                    })
+                
+                # 查询总数
+                count_result = conn.execute(text(count_sql), {k: v for k, v in params.items() if k not in ['limit', 'offset']})
+                total = count_result.fetchone().total
+                
+            return records, total
+            
+        except Exception as e:
+            print(f"[ERROR] 查询反馈记录失败: {e}")
+            raise
+    
+    def delete_feedback(self, feedback_id: int) -> bool:
+        """删除反馈记录
+        
+        Args:
+            feedback_id: 反馈记录ID
+            
+        Returns:
+            删除是否成功
+        """
+        delete_sql = "DELETE FROM qa_feedback WHERE id = :id"
+        
+        try:
+            with self.engine.connect() as conn:
+                with conn.begin():
+                    result = conn.execute(text(delete_sql), {'id': feedback_id})
+                
+                if result.rowcount > 0:
+                    print(f"[QAFeedbackManager] 反馈记录删除成功, ID: {feedback_id}")
+                    return True
+                else:
+                    print(f"[WARNING] 反馈记录不存在, ID: {feedback_id}")
+                    return False
+                    
+        except Exception as e:
+            print(f"[ERROR] 删除反馈记录失败: {e}")
+            raise
+    
+    def update_feedback(self, feedback_id: int, **kwargs) -> bool:
+        """更新反馈记录
+        
+        Args:
+            feedback_id: 反馈记录ID
+            **kwargs: 要更新的字段
+            
+        Returns:
+            更新是否成功
+        """
+        # 允许更新的字段
+        allowed_fields = ['question', 'sql', 'is_thumb_up', 'user_id', 'is_in_training_data']
+        
+        update_fields = []
+        params = {'id': feedback_id, 'update_time': datetime.now()}
+        
+        for field, value in kwargs.items():
+            if field in allowed_fields:
+                update_fields.append(f"{field} = :{field}")
+                params[field] = value
+        
+        if not update_fields:
+            print("[WARNING] 没有有效的更新字段")
+            return False
+        
+        update_fields.append("update_time = :update_time")
+        
+        update_sql = f"""
+        UPDATE qa_feedback 
+        SET {', '.join(update_fields)}
+        WHERE id = :id
+        """
+        
+        try:
+            with self.engine.connect() as conn:
+                with conn.begin():
+                    result = conn.execute(text(update_sql), params)
+                
+                if result.rowcount > 0:
+                    print(f"[QAFeedbackManager] 反馈记录更新成功, ID: {feedback_id}")
+                    return True
+                else:
+                    print(f"[WARNING] 反馈记录不存在或无变化, ID: {feedback_id}")
+                    return False
+                    
+        except Exception as e:
+            print(f"[ERROR] 更新反馈记录失败: {e}")
+            raise
+    
+    def get_feedback_by_ids(self, feedback_ids: List[int]) -> List[Dict]:
+        """根据ID列表获取反馈记录
+        
+        Args:
+            feedback_ids: 反馈记录ID列表
+            
+        Returns:
+            反馈记录列表
+        """
+        if not feedback_ids:
+            return []
+        
+        # 构建IN查询
+        placeholders = ','.join([f':id_{i}' for i in range(len(feedback_ids))])
+        params = {f'id_{i}': feedback_id for i, feedback_id in enumerate(feedback_ids)}
+        
+        query_sql = f"""
+        SELECT id, question, sql, is_thumb_up, user_id, create_time, 
+               is_in_training_data, update_time
+        FROM qa_feedback
+        WHERE id IN ({placeholders})
+        """
+        
+        try:
+            with self.engine.connect() as conn:
+                result = conn.execute(text(query_sql), params)
+                records = []
+                
+                for row in result:
+                    records.append({
+                        'id': row.id,
+                        'question': row.question,
+                        'sql': row.sql,
+                        'is_thumb_up': row.is_thumb_up,
+                        'user_id': row.user_id,
+                        'create_time': row.create_time,
+                        'is_in_training_data': row.is_in_training_data,
+                        'update_time': row.update_time
+                    })
+                
+                return records
+                
+        except Exception as e:
+            print(f"[ERROR] 根据ID查询反馈记录失败: {e}")
+            raise
+    
+    def mark_training_status(self, feedback_ids: List[int], status: bool = True) -> int:
+        """批量标记训练状态
+        
+        Args:
+            feedback_ids: 反馈记录ID列表
+            status: 训练状态
+            
+        Returns:
+            更新的记录数
+        """
+        if not feedback_ids:
+            return 0
+        
+        placeholders = ','.join([f':id_{i}' for i in range(len(feedback_ids))])
+        params = {f'id_{i}': feedback_id for i, feedback_id in enumerate(feedback_ids)}
+        params['status'] = status
+        params['update_time'] = datetime.now()
+        
+        update_sql = f"""
+        UPDATE qa_feedback 
+        SET is_in_training_data = :status, update_time = :update_time
+        WHERE id IN ({placeholders})
+        """
+        
+        try:
+            with self.engine.connect() as conn:
+                with conn.begin():
+                    result = conn.execute(text(update_sql), params)
+                
+                print(f"[QAFeedbackManager] 批量更新训练状态成功, 影响行数: {result.rowcount}")
+                return result.rowcount
+                
+        except Exception as e:
+            print(f"[ERROR] 批量更新训练状态失败: {e}")
+            raise

+ 9 - 0
common/result.py

@@ -289,4 +289,13 @@ def service_unavailable_response(response_text, can_retry=True):
         message=MessageTemplate.SERVICE_UNAVAILABLE,
         code=503,
         can_retry=can_retry
+    )
+
+def not_found_response(response_text):
+    """资源未找到响应"""
+    return error_response(
+        response_text=response_text,
+        error_type=ErrorType.RESOURCE_NOT_FOUND,
+        message=MessageTemplate.NOT_FOUND,
+        code=404
     ) 

+ 503 - 0
docs/QA反馈模块API调用说明.md

@@ -0,0 +1,503 @@
+
+# QA反馈模块API调用说明
+
+## 📋 概述
+
+QA反馈模块提供了完整的用户反馈管理功能,支持用户对问答结果进行点赞/点踩反馈,并将反馈数据转化为训练数据。本模块包含6个主要API端点,支持反馈记录的创建、查询、修改、删除以及训练数据集成。
+
+### 🔧 基础信息
+- **基础URL**: `http://localhost:5000`
+- **API前缀**: `/api/v0/qa_feedback/`
+- **数据格式**: JSON
+- **字符编码**: UTF-8
+
+---
+
+## 🔍 API端点一览
+
+| API端点 | 方法 | 功能描述 |
+|---------|------|----------|
+| `/api/v0/qa_feedback/query` | POST | 查询反馈记录(支持分页、筛选、排序) |
+| `/api/v0/qa_feedback/delete/{id}` | DELETE | 删除指定反馈记录 |
+| `/api/v0/qa_feedback/update/{id}` | PUT | 修改指定反馈记录 |
+| `/api/v0/qa_feedback/add_to_training` | POST | **核心功能**:批量添加到训练集 |
+| `/api/v0/qa_feedback/add` | POST | 创建新的反馈记录 |
+| `/api/v0/qa_feedback/stats` | GET | 获取反馈统计信息 |
+
+---
+
+## 📖 详细API说明
+
+### 1. 查询反馈记录 API
+
+**端点**: `POST /api/v0/qa_feedback/query`
+
+**功能**: 查询反馈记录,支持分页、筛选和排序功能,主要用于审核页面展示反馈数据。
+
+#### 📝 请求参数
+
+| 参数名 | 类型 | 必填 | 默认值 | 说明 |
+|--------|------|------|--------|------|
+| `page` | int | 否 | 1 | 页码(从1开始) |
+| `page_size` | int | 否 | 20 | 每页记录数(范围:1-100) |
+| `is_thumb_up` | boolean | 否 | null | 筛选点赞状态(true=点赞,false=点踩) |
+| `create_time_start` | string | 否 | null | 创建时间开始(格式:YYYY-MM-DD) |
+| `create_time_end` | string | 否 | null | 创建时间结束(格式:YYYY-MM-DD) |
+| `is_in_training_data` | boolean | 否 | null | 是否已加入训练数据 |
+| `sort_by` | string | 否 | "create_time" | 排序字段(id/create_time/update_time/user_id) |
+| `sort_order` | string | 否 | "desc" | 排序方向(asc/desc) |
+
+#### 🌰 请求示例
+
+**基础查询**:
+```json
+{
+  "page": 1,
+  "page_size": 10
+}
+```
+
+**完整筛选查询**:
+```json
+{
+  "page": 1,
+  "page_size": 20,
+  "is_thumb_up": true,
+  "create_time_start": "2024-01-01",
+  "create_time_end": "2024-12-31",
+  "is_in_training_data": false,
+  "sort_by": "create_time",
+  "sort_order": "desc"
+}
+```
+
+**查询未训练的负向反馈**:
+```json
+{
+  "is_thumb_up": false,
+  "is_in_training_data": false,
+  "sort_by": "create_time",
+  "sort_order": "asc"
+}
+```
+
+#### ✅ 成功响应格式
+
+```json
+{
+  "code": 200,
+  "success": true,
+  "message": "查询成功,共找到 25 条记录",
+  "data": {
+    "records": [
+      {
+        "id": 1,
+        "question": "查询所有用户信息",
+        "sql": "SELECT * FROM users",
+        "is_thumb_up": true,
+        "user_id": "user123",
+        "create_time": "2024-06-24T10:30:00",
+        "is_in_training_data": false,
+        "update_time": null
+      }
+    ],
+    "pagination": {
+      "page": 1,
+      "page_size": 20,
+      "total": 25,
+      "total_pages": 2,
+      "has_next": true,
+      "has_prev": false
+    }
+  }
+}
+```
+
+---
+
+### 2. 删除反馈记录 API
+
+**端点**: `DELETE /api/v0/qa_feedback/delete/{id}`
+
+**功能**: 根据记录ID删除指定的反馈记录。
+
+#### 📝 路径参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `id` | int | 是 | 反馈记录的ID |
+
+#### 🌰 请求示例
+
+```
+DELETE /api/v0/qa_feedback/delete/123
+```
+
+#### ✅ 成功响应格式
+
+```json
+{
+  "code": 200,
+  "success": true,
+  "message": "操作成功",
+  "data": {
+    "response": "反馈记录删除成功",
+    "deleted_id": 123
+  }
+}
+```
+
+#### ❌ 失败响应格式
+
+```json
+{
+  "code": 404,
+  "success": false,
+  "message": "资源未找到",
+  "data": {
+    "response": "反馈记录不存在 (ID: 123)",
+    "timestamp": "2024-06-24T10:30:00"
+  }
+}
+```
+
+---
+
+### 3. 修改反馈记录 API
+
+**端点**: `PUT /api/v0/qa_feedback/update/{id}`
+
+**功能**: 修改指定反馈记录的内容。
+
+#### 📝 路径参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `id` | int | 是 | 反馈记录的ID |
+
+#### 📝 请求参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `question` | string | 否 | 问题内容 |
+| `sql` | string | 否 | SQL内容 |
+| `is_thumb_up` | boolean | 否 | 是否点赞 |
+| `user_id` | string | 否 | 用户ID |
+| `is_in_training_data` | boolean | 否 | 是否已加入训练数据 |
+
+#### 🌰 请求示例
+
+**修改问题和SQL**:
+```json
+{
+  "question": "查询活跃用户信息",
+  "sql": "SELECT * FROM users WHERE status = 'active'"
+}
+```
+
+**修改反馈状态**:
+```json
+{
+  "is_thumb_up": false,
+  "is_in_training_data": true
+}
+```
+
+#### ✅ 成功响应格式
+
+```json
+{
+  "code": 200,
+  "success": true,
+  "message": "操作成功",
+  "data": {
+    "response": "反馈记录更新成功",
+    "updated_id": 123,
+    "updated_fields": ["question", "sql"]
+  }
+}
+```
+
+---
+
+### 4. 添加到训练数据集 API ⭐
+
+**端点**: `POST /api/v0/qa_feedback/add_to_training`
+
+**功能**: **核心功能**,将反馈记录批量添加到训练数据集。支持混合处理:正向反馈(点赞)加入SQL训练集,负向反馈(点踩)加入错误SQL训练集。
+
+#### 📝 请求参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|------|------|
+| `feedback_ids` | array[int] | 是 | 反馈记录ID列表 |
+
+#### 🌰 请求示例
+
+**批量添加训练数据**:
+```json
+{
+  "feedback_ids": [17]
+}
+```
+
+#### ✅ 成功响应格式
+
+```json
+{
+    "code": 200,
+    "data": {
+        "response": "训练数据添加完成,成功处理 1 条记录",
+        "successfully_trained_ids": [
+            17
+        ],
+        "summary": {
+            "already_trained": 0,
+            "errors": 0,
+            "negative_trained": 1,
+            "positive_trained": 0,
+            "total_processed": 1,
+            "total_requested": 1
+        },
+        "training_details": {
+            "error_sql_training_count": 1,
+            "sql_training_count": 0
+        }
+    },
+    "message": "操作成功",
+    "success": true
+}
+```
+
+#### 🔄 处理逻辑说明
+
+- **正向反馈** (`is_thumb_up=true`) → 调用 `vn.train(question, sql)` 
+- **负向反馈** (`is_thumb_up=false`) → 调用 `vn.train_error_sql(question, sql)`
+- **已训练记录** → 跳过处理
+- **训练成功** → 自动标记 `is_in_training_data=true`
+
+---
+
+### 5. 创建反馈记录 API
+
+**端点**: `POST /api/v0/qa_feedback/add`
+
+**功能**: 创建新的反馈记录,通常由前端在用户点赞/点踩时调用。
+
+#### 📝 请求参数
+
+| 参数名 | 类型 | 必填 | 默认值 | 说明 |
+|--------|------|------|--------|------|
+| `question` | string | 是 | - | 用户问题 |
+| `sql` | string | 是 | - | 生成的SQL |
+| `is_thumb_up` | boolean | 是 | - | 是否点赞 |
+| `user_id` | string | 否 | "guest" | 用户ID |
+
+#### 🌰 请求示例
+
+**用户点赞示例**:
+```json
+{
+  "question": "查询所有部门信息",
+  "sql": "SELECT * FROM departments",
+  "is_thumb_up": true,
+  "user_id": "user123"
+}
+```
+
+**用户点踩示例**:
+```json
+{
+  "question": "统计每个部门的员工数量",
+  "sql": "SELECT department, COUNT(*) FROM employees",
+  "is_thumb_up": false,
+  "user_id": "user456"
+}
+```
+
+#### ✅ 成功响应格式
+
+```json
+{
+    "code": 200,
+    "data": {
+        "feedback_id": 18,
+        "response": "反馈记录创建成功"
+    },
+    "message": "操作成功",
+    "success": true
+}
+```
+
+---
+
+### 6. 反馈统计信息 API
+
+**端点**: `GET /api/v0/qa_feedback/stats`
+
+**功能**: 获取反馈数据的统计信息,用于监控面板和数据分析。
+
+#### 🌰 请求示例
+
+```
+GET /api/v0/qa_feedback/stats
+```
+
+#### ✅ 成功响应格式
+
+```json
+{
+    "code": 200,
+    "data": {
+        "negative_feedback": 10,
+        "positive_feedback": 8,
+        "positive_rate": 44.44,
+        "response": "统计信息获取成功",
+        "total_feedback": 18,
+        "trained_feedback": 5,
+        "training_rate": 27.78,
+        "untrained_feedback": 13
+    },
+    "message": "操作成功",
+    "success": true
+}
+```
+
+#### 📊 统计字段说明
+
+| 字段名 | 说明 |
+|--------|------|
+| `total_feedback` | 总反馈数 |
+| `positive_feedback` | 正向反馈数(点赞) |
+| `negative_feedback` | 负向反馈数(点踩) |
+| `trained_feedback` | 已训练反馈数 |
+| `untrained_feedback` | 未训练反馈数 |
+| `positive_rate` | 正向反馈率(%) |
+| `training_rate` | 训练覆盖率(%) |
+
+---
+
+## 🔧 使用流程示例
+
+### 典型工作流程
+
+1. **用户反馈阶段**
+   ```json
+   POST /api/v0/qa_feedback/add
+   {
+     "question": "查询用户订单",
+     "sql": "SELECT * FROM orders WHERE user_id = 123",
+     "is_thumb_up": true,
+     "user_id": "user123"
+   }
+   ```
+
+2. **审核管理阶段**
+   ```json
+   POST /api/v0/qa_feedback/query
+   {
+     "is_in_training_data": false,
+     "page": 1,
+     "page_size": 50
+   }
+   ```
+
+3. **批量训练阶段**
+   ```json
+   POST /api/v0/qa_feedback/add_to_training
+   {
+     "feedback_ids": [1, 2, 3, 4, 5]
+   }
+   ```
+
+4. **统计监控阶段**
+   ```
+   GET /api/v0/qa_feedback/stats
+   ```
+
+---
+
+## ⚠️ 错误处理
+
+### 常见错误响应
+
+**400 - 请求参数错误**
+```json
+{
+  "code": 400,
+  "success": false,
+  "message": "请求参数错误",
+  "data": {
+    "response": "缺少必需参数:question",
+    "missing_params": ["question"],
+    "error_type": "missing_required_params",
+    "timestamp": "2024-06-24T10:30:00"
+  }
+}
+```
+
+**404 - 资源未找到**
+```json
+{
+  "code": 404,
+  "success": false,
+  "message": "资源未找到",
+  "data": {
+    "response": "反馈记录不存在 (ID: 999)",
+    "error_type": "resource_not_found",
+    "timestamp": "2024-06-24T10:30:00"
+  }
+}
+```
+
+**500 - 系统内部错误**
+```json
+{
+  "code": 500,
+  "success": false,
+  "message": "系统内部错误",
+  "data": {
+    "response": "查询反馈记录失败,请稍后重试",
+    "error_type": "database_error",
+    "can_retry": true,
+    "timestamp": "2024-06-24T10:30:00"
+  }
+}
+```
+
+---
+
+## 🚀 Postman 测试集合
+
+### 环境变量
+```json
+{
+  "base_url": "http://localhost:5000",
+  "api_prefix": "/api/v0/qa_feedback"
+}
+```
+
+### 测试用例建议
+
+1. **数据创建测试** - 先添加几条反馈记录
+2. **查询功能测试** - 测试各种筛选和排序组合
+3. **更新功能测试** - 修改记录内容
+4. **训练集成测试** - 批量添加到训练数据
+5. **统计功能测试** - 验证统计数据准确性
+6. **删除功能测试** - 清理测试数据
+
+---
+
+## 📝 注意事项
+
+1. **数据库自动创建**: 首次调用任何API时,系统会自动创建 `qa_feedback` 表
+2. **连接复用**: 系统优先复用现有的Vanna数据库连接,提高性能
+3. **事务安全**: 所有写操作都使用数据库事务,确保数据一致性
+4. **分页限制**: 查询API的 `page_size` 最大值为100,避免单次返回过多数据
+5. **训练幂等性**: 同一记录不会重复训练,系统会自动跟踪训练状态
+
+---
+
+## 🎯 总结
+
+QA反馈模块提供了完整的反馈数据生命周期管理,从用户反馈收集到训练数据集成,支持高效的数据处理和智能的训练优化。通过合理使用这些API,可以构建强大的用户反馈系统,持续改进AI模型的表现。 

+ 164 - 0
docs/qa反馈系统设计方案.md

@@ -0,0 +1,164 @@
+# QA反馈系统集成指南 - 复用Vanna连接
+
+需求:
+请检查我在知识库的代码,我希望在citu_app.py添加一组API,实现下面的功能:
+1.我会在app_db中创建一个表:
+这个表用来存储用户给question和sql点赞的场景:
+CREATE TABLE qa_feedback ( id SERIAL PRIMARY KEY, -- 主键,自增 question TEXT NOT NULL, -- 问题内容 sql TEXT NOT NULL, -- 生成的SQL is_thumb_up BOOLEAN NOT NULL, -- 是否点赞(true=点赞,false=点踩) user_id VARCHAR(64) NOT NULL, -- 用户ID create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- 创建时间 is_in_training_data BOOLEAN DEFAULT FALSE, -- 是否已加入数据集 update_time TIMESTAMP -- 更新时间 );
+2.围绕着表和添加训练数据集的功能,添加下面的API:
+a.) 查询API(包括分页和排序),在审核页面中,可以列出当前所有点赞的数据,我需要一个Post API,可以对点赞数据执行查询和分页,以及排序。
+
+ 包括 is_thumb_up,create_time 时间范围,is_in_training_data等等。
+b.) 删除API,选定某个id,点击删除。
+c.) 添加到训练数据集的API,只需要question:sql 两个字段,但是需要知道是正向,还是负向。
+d.) 修改API,允许修改后提交,一个API,update某条记录。
+请先理解我上面的需求,阅读citu_app.py,以及与它相关的代码,然后,再进行设计工作,在在进行设计时请注意:访问数据库,以及写入训练集数据,应该都有现成的function,你要尽量复用这些方法。另外写入数据集时,有两种数据,点赞的是写入SQL,点负面的是写入到error_sql.
+请先不要写代码,先理解需求,了解限制,进行设计,然后与我讨论设计。
+
+
+## 📁 文件结构
+
+```
+项目根目录/
+├── common/
+│   ├── qa_feedback_manager.py          # 新增:反馈数据管理器
+│   └── ...其他common模块
+├── citu_app.py                         # 修改:添加API端点
+└── app_config.py                       # 无需修改(已有APP_DB_CONFIG)
+```
+
+## 🔧 连接管理优势
+
+### ✅ 方案一特点:
+- **智能连接复用**:优先使用现有vanna连接,降低资源占用
+- **自动降级**:vanna连接不可用时自动创建新连接
+- **零配置集成**:无需额外配置,与现有架构无缝兼容
+- **性能优化**:减少数据库连接数,提高系统性能
+
+### 📊 连接逻辑:
+```
+1. 尝试获取vanna实例 ✓
+   ├─ 成功:复用vanna.engine
+   └─ 失败:创建独立连接池
+2. 测试连接有效性 ✓
+3. 自动创建qa_feedback表 ✓
+```
+
+## 🚀 集成步骤
+
+### 1. 放置反馈管理器文件
+将 `qa_feedback_manager.py` 放在 `common/` 目录下。
+
+### 2. 修改 citu_app.py
+
+#### 2.1 添加导入语句
+```python
+# 在现有import后添加
+from common.qa_feedback_manager import QAFeedbackManager
+from common.result import success_response, bad_request_response, not_found_response, internal_error_response
+```
+
+#### 2.2 添加管理器初始化函数
+```python
+# 全局反馈管理器实例
+qa_feedback_manager = None
+
+def get_qa_feedback_manager():
+    """获取QA反馈管理器实例(懒加载)- 复用Vanna连接版本"""
+    global qa_feedback_manager
+    if qa_feedback_manager is None:
+        try:
+            # 优先尝试复用vanna连接
+            vanna_instance = None
+            try:
+                # 尝试获取现有的vanna实例
+                if 'get_citu_langraph_agent' in globals():
+                    agent = get_citu_langraph_agent()
+                    if hasattr(agent, 'vn'):
+                        vanna_instance = agent.vn
+                elif 'vn' in globals():
+                    vanna_instance = vn
+                else:
+                    print("[INFO] 未找到可用的vanna实例,将创建新的数据库连接")
+            except Exception as e:
+                print(f"[INFO] 获取vanna实例失败: {e},将创建新的数据库连接")
+                vanna_instance = None
+            
+            qa_feedback_manager = QAFeedbackManager(vanna_instance=vanna_instance)
+            print("[CITU_APP] QA反馈管理器实例创建成功")
+        except Exception as e:
+            print(f"[CRITICAL] QA反馈管理器创建失败: {str(e)}")
+            raise Exception(f"QA反馈管理器初始化失败: {str(e)}")
+    return qa_feedback_manager
+```
+
+#### 2.3 添加所有API端点
+将完整集成示例中的所有6个API函数复制到 `citu_app.py` 文件末尾。
+
+## 🔧 API端点一览
+
+| API端点 | 方法 | 功能 |
+|---------|------|------|
+| `/api/v0/qa_feedback/query` | POST | 查询反馈记录(分页、筛选、排序) |
+| `/api/v0/qa_feedback/delete/{id}` | DELETE | 删除反馈记录 |
+| `/api/v0/qa_feedback/update/{id}` | PUT | 修改反馈记录 |
+| `/api/v0/qa_feedback/add_to_training` | POST | **核心功能**:批量添加到训练集 |
+| `/api/v0/qa_feedback/add` | POST | 创建反馈记录 |
+| `/api/v0/qa_feedback/stats` | GET | 统计信息 |
+
+## 💡 核心功能:混合批量训练
+
+```http
+POST /api/v0/qa_feedback/add_to_training
+Content-Type: application/json
+
+{
+  "feedback_ids": [1, 2, 3, 4, 5]
+}
+```
+
+**自动分类处理:**
+- ✅ `is_thumb_up=true` → `vn.train(question, sql)` (正向训练)
+- ❌ `is_thumb_up=false` → `vn.train_error_sql(question, sql)` (负向训练)
+
+## 🧪 验证安装
+
+### 启动服务器后检查:
+1. **连接复用日志**:
+   ```
+   [QAFeedbackManager] 复用Vanna数据库连接
+   [QAFeedbackManager] qa_feedback表检查/创建成功
+   ```
+
+2. **测试API**:
+   ```bash
+   # 获取统计信息
+   curl http://localhost:5000/api/v0/qa_feedback/stats
+   
+   # 应返回:
+   {
+     "success": true,
+     "data": {
+       "total_feedback": 0,
+       "positive_feedback": 0,
+       "negative_feedback": 0,
+       ...
+     }
+   }
+   ```
+
+## ⚠️ 注意事项
+
+1. **连接复用逻辑**:系统会自动尝试复用vanna连接,失败时自动创建新连接
+2. **数据库权限**:确保APP_DB_CONFIG配置的用户有创建表和索引的权限
+3. **训练集成**:需要确保vn实例已正确初始化,包含train()和train_error_sql()方法
+4. **性能监控**:复用连接模式下,所有数据库操作共享连接池,请关注连接池状态
+
+## 🎯 工作流程
+
+1. **用户反馈** → 点赞/点踩生成反馈记录
+2. **审核管理** → 使用查询API筛选待处理记录  
+3. **批量训练** → 选择记录调用训练API
+4. **状态跟踪** → 系统自动标记训练状态,避免重复训练
+
+恭喜!现在你的QA反馈系统已经完成集成,可以开始使用了!🎉

+ 62 - 0
output/metadata.txt

@@ -0,0 +1,62 @@
+-- Schema Tools生成的主题元数据
+-- 业务背景: 高速公路服务区管理系统
+-- 生成时间: 2025-06-24 10:55:00
+-- 数据库: highway_db
+
+-- 创建表(如果不存在)
+CREATE TABLE IF NOT EXISTS metadata (
+    id SERIAL PRIMARY KEY,
+    topic_name VARCHAR(100) NOT NULL,
+    description TEXT,
+    related_tables TEXT[],
+    keywords TEXT[],
+    focus_areas TEXT[],
+    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+);
+
+-- 插入主题数据
+INSERT INTO metadata(topic_name, description, related_tables, keywords, focus_areas) VALUES
+(
+  '日营收分析',
+  '分析各服务区每日营收构成及支付方式占比,评估经营效能',
+  '{bss_business_day_data,bss_service_area}',
+  '{营收趋势,支付占比,服务对比}',
+  '{收入趋势,服务区对比,支付方式分布}'
+);
+
+INSERT INTO metadata(topic_name, description, related_tables, keywords, focus_areas) VALUES
+(
+  '车流统计',
+  '统计各服务区车辆通行量及类型分布,优化资源调度',
+  '{bss_car_day_count,bss_service_area}',
+  '{车流量,车型分布,时段分析}',
+  '{日车流量,车型占比,高峰时段}'
+);
+
+INSERT INTO metadata(topic_name, description, related_tables, keywords, focus_areas) VALUES
+(
+  '公司绩效',
+  '对比不同运营公司下属服务区的营收及车流指标,评估经营质量',
+  '{bss_company,bss_service_area,bss_business_day_data}',
+  '{公司对比,营收排名,运营效率}',
+  '{营收排名,车流对比,单位产值}'
+);
+
+INSERT INTO metadata(topic_name, description, related_tables, keywords, focus_areas) VALUES
+(
+  '路段关联',
+  '分析路段与服务区的关联数据,评估路段车流对营收的影响',
+  '{bss_section_route,bss_section_route_area_link,bss_car_day_count}',
+  '{路段分析,车流转化,路网关联}',
+  '{路段车流,服务区转化率,高峰时段关联}'
+);
+
+INSERT INTO metadata(topic_name, description, related_tables, keywords, focus_areas) VALUES
+(
+  '数据质量',
+  '校验不同来源系统的服务区数据一致性,确保数据准确性',
+  '{bss_service_area,bss_service_area_mapper}',
+  '{数据校验,系统比对,编码一致性}',
+  '{编码一致性,系统差异,数据完整性}'
+);
+

+ 158 - 0
output/qs_highway_db_20250624_105500_pair.json

@@ -0,0 +1,158 @@
+[
+  {
+    "question": "近30天各服务区总营收排名TOP10",
+    "sql": "SELECT b.service_area_name AS 服务区名称, SUM(a.pay_sum) AS 总营收 FROM bss_business_day_data a JOIN bss_service_area b ON a.service_name = b.service_area_name WHERE a.oper_date >= CURRENT_DATE - INTERVAL '30 days' AND b.delete_ts IS NULL GROUP BY b.service_area_name ORDER BY 总营收 DESC LIMIT 10;"
+  },
+  {
+    "question": "各服务区微信支付金额占总营收比例分析",
+    "sql": "SELECT service_name AS 服务区名称, SUM(wx)/SUM(pay_sum)*100 AS 微信占比 FROM bss_business_day_data WHERE oper_date = '2023-09-01' AND delete_ts IS NULL GROUP BY service_name ORDER BY 微信占比 DESC;"
+  },
+  {
+    "question": "国庆黄金周(2023-10-01至2023-10-07)营收最高的五个服务区",
+    "sql": "SELECT service_name AS 服务区名称, SUM(pay_sum) AS 总营收 FROM bss_business_day_data WHERE oper_date BETWEEN '2023-10-01' AND '2023-10-07' AND delete_ts IS NULL GROUP BY service_name ORDER BY 总营收 DESC LIMIT 5;"
+  },
+  {
+    "question": "最近一个月每日总营收变化趋势图",
+    "sql": "SELECT oper_date AS 统计日期, SUM(pay_sum) AS 当日营收 FROM bss_business_day_data WHERE oper_date >= CURRENT_DATE - INTERVAL '1 month' AND delete_ts IS NULL GROUP BY oper_date ORDER BY 统计日期;"
+  },
+  {
+    "question": "各运营公司下属服务区平均营收对比",
+    "sql": "SELECT c.company_name AS 运营公司, AVG(a.pay_sum) AS 平均营收 FROM bss_business_day_data a JOIN bss_service_area b ON a.service_name = b.service_area_name JOIN bss_company c ON b.company_id = c.id WHERE a.oper_date = '2023-09-01' AND a.delete_ts IS NULL GROUP BY c.company_name;"
+  },
+  {
+    "question": "现金支付占比超过20%的服务区清单",
+    "sql": "SELECT service_name AS 服务区名称, SUM(rmb)/SUM(pay_sum)*100 AS 现金占比 FROM bss_business_day_data WHERE oper_date = '2023-09-01' AND delete_ts IS NULL GROUP BY service_name HAVING SUM(rmb)/SUM(pay_sum)*100 > 20 ORDER BY 现金占比 DESC;"
+  },
+  {
+    "question": "第三季度(7-9月)各月订单量变化趋势",
+    "sql": "SELECT EXTRACT(MONTH FROM oper_date) AS 月份, SUM(order_sum) AS 月订单量 FROM bss_business_day_data WHERE EXTRACT(YEAR FROM oper_date) = 2023 AND EXTRACT(MONTH FROM oper_date) BETWEEN 7 AND 9 AND delete_ts IS NULL GROUP BY 月份 ORDER BY 月份;"
+  },
+  {
+    "question": "庐山服务区与宜春服务区近7日营收对比",
+    "sql": "SELECT service_name AS 服务区名称, oper_date AS 统计日期, pay_sum AS 当日营收 FROM bss_business_day_data WHERE service_name IN ('庐山服务区', '宜春服务区') AND oper_date >= CURRENT_DATE - INTERVAL '7 days' AND delete_ts IS NULL ORDER BY 统计日期 DESC;"
+  },
+  {
+    "question": "连续3日营收超5万元的服务区清单",
+    "sql": "SELECT service_name AS 服务区名称 FROM (SELECT service_name, COUNT(*) AS 高营收天数 FROM bss_business_day_data WHERE pay_sum > 50000 AND oper_date >= CURRENT_DATE - INTERVAL '7 days' AND delete_ts IS NULL GROUP BY service_name) t WHERE 高营收天数 >= 3;"
+  },
+  {
+    "question": "各支付方式订单数量占比分析",
+    "sql": "SELECT SUM(wx_order) AS 微信订单, SUM(zf_order) AS 支付宝订单, SUM(rmb_order) AS 现金订单, SUM(xs_order) AS 行吧订单, SUM(jd_order) AS 金豆订单 FROM bss_business_day_data WHERE oper_date = '2023-09-01' AND delete_ts IS NULL;"
+  },
+  {
+    "question": "统计各服务区2023年4月每日平均车流量,并按平均流量降序排列",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, AVG(dc.customer_count) AS 日均车流量 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id WHERE dc.count_date BETWEEN '2023-04-01' AND '2023-04-30' AND dc.delete_ts IS NULL AND sa.delete_ts IS NULL GROUP BY sa.service_area_name ORDER BY 日均车流量 DESC;"
+  },
+  {
+    "question": "分析2023年危化品车辆在各服务区的通行占比,仅显示占比超过5%的服务区",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, COUNT(*) AS 总车次, SUM(CASE WHEN dc.car_type='危化品' THEN 1 ELSE 0 END)::DECIMAL/COUNT(*) AS 危化品占比 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id WHERE dc.count_date >= '2023-01-01' AND dc.delete_ts IS NULL AND sa.delete_ts IS NULL GROUP BY sa.service_area_name HAVING SUM(CASE WHEN dc.car_type='危化品' THEN 1 ELSE 0 END)::DECIMAL/COUNT(*) > 0.05;"
+  },
+  {
+    "question": "查询2023年车流量TOP10服务区及对应所属公司信息",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, comp.company_name AS 所属公司, SUM(dc.customer_count) AS 总车流量 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id JOIN bss_company comp ON sa.company_id = comp.id WHERE dc.count_date >= '2023-01-01' AND dc.delete_ts IS NULL GROUP BY sa.service_area_name, comp.company_name ORDER BY 总车流量 DESC LIMIT 10;"
+  },
+  {
+    "question": "统计最近7天各日期过境车辆占比变化趋势",
+    "sql": "SELECT count_date AS 统计日期, SUM(CASE WHEN car_type='过境' THEN customer_count ELSE 0 END)::DECIMAL/SUM(customer_count) AS 过境占比 FROM bss_car_day_count WHERE count_date >= CURRENT_DATE - 7 AND delete_ts IS NULL GROUP BY count_date ORDER BY count_date;"
+  },
+  {
+    "question": "对比2023年Q1季度城际车辆与过境车辆月均流量差异",
+    "sql": "SELECT TO_CHAR(count_date, 'YYYY-MM') AS 月份, AVG(CASE WHEN car_type='城际' THEN customer_count ELSE 0 END) AS 城际月均流量, AVG(CASE WHEN car_type='过境' THEN customer_count ELSE 0 END) AS 过境月均流量 FROM bss_car_day_count WHERE count_date BETWEEN '2023-01-01' AND '2023-03-31' AND delete_ts IS NULL GROUP BY TO_CHAR(count_date, 'YYYY-MM') ORDER BY 月份;"
+  },
+  {
+    "question": "查询2023年3月15日各服务区车流量及环比增长率",
+    "sql": "WITH daily_total AS (SELECT service_area_id, count_date, SUM(customer_count) AS total_count FROM bss_car_day_count WHERE count_date IN ('2023-03-15', '2023-03-08') AND delete_ts IS NULL GROUP BY service_area_id, count_date) SELECT d1.service_area_id AS 服务区ID, d1.total_count AS 当日流量, d2.total_count AS 上周同期流量, (d1.total_count - d2.total_count)/d2.total_count::DECIMAL AS 环比增长率 FROM daily_total d1 JOIN daily_total d2 ON d1.service_area_id = d2.service_area_id AND d1.count_date = '2023-03-15' AND d2.count_date = '2023-03-08';"
+  },
+  {
+    "question": "分析各服务区不同时段(早/中/晚)车流量分布,取最近30天数据",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, EXTRACT(HOUR FROM dc.create_ts)::INT / 8 AS 时段段, SUM(dc.customer_count) AS 总车流量 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id WHERE dc.count_date >= CURRENT_DATE - 30 AND dc.delete_ts IS NULL GROUP BY sa.service_area_name, 时段段 ORDER BY 服务区名称, 时段段;"
+  },
+  {
+    "question": "统计连续3天日车流量超过1000辆的服务区及出现次数",
+    "sql": "WITH daily_filter AS (SELECT service_area_id, count_date, SUM(customer_count) AS total_count FROM bss_car_day_count WHERE delete_ts IS NULL GROUP BY service_area_id, count_date HAVING SUM(customer_count) > 1000) SELECT service_area_id, COUNT(*) AS 超量次数 FROM (SELECT *, COUNT(*) OVER (PARTITION BY service_area_id ORDER BY count_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS streak FROM daily_filter) t WHERE streak >= 3 GROUP BY service_area_id;"
+  },
+  {
+    "question": "查询2023年各车型月度通行量变化趋势",
+    "sql": "SELECT TO_CHAR(count_date, 'YYYY-MM') AS 月份, car_type AS 车型, SUM(customer_count) AS 通行量 FROM bss_car_day_count WHERE count_date >= '2023-01-01' AND delete_ts IS NULL GROUP BY TO_CHAR(count_date, 'YYYY-MM'), car_type ORDER BY 月份, 车型;"
+  },
+  {
+    "question": "分析各公司管理服务区的车流密度(车流量/服务区数量)",
+    "sql": "SELECT comp.company_name AS 公司名称, SUM(dc.customer_count) AS 总车流量, COUNT(DISTINCT sa.id) AS 服务区数量, SUM(dc.customer_count)/COUNT(DISTINCT sa.id)::DECIMAL AS 车流密度 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id JOIN bss_company comp ON sa.company_id = comp.id WHERE dc.count_date >= '2023-01-01' AND dc.delete_ts IS NULL GROUP BY comp.company_name;"
+  },
+  {
+    "question": "统计各运营公司下属服务区最近一周总营收排名(按公司维度)",
+    "sql": "SELECT c.company_name AS 公司名称, SUM(b.pay_sum) AS 总营收 FROM bss_business_day_data b JOIN bss_service_area s ON b.service_no = s.service_area_no JOIN bss_company c ON s.company_id = c.id WHERE b.oper_date >= CURRENT_DATE - 7 AND c.delete_ts IS NULL GROUP BY c.company_name ORDER BY 总营收 DESC LIMIT 5;"
+  },
+  {
+    "question": "对比不同运营公司2023年Q1季度日均车流量(按车辆类型分类)",
+    "sql": "SELECT c.company_name AS 公司名称, car_type AS 车辆类型, AVG(customer_count) AS 日均车流量 FROM bss_car_day_count cc JOIN bss_service_area sa ON cc.service_area_id = sa.id JOIN bss_company c ON sa.company_id = c.id WHERE cc.count_date BETWEEN '2023-01-01' AND '2023-03-31' GROUP BY c.company_name, car_type ORDER BY 公司名称, 日均车流量 DESC;"
+  },
+  {
+    "question": "计算各公司下属服务区单位产值(每辆车产生营收)TOP3",
+    "sql": "SELECT c.company_name AS 公司名称, sa.service_area_name AS 服务区名称, SUM(b.pay_sum)/NULLIF(SUM(cc.customer_count),0) AS 单位产值 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id JOIN bss_car_day_count cc ON sa.id = cc.service_area_id AND b.oper_date = cc.count_date WHERE b.oper_date = CURRENT_DATE -1 GROUP BY c.company_name, sa.service_area_name ORDER BY 单位产值 DESC LIMIT 3;"
+  },
+  {
+    "question": "查询2023年营收未达标(低于平均值20%)的服务区及所属公司信息",
+    "sql": "WITH avg_revenue AS (SELECT AVG(pay_sum) AS avg_val FROM bss_business_day_data WHERE oper_date BETWEEN '2023-01-01' AND '2023-12-31') SELECT sa.service_area_name AS 服务区名称, c.company_name AS 公司名称, SUM(b.pay_sum) AS 总营收 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id GROUP BY sa.service_area_name, c.company_name HAVING SUM(b.pay_sum) < (SELECT avg_val*0.8 FROM avg_revenue) ORDER BY 总营收 ASC;"
+  },
+  {
+    "question": "统计各公司不同支付方式占比(微信/支付宝/现金)",
+    "sql": "SELECT c.company_name AS 公司名称, '微信' AS 支付方式, SUM(wx)/SUM(pay_sum)*100 AS 占比 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id GROUP BY c.company_name UNION ALL SELECT c.company_name, '支付宝', SUM(zfb)/SUM(pay_sum)*100 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id GROUP BY c.company_name UNION ALL SELECT c.company_name, '现金', SUM(rmb)/SUM(pay_sum)*100 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id GROUP BY c.company_name ORDER BY 公司名称, 占比 DESC;"
+  },
+  {
+    "question": "查询当前在运营的服务区数量及对应公司(服务状态为开放)",
+    "sql": "SELECT c.company_name AS 公司名称, COUNT(*) AS 在运营服务区数量 FROM bss_service_area sa JOIN bss_company c ON sa.company_id = c.id WHERE sa.service_state = '开放' AND sa.delete_ts IS NULL GROUP BY c.company_name ORDER BY 在运营服务区数量 DESC;"
+  },
+  {
+    "question": "分析国庆黄金周(10.1-10.7)各公司车流峰值及对应日期",
+    "sql": "SELECT c.company_name AS 公司名称, MAX(customer_count) AS 最大车流量, count_date AS 日期 FROM bss_car_day_count cc JOIN bss_service_area sa ON cc.service_area_id = sa.id JOIN bss_company c ON sa.company_id = c.id WHERE count_date BETWEEN '2023-10-01' AND '2023-10-07' GROUP BY c.company_name, count_date ORDER BY 最大车流量 DESC;"
+  },
+  {
+    "question": "计算各公司下属服务区客单价(平均订单金额)排名",
+    "sql": "SELECT c.company_name AS 公司名称, SUM(pay_sum)/NULLIF(SUM(order_sum),0) AS 客单价 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id WHERE b.oper_date >= CURRENT_DATE - 30 GROUP BY c.company_name ORDER BY 客单价 DESC;"
+  },
+  {
+    "question": "查询昨日营收环比下降超过15%的服务区及公司信息",
+    "sql": "WITH yesterday AS (SELECT service_no, SUM(pay_sum) AS y_pay FROM bss_business_day_data WHERE oper_date = CURRENT_DATE -1 GROUP BY service_no), before_yesterday AS (SELECT service_no, SUM(pay_sum) AS by_pay FROM bss_business_day_data WHERE oper_date = CURRENT_DATE -2 GROUP BY service_no) SELECT sa.service_area_name AS 服务区名称, c.company_name AS 公司名称, (y_pay - by_pay)/NULLIF(by_pay,0)*100 AS 下降幅度 FROM yesterday y JOIN before_yesterday byy ON y.service_no = byy.service_no JOIN bss_service_area sa ON y.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id WHERE (y_pay - by_pay)/NULLIF(by_pay,0) < -15 ORDER BY 下降幅度 ASC;"
+  },
+  {
+    "question": "检查服务区编码与名称在不同来源系统的映射一致性",
+    "sql": "SELECT service_name, service_no, source_system_type FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY service_name, service_no, source_system_type ORDER BY service_name;"
+  },
+  {
+    "question": "统计各来源系统的有效服务区映射数量对比",
+    "sql": "SELECT source_system_type AS 系统类型, COUNT(*) AS 数量 FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY source_system_type ORDER BY 数量 DESC;"
+  },
+  {
+    "question": "查找未建立映射关系的原始服务区数据",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, sa.service_area_no AS 服务区编码 FROM bss_service_area sa LEFT JOIN bss_service_area_mapper sam ON sa.id = sam.service_area_id WHERE sam.id IS NULL AND sa.delete_ts IS NULL;"
+  },
+  {
+    "question": "分析相同服务区ID在不同系统中的编码差异",
+    "sql": "SELECT service_area_id, ARRAY_AGG(DISTINCT service_no) AS 编码列表 FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY service_area_id HAVING COUNT(DISTINCT service_no) > 1 ORDER BY service_area_id;"
+  },
+  {
+    "question": "统计重复使用的服务区编码及其关联的服务区名称",
+    "sql": "SELECT service_no AS 重复编码, STRING_AGG(service_name, ',') AS 关联名称列表 FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY service_no HAVING COUNT(*) > 1;"
+  },
+  {
+    "question": "查询存在多个系统映射的服务区及其映射数量",
+    "sql": "SELECT service_name, COUNT(*) AS 映射系统数 FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY service_name HAVING COUNT(*) > 1 ORDER BY 映射系统数 DESC LIMIT 10;"
+  },
+  {
+    "question": "查找最近7天新增的服务区映射记录",
+    "sql": "SELECT service_name, service_no, source_system_type, create_ts AS 创建时间 FROM bss_service_area_mapper WHERE create_ts >= NOW() - INTERVAL '7 days' AND delete_ts IS NULL ORDER BY create_ts DESC;"
+  },
+  {
+    "question": "统计已删除的服务区映射记录数量及占比",
+    "sql": "SELECT (COUNT(CASE WHEN delete_ts IS NOT NULL THEN 1 END)::DECIMAL / COUNT(*)) * 100 AS 删除比例 FROM bss_service_area_mapper;"
+  },
+  {
+    "question": "分析不同运营公司下服务区编码的一致性",
+    "sql": "SELECT c.company_name, sa.service_area_name, sam.service_no FROM bss_company c JOIN bss_service_area sa ON c.id = sa.company_id JOIN bss_service_area_mapper sam ON sa.id = sam.service_area_id WHERE sa.delete_ts IS NULL AND sam.delete_ts IS NULL GROUP BY c.company_name, sa.service_area_name, sam.service_no HAVING COUNT(DISTINCT c.company_name) > 0;"
+  },
+  {
+    "question": "查询同时存在'驿购'和'驿美'系统映射的服务区",
+    "sql": "SELECT service_name FROM bss_service_area_mapper WHERE delete_ts IS NULL AND source_system_type IN ('驿购','驿美') GROUP BY service_name HAVING COUNT(DISTINCT source_system_type) = 2;"
+  }
+]

+ 202 - 0
output/qs_highway_db_20250624_105500_pair.json.backup

@@ -0,0 +1,202 @@
+[
+  {
+    "question": "近30天各服务区总营收排名TOP10",
+    "sql": "SELECT b.service_area_name AS 服务区名称, SUM(a.pay_sum) AS 总营收 FROM bss_business_day_data a JOIN bss_service_area b ON a.service_name = b.service_area_name WHERE a.oper_date >= CURRENT_DATE - INTERVAL '30 days' AND b.delete_ts IS NULL GROUP BY b.service_area_name ORDER BY 总营收 DESC LIMIT 10;"
+  },
+  {
+    "question": "各服务区微信支付金额占总营收比例分析",
+    "sql": "SELECT service_name AS 服务区名称, SUM(wx)/SUM(pay_sum)*100 AS 微信占比 FROM bss_business_day_data WHERE oper_date = '2023-09-01' AND delete_ts IS NULL GROUP BY service_name ORDER BY 微信占比 DESC;"
+  },
+  {
+    "question": "国庆黄金周(2023-10-01至2023-10-07)营收最高的五个服务区",
+    "sql": "SELECT service_name AS 服务区名称, SUM(pay_sum) AS 总营收 FROM bss_business_day_data WHERE oper_date BETWEEN '2023-10-01' AND '2023-10-07' AND delete_ts IS NULL GROUP BY service_name ORDER BY 总营收 DESC LIMIT 5;"
+  },
+  {
+    "question": "最近一个月每日总营收变化趋势图",
+    "sql": "SELECT oper_date AS 统计日期, SUM(pay_sum) AS 当日营收 FROM bss_business_day_data WHERE oper_date >= CURRENT_DATE - INTERVAL '1 month' AND delete_ts IS NULL GROUP BY oper_date ORDER BY 统计日期;"
+  },
+  {
+    "question": "各运营公司下属服务区平均营收对比",
+    "sql": "SELECT c.company_name AS 运营公司, AVG(a.pay_sum) AS 平均营收 FROM bss_business_day_data a JOIN bss_service_area b ON a.service_name = b.service_area_name JOIN bss_company c ON b.company_id = c.id WHERE a.oper_date = '2023-09-01' AND a.delete_ts IS NULL GROUP BY c.company_name;"
+  },
+  {
+    "question": "现金支付占比超过20%的服务区清单",
+    "sql": "SELECT service_name AS 服务区名称, SUM(rmb)/SUM(pay_sum)*100 AS 现金占比 FROM bss_business_day_data WHERE oper_date = '2023-09-01' AND delete_ts IS NULL GROUP BY service_name HAVING SUM(rmb)/SUM(pay_sum)*100 > 20 ORDER BY 现金占比 DESC;"
+  },
+  {
+    "question": "第三季度(7-9月)各月订单量变化趋势",
+    "sql": "SELECT EXTRACT(MONTH FROM oper_date) AS 月份, SUM(order_sum) AS 月订单量 FROM bss_business_day_data WHERE EXTRACT(YEAR FROM oper_date) = 2023 AND EXTRACT(MONTH FROM oper_date) BETWEEN 7 AND 9 AND delete_ts IS NULL GROUP BY 月份 ORDER BY 月份;"
+  },
+  {
+    "question": "庐山服务区与宜春服务区近7日营收对比",
+    "sql": "SELECT service_name AS 服务区名称, oper_date AS 统计日期, pay_sum AS 当日营收 FROM bss_business_day_data WHERE service_name IN ('庐山服务区', '宜春服务区') AND oper_date >= CURRENT_DATE - INTERVAL '7 days' AND delete_ts IS NULL ORDER BY 统计日期 DESC;"
+  },
+  {
+    "question": "连续3日营收超5万元的服务区清单",
+    "sql": "SELECT service_name AS 服务区名称 FROM (SELECT service_name, COUNT(*) AS 高营收天数 FROM bss_business_day_data WHERE pay_sum > 50000 AND oper_date >= CURRENT_DATE - INTERVAL '7 days' AND delete_ts IS NULL GROUP BY service_name) t WHERE 高营收天数 >= 3;"
+  },
+  {
+    "question": "各支付方式订单数量占比分析",
+    "sql": "SELECT SUM(wx_order) AS 微信订单, SUM(zf_order) AS 支付宝订单, SUM(rmb_order) AS 现金订单, SUM(xs_order) AS 行吧订单, SUM(jd_order) AS 金豆订单 FROM bss_business_day_data WHERE oper_date = '2023-09-01' AND delete_ts IS NULL;"
+  },
+  {
+    "question": "统计各服务区2023年4月每日平均车流量,并按平均流量降序排列",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, AVG(dc.customer_count) AS 日均车流量 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id WHERE dc.count_date BETWEEN '2023-04-01' AND '2023-04-30' AND dc.delete_ts IS NULL AND sa.delete_ts IS NULL GROUP BY sa.service_area_name ORDER BY 日均车流量 DESC;"
+  },
+  {
+    "question": "分析2023年危化品车辆在各服务区的通行占比,仅显示占比超过5%的服务区",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, COUNT(*) AS 总车次, SUM(CASE WHEN dc.car_type='危化品' THEN 1 ELSE 0 END)::DECIMAL/COUNT(*) AS 危化品占比 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id WHERE dc.count_date >= '2023-01-01' AND dc.delete_ts IS NULL AND sa.delete_ts IS NULL GROUP BY sa.service_area_name HAVING SUM(CASE WHEN dc.car_type='危化品' THEN 1 ELSE 0 END)::DECIMAL/COUNT(*) > 0.05;"
+  },
+  {
+    "question": "查询2023年车流量TOP10服务区及对应所属公司信息",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, comp.company_name AS 所属公司, SUM(dc.customer_count) AS 总车流量 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id JOIN bss_company comp ON sa.company_id = comp.id WHERE dc.count_date >= '2023-01-01' AND dc.delete_ts IS NULL GROUP BY sa.service_area_name, comp.company_name ORDER BY 总车流量 DESC LIMIT 10;"
+  },
+  {
+    "question": "统计最近7天各日期过境车辆占比变化趋势",
+    "sql": "SELECT count_date AS 统计日期, SUM(CASE WHEN car_type='过境' THEN customer_count ELSE 0 END)::DECIMAL/SUM(customer_count) AS 过境占比 FROM bss_car_day_count WHERE count_date >= CURRENT_DATE - 7 AND delete_ts IS NULL GROUP BY count_date ORDER BY count_date;"
+  },
+  {
+    "question": "对比2023年Q1季度城际车辆与过境车辆月均流量差异",
+    "sql": "SELECT TO_CHAR(count_date, 'YYYY-MM') AS 月份, AVG(CASE WHEN car_type='城际' THEN customer_count ELSE 0 END) AS 城际月均流量, AVG(CASE WHEN car_type='过境' THEN customer_count ELSE 0 END) AS 过境月均流量 FROM bss_car_day_count WHERE count_date BETWEEN '2023-01-01' AND '2023-03-31' AND delete_ts IS NULL GROUP BY TO_CHAR(count_date, 'YYYY-MM') ORDER BY 月份;"
+  },
+  {
+    "question": "查询2023年3月15日各服务区车流量及环比增长率",
+    "sql": "WITH daily_total AS (SELECT service_area_id, count_date, SUM(customer_count) AS total_count FROM bss_car_day_count WHERE count_date IN ('2023-03-15', '2023-03-08') AND delete_ts IS NULL GROUP BY service_area_id, count_date) SELECT d1.service_area_id AS 服务区ID, d1.total_count AS 当日流量, d2.total_count AS 上周同期流量, (d1.total_count - d2.total_count)/d2.total_count::DECIMAL AS 环比增长率 FROM daily_total d1 JOIN daily_total d2 ON d1.service_area_id = d2.service_area_id AND d1.count_date = '2023-03-15' AND d2.count_date = '2023-03-08';"
+  },
+  {
+    "question": "分析各服务区不同时段(早/中/晚)车流量分布,取最近30天数据",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, EXTRACT(HOUR FROM dc.create_ts)::INT / 8 AS 时段段, SUM(dc.customer_count) AS 总车流量 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id WHERE dc.count_date >= CURRENT_DATE - 30 AND dc.delete_ts IS NULL GROUP BY sa.service_area_name, 时段段 ORDER BY 服务区名称, 时段段;"
+  },
+  {
+    "question": "统计连续3天日车流量超过1000辆的服务区及出现次数",
+    "sql": "WITH daily_filter AS (SELECT service_area_id, count_date, SUM(customer_count) AS total_count FROM bss_car_day_count WHERE delete_ts IS NULL GROUP BY service_area_id, count_date HAVING SUM(customer_count) > 1000) SELECT service_area_id, COUNT(*) AS 超量次数 FROM (SELECT *, COUNT(*) OVER (PARTITION BY service_area_id ORDER BY count_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS streak FROM daily_filter) t WHERE streak >= 3 GROUP BY service_area_id;"
+  },
+  {
+    "question": "查询2023年各车型月度通行量变化趋势",
+    "sql": "SELECT TO_CHAR(count_date, 'YYYY-MM') AS 月份, car_type AS 车型, SUM(customer_count) AS 通行量 FROM bss_car_day_count WHERE count_date >= '2023-01-01' AND delete_ts IS NULL GROUP BY TO_CHAR(count_date, 'YYYY-MM'), car_type ORDER BY 月份, 车型;"
+  },
+  {
+    "question": "分析各公司管理服务区的车流密度(车流量/服务区数量)",
+    "sql": "SELECT comp.company_name AS 公司名称, SUM(dc.customer_count) AS 总车流量, COUNT(DISTINCT sa.id) AS 服务区数量, SUM(dc.customer_count)/COUNT(DISTINCT sa.id)::DECIMAL AS 车流密度 FROM bss_car_day_count dc JOIN bss_service_area sa ON dc.service_area_id = sa.id JOIN bss_company comp ON sa.company_id = comp.id WHERE dc.count_date >= '2023-01-01' AND dc.delete_ts IS NULL GROUP BY comp.company_name;"
+  },
+  {
+    "question": "统计各运营公司下属服务区最近一周总营收排名(按公司维度)",
+    "sql": "SELECT c.company_name AS 公司名称, SUM(b.pay_sum) AS 总营收 FROM bss_business_day_data b JOIN bss_service_area s ON b.service_no = s.service_area_no JOIN bss_company c ON s.company_id = c.id WHERE b.oper_date >= CURRENT_DATE - 7 AND c.delete_ts IS NULL GROUP BY c.company_name ORDER BY 总营收 DESC LIMIT 5;"
+  },
+  {
+    "question": "对比不同运营公司2023年Q1季度日均车流量(按车辆类型分类)",
+    "sql": "SELECT c.company_name AS 公司名称, car_type AS 车辆类型, AVG(customer_count) AS 日均车流量 FROM bss_car_day_count cc JOIN bss_service_area sa ON cc.service_area_id = sa.id JOIN bss_company c ON sa.company_id = c.id WHERE cc.count_date BETWEEN '2023-01-01' AND '2023-03-31' GROUP BY c.company_name, car_type ORDER BY 公司名称, 日均车流量 DESC;"
+  },
+  {
+    "question": "计算各公司下属服务区单位产值(每辆车产生营收)TOP3",
+    "sql": "SELECT c.company_name AS 公司名称, sa.service_area_name AS 服务区名称, SUM(b.pay_sum)/NULLIF(SUM(cc.customer_count),0) AS 单位产值 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id JOIN bss_car_day_count cc ON sa.id = cc.service_area_id AND b.oper_date = cc.count_date WHERE b.oper_date = CURRENT_DATE -1 GROUP BY c.company_name, sa.service_area_name ORDER BY 单位产值 DESC LIMIT 3;"
+  },
+  {
+    "question": "分析各运营公司月度营收环比增长率(最近6个月数据)",
+    "sql": "SELECT company_name AS 公司名称, DATE_TRUNC('month', oper_date) AS 月份, SUM(pay_sum) AS 当月营收, (SUM(pay_sum) OVER (PARTITION BY company_name ORDER BY DATE_TRUNC('month', oper_date)) / NULLIF(LAG(SUM(pay_sum),1) OVER (PARTITION BY company_name ORDER BY DATE_TRUNC('month', oper_date)),0) -1)*100 AS 环比增长率 FROM (SELECT * FROM bss_business_day_data WHERE oper_date >= DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '6 months') b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id GROUP BY company_name, 月份 ORDER BY 月份;"
+  },
+  {
+    "question": "查询2023年营收未达标(低于平均值20%)的服务区及所属公司信息",
+    "sql": "WITH avg_revenue AS (SELECT AVG(pay_sum) AS avg_val FROM bss_business_day_data WHERE oper_date BETWEEN '2023-01-01' AND '2023-12-31') SELECT sa.service_area_name AS 服务区名称, c.company_name AS 公司名称, SUM(b.pay_sum) AS 总营收 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id GROUP BY sa.service_area_name, c.company_name HAVING SUM(b.pay_sum) < (SELECT avg_val*0.8 FROM avg_revenue) ORDER BY 总营收 ASC;"
+  },
+  {
+    "question": "统计各公司不同支付方式占比(微信/支付宝/现金)",
+    "sql": "SELECT c.company_name AS 公司名称, '微信' AS 支付方式, SUM(wx)/SUM(pay_sum)*100 AS 占比 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id GROUP BY c.company_name UNION ALL SELECT c.company_name, '支付宝', SUM(zfb)/SUM(pay_sum)*100 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id GROUP BY c.company_name UNION ALL SELECT c.company_name, '现金', SUM(rmb)/SUM(pay_sum)*100 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id GROUP BY c.company_name ORDER BY 公司名称, 占比 DESC;"
+  },
+  {
+    "question": "查询当前在运营的服务区数量及对应公司(服务状态为开放)",
+    "sql": "SELECT c.company_name AS 公司名称, COUNT(*) AS 在运营服务区数量 FROM bss_service_area sa JOIN bss_company c ON sa.company_id = c.id WHERE sa.service_state = '开放' AND sa.delete_ts IS NULL GROUP BY c.company_name ORDER BY 在运营服务区数量 DESC;"
+  },
+  {
+    "question": "分析国庆黄金周(10.1-10.7)各公司车流峰值及对应日期",
+    "sql": "SELECT c.company_name AS 公司名称, MAX(customer_count) AS 最大车流量, count_date AS 日期 FROM bss_car_day_count cc JOIN bss_service_area sa ON cc.service_area_id = sa.id JOIN bss_company c ON sa.company_id = c.id WHERE count_date BETWEEN '2023-10-01' AND '2023-10-07' GROUP BY c.company_name, count_date ORDER BY 最大车流量 DESC;"
+  },
+  {
+    "question": "计算各公司下属服务区客单价(平均订单金额)排名",
+    "sql": "SELECT c.company_name AS 公司名称, SUM(pay_sum)/NULLIF(SUM(order_sum),0) AS 客单价 FROM bss_business_day_data b JOIN bss_service_area sa ON b.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id WHERE b.oper_date >= CURRENT_DATE - 30 GROUP BY c.company_name ORDER BY 客单价 DESC;"
+  },
+  {
+    "question": "查询昨日营收环比下降超过15%的服务区及公司信息",
+    "sql": "WITH yesterday AS (SELECT service_no, SUM(pay_sum) AS y_pay FROM bss_business_day_data WHERE oper_date = CURRENT_DATE -1 GROUP BY service_no), before_yesterday AS (SELECT service_no, SUM(pay_sum) AS by_pay FROM bss_business_day_data WHERE oper_date = CURRENT_DATE -2 GROUP BY service_no) SELECT sa.service_area_name AS 服务区名称, c.company_name AS 公司名称, (y_pay - by_pay)/NULLIF(by_pay,0)*100 AS 下降幅度 FROM yesterday y JOIN before_yesterday byy ON y.service_no = byy.service_no JOIN bss_service_area sa ON y.service_no = sa.service_area_no JOIN bss_company c ON sa.company_id = c.id WHERE (y_pay - by_pay)/NULLIF(by_pay,0) < -15 ORDER BY 下降幅度 ASC;"
+  },
+  {
+    "question": "统计近30天各路段关联服务区的平均日车流量及总营收,按车流量降序排列",
+    "sql": "SELECT sr.section_name AS 路段名称, AVG(cdc.customer_count) AS 平均日车流量, SUM(bdd.pay_sum) AS 总营收 FROM bss_section_route sr LEFT JOIN bss_section_route_area_link link ON sr.id = link.section_route_id LEFT JOIN bss_car_day_count cdc ON link.service_area_id = cdc.service_area_id LEFT JOIN bss_business_day_data bdd ON link.service_area_id = bdd.service_area_id WHERE cdc.count_date >= CURRENT_DATE - 30 AND sr.delete_ts IS NULL GROUP BY sr.section_name ORDER BY 平均日车流量 DESC;"
+  },
+  {
+    "question": "分析上周每日危化品车辆占比与服务区营收的相关性(按路线维度)",
+    "sql": "SELECT sr.route_name AS 路线名称, cdc.count_date AS 日期, (SUM(CASE WHEN cdc.car_type='危化品' THEN cdc.customer_count ELSE 0 END)/SUM(cdc.customer_count)) AS 危化品占比, SUM(bdd.pay_sum) AS 当日营收 FROM bss_section_route sr LEFT JOIN bss_section_route_area_link link ON sr.id = link.section_route_id LEFT JOIN bss_car_day_count cdc ON link.service_area_id = cdc.service_area_id LEFT JOIN bss_business_day_data bdd ON link.service_area_id = bdd.service_area_id AND cdc.count_date = bdd.oper_date WHERE cdc.count_date >= CURRENT_DATE - 7 GROUP BY sr.route_name, cdc.count_date;"
+  },
+  {
+    "question": "查询本月车流排名前5的服务区及其所属路段,包含现金支付占比统计",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, sr.section_name AS 所属路段, SUM(cdc.customer_count) AS 总车流量, (SUM(bdd.rmb)/SUM(bdd.pay_sum)) AS 现金支付占比 FROM bss_service_area sa LEFT JOIN bss_section_route_area_link link ON sa.id = link.service_area_id LEFT JOIN bss_section_route sr ON link.section_route_id = sr.id LEFT JOIN bss_car_day_count cdc ON sa.id = cdc.service_area_id LEFT JOIN bss_business_day_data bdd ON sa.id = bdd.service_area_id WHERE cdc.count_date >= DATE_TRUNC('month', CURRENT_DATE) GROUP BY sa.service_area_name, sr.section_name ORDER BY 总车流量 DESC LIMIT 5;"
+  },
+  {
+    "question": "对比工作日与周末各路段服务区的高峰时段(18-20点)车流差异",
+    "sql": "SELECT sr.section_name AS 路段名称, EXTRACT(DOW FROM cdc.create_ts) AS 星期, SUM(CASE WHEN EXTRACT(HOUR FROM cdc.create_ts) BETWEEN 18 AND 20 THEN cdc.customer_count ELSE 0 END) AS 高峰车流, SUM(CASE WHEN EXTRACT(HOUR FROM cdc.create_ts) NOT BETWEEN 18 AND 20 THEN cdc.customer_count ELSE 0 END) AS 非高峰车流 FROM bss_section_route sr LEFT JOIN bss_section_route_area_link link ON sr.id = link.section_route_id LEFT JOIN bss_car_day_count cdc ON link.service_area_id = cdc.service_area_id WHERE cdc.count_date BETWEEN '2023-03-01' AND '2023-03-31' GROUP BY sr.section_name, 星期 HAVING 星期 IN (0,6) OR 星期 BETWEEN 1 AND 4;"
+  },
+  {
+    "question": "计算各路段服务区档口转化率(订单/车流)并按城际车辆比例排序",
+    "sql": "SELECT sr.section_name AS 路段名称, sa.service_area_name AS 服务区名称, (SUM(bdd.order_sum)/SUM(cdc.customer_count)) AS 档口转化率, (SUM(CASE WHEN cdc.car_type='城际' THEN cdc.customer_count ELSE 0 END)/SUM(cdc.customer_count)) AS 城际占比 FROM bss_section_route sr LEFT JOIN bss_section_route_area_link link ON sr.id = link.section_route_id LEFT JOIN bss_service_area sa ON link.service_area_id = sa.id LEFT JOIN bss_car_day_count cdc ON sa.id = cdc.service_area_id LEFT JOIN bss_business_day_data bdd ON sa.id = bdd.service_area_id GROUP BY sr.section_name, sa.service_area_name ORDER BY 城际占比 DESC;"
+  },
+  {
+    "question": "查询清明节假期期间(4.5-4.7)过境车辆激增超200%的服务区及关联路段",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, sr.section_name AS 关联路段, (SUM(CASE WHEN cdc.count_date BETWEEN '2023-04-05' AND '2023-04-07' AND cdc.car_type='过境' THEN cdc.customer_count ELSE 0 END)/NULLIF(SUM(CASE WHEN cdc.count_date BETWEEN '2023-03-29' AND '2023-03-31' AND cdc.car_type='过境' THEN cdc.customer_count ELSE 0 END),0) - 1) AS 增长率 FROM bss_service_area sa LEFT JOIN bss_section_route_area_link link ON sa.id = link.service_area_id LEFT JOIN bss_section_route sr ON link.section_route_id = sr.id LEFT JOIN bss_car_day_count cdc ON sa.id = cdc.service_area_id GROUP BY sa.service_area_name, sr.section_name HAVING (增长率 > 2);"
+  },
+  {
+    "question": "统计各路段不同时段(早/中/晚)车辆类型分布及对应餐饮档口销售额占比",
+    "sql": "SELECT sr.section_name AS 路段名称, (CASE WHEN EXTRACT(HOUR FROM cdc.create_ts) BETWEEN 6 AND 11 THEN '早' WHEN EXTRACT(HOUR FROM cdc.create_ts) BETWEEN 12 AND 17 THEN '中' ELSE '晚' END) AS 时段, cdc.car_type AS 车辆类型, (SUM(CASE WHEN bdd.branch_name LIKE '%餐饮%' THEN bdd.pay_sum ELSE 0 END)/SUM(bdd.pay_sum)) AS 餐饮占比 FROM bss_section_route sr LEFT JOIN bss_section_route_area_link link ON sr.id = link.section_route_id LEFT JOIN bss_car_day_count cdc ON link.service_area_id = cdc.service_area_id LEFT JOIN bss_business_day_data bdd ON link.service_area_id = bdd.service_area_id GROUP BY sr.section_name, 时段, cdc.car_type;"
+  },
+  {
+    "question": "查询连续3天车流下降且营收环比降低10%以上的服务区及所属路段",
+    "sql": "WITH daily_stats AS (SELECT link.section_route_id, cdc.service_area_id, cdc.count_date, cdc.customer_count AS 车流, SUM(bdd.pay_sum) AS 营收 FROM bss_section_route_area_link link LEFT JOIN bss_car_day_count cdc ON link.service_area_id = cdc.service_area_id LEFT JOIN bss_business_day_data bdd ON link.service_area_id = bdd.service_area_id AND cdc.count_date = bdd.oper_date GROUP BY link.section_route_id, cdc.service_area_id, cdc.count_date) SELECT sr.section_name AS 路段名称, sa.service_area_name AS 服务区名称 FROM daily_stats ds LEFT JOIN bss_section_route sr ON ds.section_route_id = sr.id LEFT JOIN bss_service_area sa ON ds.service_area_id = sa.id WHERE ds.count_date >= CURRENT_DATE - 5 AND LAG(ds.车流,1) OVER (PARTITION BY ds.service_area_id ORDER BY ds.count_date) > ds.车流 AND LAG(ds.营收,1) OVER (PARTITION BY ds.service_area_id ORDER BY ds.count_date) * 0.9 > ds.营收 GROUP BY sr.section_name, sa.service_area_name HAVING COUNT(*) >=3;"
+  },
+  {
+    "question": "分析不同天气状况下(假设存在天气字段)各路段车辆分流比例与加油服务营收的关系",
+    "sql": "SELECT sr.section_name AS 路段名称, (SUM(CASE WHEN sa.service_area_type='加油' THEN cdc.customer_count ELSE 0 END)/SUM(cdc.customer_count)) AS 分流比例, SUM(CASE WHEN sa.service_area_type='加油' THEN bdd.pay_sum ELSE 0 END) AS 加油营收, EXTRACT(MONTH FROM cdc.count_date) AS 月份 FROM bss_section_route sr LEFT JOIN bss_section_route_area_link link ON sr.id = link.section_route_id LEFT JOIN bss_car_day_count cdc ON link.service_area_id = cdc.service_area_id LEFT JOIN bss_service_area sa ON link.service_area_id = sa.id LEFT JOIN bss_business_day_data bdd ON link.service_area_id = bdd.service_area_id GROUP BY sr.section_name, 月份;"
+  },
+  {
+    "question": "查询本周新开通路段关联服务区的小时级车流分布及支付方式偏好(微信/支付宝占比)",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, EXTRACT(HOUR FROM cdc.create_ts) AS 小时, SUM(cdc.customer_count) AS 车流量, (SUM(bdd.wx)/SUM(bdd.pay_sum)) AS 微信占比, (SUM(bdd.zfb)/SUM(bdd.pay_sum)) AS 支付宝占比 FROM bss_section_route sr LEFT JOIN bss_section_route_area_link link ON sr.id = link.section_route_id LEFT JOIN bss_service_area sa ON link.service_area_id = sa.id LEFT JOIN bss_car_day_count cdc ON sa.id = cdc.service_area_id LEFT JOIN bss_business_day_data bdd ON sa.id = bdd.service_area_id WHERE sr.create_ts >= CURRENT_DATE - 7 GROUP BY sa.service_area_name, 小时 ORDER BY 小时;"
+  },
+  {
+    "question": "检查服务区编码与名称在不同来源系统的映射一致性",
+    "sql": "SELECT service_name, service_no, source_system_type FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY service_name, service_no, source_system_type ORDER BY service_name;"
+  },
+  {
+    "question": "统计各来源系统的有效服务区映射数量对比",
+    "sql": "SELECT source_system_type AS 系统类型, COUNT(*) AS 数量 FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY source_system_type ORDER BY 数量 DESC;"
+  },
+  {
+    "question": "查找未建立映射关系的原始服务区数据",
+    "sql": "SELECT sa.service_area_name AS 服务区名称, sa.service_area_no AS 服务区编码 FROM bss_service_area sa LEFT JOIN bss_service_area_mapper sam ON sa.id = sam.service_area_id WHERE sam.id IS NULL AND sa.delete_ts IS NULL;"
+  },
+  {
+    "question": "分析相同服务区ID在不同系统中的编码差异",
+    "sql": "SELECT service_area_id, ARRAY_AGG(DISTINCT service_no) AS 编码列表 FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY service_area_id HAVING COUNT(DISTINCT service_no) > 1 ORDER BY service_area_id;"
+  },
+  {
+    "question": "统计重复使用的服务区编码及其关联的服务区名称",
+    "sql": "SELECT service_no AS 重复编码, STRING_AGG(service_name, ',') AS 关联名称列表 FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY service_no HAVING COUNT(*) > 1;"
+  },
+  {
+    "question": "查询存在多个系统映射的服务区及其映射数量",
+    "sql": "SELECT service_name, COUNT(*) AS 映射系统数 FROM bss_service_area_mapper WHERE delete_ts IS NULL GROUP BY service_name HAVING COUNT(*) > 1 ORDER BY 映射系统数 DESC LIMIT 10;"
+  },
+  {
+    "question": "查找最近7天新增的服务区映射记录",
+    "sql": "SELECT service_name, service_no, source_system_type, create_ts AS 创建时间 FROM bss_service_area_mapper WHERE create_ts >= NOW() - INTERVAL '7 days' AND delete_ts IS NULL ORDER BY create_ts DESC;"
+  },
+  {
+    "question": "统计已删除的服务区映射记录数量及占比",
+    "sql": "SELECT (COUNT(CASE WHEN delete_ts IS NOT NULL THEN 1 END)::DECIMAL / COUNT(*)) * 100 AS 删除比例 FROM bss_service_area_mapper;"
+  },
+  {
+    "question": "分析不同运营公司下服务区编码的一致性",
+    "sql": "SELECT c.company_name, sa.service_area_name, sam.service_no FROM bss_company c JOIN bss_service_area sa ON c.id = sa.company_id JOIN bss_service_area_mapper sam ON sa.id = sam.service_area_id WHERE sa.delete_ts IS NULL AND sam.delete_ts IS NULL GROUP BY c.company_name, sa.service_area_name, sam.service_no HAVING COUNT(DISTINCT c.company_name) > 0;"
+  },
+  {
+    "question": "查询同时存在'驿购'和'驿美'系统映射的服务区",
+    "sql": "SELECT service_name FROM bss_service_area_mapper WHERE delete_ts IS NULL AND source_system_type IN ('驿购','驿美') GROUP BY service_name HAVING COUNT(DISTINCT source_system_type) = 2;"
+  }
+]

+ 89 - 0
test_qa_apis.py

@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+"""
+QA反馈API测试脚本
+用于验证所有API端点是否正常工作
+"""
+
+import requests
+import json
+
+# 配置
+BASE_URL = "http://localhost:8084"  # 根据你的端口配置
+API_PREFIX = "/api/v0/qa_feedback"
+
+def test_api(method, endpoint, data=None, expected_status=200):
+    """测试API端点"""
+    url = f"{BASE_URL}{API_PREFIX}{endpoint}"
+    
+    try:
+        if method == "GET":
+            response = requests.get(url)
+        elif method == "POST":
+            response = requests.post(url, json=data, headers={'Content-Type': 'application/json'})
+        elif method == "PUT":
+            response = requests.put(url, json=data, headers={'Content-Type': 'application/json'})
+        elif method == "DELETE":
+            response = requests.delete(url)
+        
+        print(f"\n{'='*60}")
+        print(f"测试: {method} {endpoint}")
+        print(f"URL: {url}")
+        print(f"状态码: {response.status_code}")
+        print(f"响应:")
+        try:
+            print(json.dumps(response.json(), indent=2, ensure_ascii=False))
+        except:
+            print(response.text)
+        
+        return response.status_code == expected_status
+        
+    except Exception as e:
+        print(f"❌ 测试失败: {e}")
+        return False
+
+def main():
+    """主测试函数"""
+    print("🚀 开始测试QA反馈模块API...")
+    
+    # 1. 测试统计API (GET)
+    print("\n📊 测试统计API")
+    test_api("GET", "/stats")
+    
+    # 2. 测试查询API (POST)
+    print("\n🔍 测试查询API")
+    test_api("POST", "/query", {
+        "page": 1,
+        "page_size": 10
+    })
+    
+    # 3. 测试添加反馈API (POST)
+    print("\n➕ 测试添加反馈API")
+    add_result = test_api("POST", "/add", {
+        "question": "测试问题",
+        "sql": "SELECT 1 as test",
+        "is_thumb_up": True,
+        "user_id": "test_user"
+    })
+    
+    # 4. 测试训练API (POST) - 重点测试
+    print("\n⭐ 测试训练API (重点)")
+    test_api("POST", "/add_to_training", {
+        "feedback_ids": [1, 2, 3]
+    }, expected_status=404)  # 可能没有这些ID,但API应该存在
+    
+    # 5. 测试更新API (PUT)
+    print("\n✏️ 测试更新API")
+    test_api("PUT", "/update/1", {
+        "question": "更新的问题"
+    }, expected_status=404)  # 可能没有ID=1的记录
+    
+    # 6. 测试删除API (DELETE)
+    print("\n🗑️ 测试删除API")
+    test_api("DELETE", "/delete/999", expected_status=404)  # 测试不存在的ID
+    
+    print(f"\n{'='*60}")
+    print("🎯 测试完成!")
+    print("📝 重点关注训练API是否返回正确的错误信息而不是'API not ported'")
+
+if __name__ == "__main__":
+    main()