Files
leaudit-platform-frontend/auth_doc/系统概览.md
T
2025-12-05 00:09:32 +08:00

30 KiB
Raw Blame History

统计分析模块 API 文档

路径: app/routes/v3/statistics.py 路由前缀: /admin/v3/statistics 功能: 提供评查数据的统计分析,包括高频错误评查点和高风险用户排行


📋 目录


1. 模块概述

统计分析模块专为管理员提供数据洞察能力,帮助发现系统中的薄弱环节和高风险用户,从而进行针对性的培训和改进。

核心特性

  1. 仅管理员访问

    • 所有统计接口仅限 admin 角色访问
    • 普通用户无法查看统计数据
  2. 地市隔离

    • 管理员只能查看自己地市的统计数据
    • 从 JWT Token 中提取 area 字段进行数据过滤
    • 确保数据安全和隐私
  3. 时间范围过滤

    • 支持按时间范围统计(start_date - end_date
    • 不提供时间参数时统计全部历史数据
  4. Top N 排行

    • 支持自定义返回数量(limit 参数)
    • 高频错误评查点:最多返回 50 条
    • 高风险用户:最多返回 20 条

应用场景

统计类型 应用场景 改进措施
高频错误评查点 发现常见合规问题 加强培训、优化模板
高风险用户 识别需要培训的用户 针对性培训、质量把控

2. 接口列表

序号 HTTP方法 路径 功能 认证要求 权限要求
1 GET /top-error-points 获取高频错误评查点 Top N JWT必需 admin
2 GET /top-risk-users 获取高风险用户 Top N JWT必需 admin

3. 详细接口说明

3.1 获取高频错误评查点

统计最常出错的评查点,按累计出错用户数排序。

接口信息

  • HTTP方法: GET
  • 路径: /admin/v3/statistics/top-error-points
  • 认证: JWT Token(必需)
  • 权限: admin(管理员)

请求参数

Headers:

Authorization: Bearer <JWT_TOKEN>

Query 参数:

参数 类型 必填 默认值 范围 说明
limit int 10 1-50 返回Top N条记录
start_date str null - 开始时间(格式:YYYY-MM-DD
end_date str null - 结束时间(格式:YYYY-MM-DD

请求示例

示例 1: 获取 Top 10 高频错误评查点(全部历史)

cURL:

curl -X GET "http://localhost:8000/admin/v3/statistics/top-error-points?limit=10" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Python:

import requests

url = "http://localhost:8000/admin/v3/statistics/top-error-points"
headers = {
    "Authorization": f"Bearer {jwt_token}"
}
params = {
    "limit": 10
}

response = requests.get(url, headers=headers, params=params)
data = response.json()

print(f"高频错误评查点 Top {data['total']}:")
for item in data['items']:
    print(f"  {item['rank']}. {item['point_name']} - {item['error_user_count']} 人出错")

JavaScript:

const params = new URLSearchParams({
  limit: '10'
});

const response = await fetch(
  `http://localhost:8000/admin/v3/statistics/top-error-points?${params}`,
  {
    headers: {
      'Authorization': `Bearer ${jwtToken}`
    }
  }
);
const data = await response.json();

console.log(`高频错误评查点 Top ${data.total}:`);
data.items.forEach(item => {
  console.log(`  ${item.rank}. ${item.point_name} - ${item.error_user_count} 人出错`);
});

示例 2: 获取 2025年11月的 Top 20 高频错误评查点

cURL:

curl -X GET "http://localhost:8000/admin/v3/statistics/top-error-points?limit=20&start_date=2025-11-01&end_date=2025-11-30" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Python:

import requests

url = "http://localhost:8000/admin/v3/statistics/top-error-points"
headers = {
    "Authorization": f"Bearer {jwt_token}"
}
params = {
    "limit": 20,
    "start_date": "2025-11-01",
    "end_date": "2025-11-30"
}

response = requests.get(url, headers=headers, params=params)
data = response.json()

print(f"2025年11月高频错误评查点 Top {data['total']}:")
for item in data['items']:
    print(f"  {item['rank']}. {item['point_name']} (ID: {item['evaluation_point_id']})")
    print(f"      累计出错用户数: {item['error_user_count']}")

JavaScript:

const params = new URLSearchParams({
  limit: '20',
  start_date: '2025-11-01',
  end_date: '2025-11-30'
});

const response = await fetch(
  `http://localhost:8000/admin/v3/statistics/top-error-points?${params}`,
  {
    headers: {
      'Authorization': `Bearer ${jwtToken}`
    }
  }
);
const data = await response.json();
console.log(`2025年11月高频错误评查点 Top ${data.total}`);

响应示例

成功响应 (200 OK):

{
  "total": 10,
  "items": [
    {
      "rank": 1,
      "evaluation_point_id": 45,
      "point_name": "管辖地点明确性",
      "error_user_count": 38
    },
    {
      "rank": 2,
      "evaluation_point_id": 67,
      "point_name": "履约保证金比例合规性",
      "error_user_count": 25
    },
    {
      "rank": 3,
      "evaluation_point_id": 23,
      "point_name": "付款条款完整性",
      "error_user_count": 19
    },
    {
      "rank": 4,
      "evaluation_point_id": 89,
      "point_name": "违约责任条款明确性",
      "error_user_count": 15
    },
    {
      "rank": 5,
      "evaluation_point_id": 12,
      "point_name": "工程质量标准规范性",
      "error_user_count": 12
    }
  ]
}

失败响应 - 权限不足 (403 Forbidden):

{
  "detail": "此接口仅限管理员访问"
}

失败响应 - 缺少地市信息 (400 Bad Request):

{
  "detail": "用户缺少地市信息,无法进行统计查询"
}

失败响应 - 时间格式错误 (400 Bad Request):

{
  "detail": "开始时间格式错误,应为 YYYY-MM-DD,实际为: 2025/11/01"
}

业务逻辑

1. 验证JWT Token
2. 提取用户信息(user_id, user_role, area
3. ✅ 权限检查
   ├─ 验证user_role == "admin"
   ├─ 验证current_user.area存在
   └─ 不满足则返回403或400
4. 📅 解析时间参数
   ├─ start_date: 转换为datetime对象
   ├─ end_date: 转换为datetime对象,并调整到23:59:59
   └─ 格式错误则返回400
5. 🗂️ 调用服务层
   ├─ StatisticsService.get_top_error_evaluation_points()
   ├─ 传入参数: area, limit, start_date, end_date
   └─ 查询evaluation_results表
6. 📊 统计逻辑
   ├─ 筛选条件:
   │   ├─ result = 'false' (评查失败)
   │   ├─ documents.ou_id = current_user.area (地市隔离)
   │   └─ created_at BETWEEN start_date AND end_date (时间范围)
   ├─ 分组统计:
   │   ├─ GROUP BY evaluation_point_id
   │   └─ COUNT(DISTINCT user_id) AS error_user_count (去重用户)
   └─ 排序: ORDER BY error_user_count DESC
7. 📤 构建响应
   ├─ 添加排名(rank)
   └─ 返回TopErrorPointsResponse

统计说明

去重规则:

  • 同一用户在多个文档中触发同一评查点错误,只计数一次
  • 统计的是 "有多少人犯了这个错误",而不是 "这个错误发生了多少次"

示例:

用户A的文档1: 管辖地点明确性 - 失败
用户A的文档2: 管辖地点明确性 - 失败
用户B的文档1: 管辖地点明确性 - 失败

结果: 管辖地点明确性的error_user_count = 2(只计数A和B两个用户)

3.2 获取高风险用户

统计最容易出错的用户,按累计出错数排序。

接口信息

  • HTTP方法: GET
  • 路径: /admin/v3/statistics/top-risk-users
  • 认证: JWT Token(必需)
  • 权限: admin(管理员)

请求参数

Headers:

Authorization: Bearer <JWT_TOKEN>

Query 参数:

参数 类型 必填 默认值 范围 说明
limit int 5 1-20 返回Top N条记录
start_date str null - 开始时间(格式:YYYY-MM-DD
end_date str null - 结束时间(格式:YYYY-MM-DD

请求示例

示例 1: 获取 Top 5 高风险用户(全部历史)

cURL:

curl -X GET "http://localhost:8000/admin/v3/statistics/top-risk-users?limit=5" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Python:

import requests

url = "http://localhost:8000/admin/v3/statistics/top-risk-users"
headers = {
    "Authorization": f"Bearer {jwt_token}"
}
params = {
    "limit": 5
}

response = requests.get(url, headers=headers, params=params)
data = response.json()

print(f"高风险用户 Top {data['total']}:")
for item in data['items']:
    print(f"  {item['rank']}. {item['user_name']} ({item['department']})")
    print(f"      累计出错: {item['total_errors']} 次")
    print(f"      单文档平均出错: {item['avg_errors_per_doc']:.2f} 次")

JavaScript:

const params = new URLSearchParams({
  limit: '5'
});

const response = await fetch(
  `http://localhost:8000/admin/v3/statistics/top-risk-users?${params}`,
  {
    headers: {
      'Authorization': `Bearer ${jwtToken}`
    }
  }
);
const data = await response.json();

console.log(`高风险用户 Top ${data.total}:`);
data.items.forEach(item => {
  console.log(`  ${item.rank}. ${item.user_name} (${item.department})`);
  console.log(`      累计出错: ${item.total_errors} 次`);
  console.log(`      单文档平均出错: ${item.avg_errors_per_doc.toFixed(2)} 次`);
});

示例 2: 获取 2025年11月的 Top 10 高风险用户

cURL:

curl -X GET "http://localhost:8000/admin/v3/statistics/top-risk-users?limit=10&start_date=2025-11-01&end_date=2025-11-30" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Python:

import requests

url = "http://localhost:8000/admin/v3/statistics/top-risk-users"
headers = {
    "Authorization": f"Bearer {jwt_token}"
}
params = {
    "limit": 10,
    "start_date": "2025-11-01",
    "end_date": "2025-11-30"
}

response = requests.get(url, headers=headers, params=params)
data = response.json()

print(f"2025年11月高风险用户 Top {data['total']}:")
for item in data['items']:
    print(f"  {item['rank']}. {item['user_name']} (ID: {item['user_id']})")
    print(f"      部门: {item['department']}")
    print(f"      累计出错: {item['total_errors']} 次")
    print(f"      单文档平均出错: {item['avg_errors_per_doc']:.2f} 次")

响应示例

成功响应 (200 OK):

{
  "total": 5,
  "items": [
    {
      "rank": 1,
      "user_id": 123,
      "user_name": "张三",
      "department": "工程部",
      "total_errors": 45,
      "avg_errors_per_doc": 3.75
    },
    {
      "rank": 2,
      "user_id": 456,
      "user_name": "李四",
      "department": "采购部",
      "total_errors": 38,
      "avg_errors_per_doc": 3.17
    },
    {
      "rank": 3,
      "user_id": 789,
      "user_name": "王五",
      "department": "法务部",
      "total_errors": 32,
      "avg_errors_per_doc": 2.67
    },
    {
      "rank": 4,
      "user_id": 234,
      "user_name": "赵六",
      "department": "工程部",
      "total_errors": 28,
      "avg_errors_per_doc": 2.33
    },
    {
      "rank": 5,
      "user_id": 567,
      "user_name": "孙七",
      "department": "采购部",
      "total_errors": 25,
      "avg_errors_per_doc": 2.08
    }
  ]
}

失败响应 - 权限不足 (403 Forbidden):

{
  "detail": "此接口仅限管理员访问"
}

业务逻辑

1. 验证JWT Token
2. 提取用户信息(user_id, user_role, area
3. ✅ 权限检查
   ├─ 验证user_role == "admin"
   ├─ 验证current_user.area存在
   └─ 不满足则返回403或400
4. 📅 解析时间参数(同高频错误评查点)
5. 🗂️ 调用服务层
   ├─ StatisticsService.get_top_risk_users()
   ├─ 传入参数: area, limit, start_date, end_date
   └─ 查询evaluation_results + documents + users表
6. 📊 统计逻辑
   ├─ 筛选条件:
   │   ├─ result = 'false' (评查失败)
   │   ├─ documents.ou_id = current_user.area (地市隔离)
   │   └─ created_at BETWEEN start_date AND end_date (时间范围)
   ├─ 分组统计:
   │   ├─ GROUP BY documents.user_id
   │   ├─ COUNT(*) AS total_errors (累计出错数)
   │   └─ COUNT(DISTINCT document_id) AS doc_count (文档总数)
   ├─ 计算平均值:
   │   └─ avg_errors_per_doc = total_errors / doc_count
   └─ 排序: ORDER BY total_errors DESC
7. 📤 构建响应
   ├─ 添加排名(rank)
   ├─ 联表查询用户名和部门
   └─ 返回TopRiskUsersResponse

统计说明

计算公式:

累计出错数 = 该用户所有文档的错误评查点总数
单文档平均出错数 = 累计出错数 / 文档总数

示例:

用户A:
  - 文档1: 3个错误评查点
  - 文档2: 5个错误评查点
  - 文档3: 2个错误评查点

结果:
  - total_errors = 10
  - doc_count = 3
  - avg_errors_per_doc = 10 / 3 = 3.33

4. 权限控制

4.1 权限验证函数

代码位置: app/routes/v3/statistics.py:24-55

def require_admin(current_user: User = Depends(verify_token)) -> User:
    """
    权限验证:要求管理员角色

    Args:
        current_user: 当前登录用户

    Returns:
        User: 验证通过的管理员用户

    Raises:
        HTTPException: 403 如果用户不是管理员
    """
    if current_user.user_role != "admin":
        logger.warning(
            f"用户 {current_user.username} (角色:{current_user.user_role}) "
            f"尝试访问管理员专属统计接口"
        )
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="此接口仅限管理员访问"
        )

    # 检查用户是否有地市信息
    if not hasattr(current_user, 'area') or not current_user.area:
        logger.error(f"管理员用户 {current_user.username} 缺少地市(area)信息")
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="用户缺少地市信息,无法进行统计查询"
        )

    return current_user

4.2 权限检查流程

1. JWT Token 验证
   ├─ 验证Token签名
   ├─ 验证Token有效期
   └─ 提取用户信息(user_id, username, user_role, area
2. 角色验证
   ├─ 检查user_role == "admin"
   └─ 不是管理员则返回403
3. 地市信息验证
   ├─ 检查current_user.area存在
   └─ 不存在则返回400

4.3 权限级别

用户角色 访问权限 可见数据范围
admin 允许访问 只能查看自己地市的统计数据
provincial_admin 禁止访问 -
user 禁止访问 -
guest 禁止访问 -

注意: 即使是省级管理员(provincial_admin)也无法访问统计接口,因为统计接口专为地市级管理员设计。


5. 地市隔离机制

5.1 地市隔离原理

数据过滤逻辑:

-- 高频错误评查点统计
SELECT
  ep.id AS evaluation_point_id,
  ep.name AS point_name,
  COUNT(DISTINCT d.user_id) AS error_user_count
FROM evaluation_results er
JOIN evaluation_points ep ON er.evaluation_point_id = ep.id
JOIN documents d ON er.document_id = d.id
WHERE er.evaluated_results->>'result' = 'false'
  AND d.ou_id = ${current_user.area}  -- 地市隔离
  AND er.created_at BETWEEN ${start_date} AND ${end_date}
GROUP BY ep.id, ep.name
ORDER BY error_user_count DESC
LIMIT ${limit};

关键字段:

  • documents.ou_id: 文档所属地市(如: "梅州", "云浮", "揭阳", "潮州"
  • current_user.area: 当前管理员所属地市

5.2 地市映射

地市代码 地市名称 说明
梅州 梅州市 地市级
云浮 云浮市 地市级
揭阳 揭阳市 地市级
潮州 潮州市 地市级
省级 广东省 省级(暂不支持统计接口)

5.3 地市隔离示例

场景:

  • 梅州管理员登录
  • JWT Token中 area = "梅州"

查询结果:

  • 包含:梅州市用户的所有文档评查数据
  • 不包含:云浮、揭阳、潮州的文档评查数据

效果:

梅州管理员查询高频错误评查点:
  1. 管辖地点明确性 - 38人出错(都是梅州市用户)
  2. 履约保证金比例 - 25人出错(都是梅州市用户)
  ...

云浮管理员查询高频错误评查点:
  1. 付款条款完整性 - 42人出错(都是云浮市用户)
  2. 工程质量标准 - 30人出错(都是云浮市用户)
  ...

6. 数据结构

6.1 高频错误评查点

interface TopErrorPointItem {
  rank: number;                    // 排名(1, 2, 3...
  evaluation_point_id: number;     // 评查点ID
  point_name: string;              // 评查点名称
  error_user_count: number;        // 累计出错用户数(去重)
}

interface TopErrorPointsResponse {
  total: number;                          // 返回记录数
  items: TopErrorPointItem[];             // 评查点列表
}

6.2 高风险用户

interface TopRiskUserItem {
  rank: number;                    // 排名(1, 2, 3...
  user_id: number;                 // 用户ID
  user_name: string;               // 用户真实姓名
  department: string;              // 所在部门
  total_errors: number;            // 累计出错数
  avg_errors_per_doc: number;      // 单文档平均出错数(保留2位小数)
}

interface TopRiskUsersResponse {
  total: number;                          // 返回记录数
  items: TopRiskUserItem[];               // 用户列表
}

7. 使用示例

7.1 完整的统计看板示例

Vue 3 + Element Plus + ECharts:

<template>
  <div class="statistics-dashboard">
    <el-row :gutter="20">
      <!-- 时间范围选择器 -->
      <el-col :span="24">
        <el-card>
          <el-date-picker
            v-model="dateRange"
            type="daterange"
            range-separator=""
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            value-format="YYYY-MM-DD"
            @change="fetchAllStatistics"
          />
          <el-button type="primary" style="margin-left: 10px;" @click="resetDateRange">
            查看全部历史
          </el-button>
        </el-card>
      </el-col>

      <!-- 高频错误评查点 -->
      <el-col :span="12">
        <el-card>
          <template #header>
            <div class="card-header">
              <span>高频错误评查点 Top 10</span>
              <el-tag type="danger">重点关注</el-tag>
            </div>
          </template>

          <div ref="errorPointsChart" style="height: 400px;"></div>

          <el-table :data="errorPoints" style="margin-top: 20px;">
            <el-table-column prop="rank" label="排名" width="80" />
            <el-table-column prop="point_name" label="评查点名称" />
            <el-table-column prop="error_user_count" label="出错人数" width="120">
              <template #default="{ row }">
                <el-tag type="danger">{{ row.error_user_count }} </el-tag>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </el-col>

      <!-- 高风险用户 -->
      <el-col :span="12">
        <el-card>
          <template #header>
            <div class="card-header">
              <span>高风险用户 Top 10</span>
              <el-tag type="warning">需要培训</el-tag>
            </div>
          </template>

          <div ref="riskUsersChart" style="height: 400px;"></div>

          <el-table :data="riskUsers" style="margin-top: 20px;">
            <el-table-column prop="rank" label="排名" width="80" />
            <el-table-column prop="user_name" label="用户" />
            <el-table-column prop="department" label="部门" />
            <el-table-column prop="total_errors" label="累计出错" width="120">
              <template #default="{ row }">
                <el-tag type="warning">{{ row.total_errors }} </el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="avg_errors_per_doc" label="平均出错" width="120">
              <template #default="{ row }">
                {{ row.avg_errors_per_doc.toFixed(2) }}
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick } from 'vue';
import axios from 'axios';
import * as echarts from 'echarts';
import { ElMessage } from 'element-plus';

const dateRange = ref(null);
const errorPoints = ref([]);
const riskUsers = ref([]);
const errorPointsChart = ref(null);
const riskUsersChart = ref(null);

let errorPointsChartInstance = null;
let riskUsersChartInstance = null;

async function fetchErrorPoints() {
  try {
    const params = {
      limit: 10
    };

    if (dateRange.value && dateRange.value.length === 2) {
      params.start_date = dateRange.value[0];
      params.end_date = dateRange.value[1];
    }

    const { data } = await axios.get('/admin/v3/statistics/top-error-points', { params });
    errorPoints.value = data.items;

    // 更新图表
    renderErrorPointsChart(data.items);
  } catch (error) {
    ElMessage.error('获取高频错误评查点失败');
    console.error(error);
  }
}

async function fetchRiskUsers() {
  try {
    const params = {
      limit: 10
    };

    if (dateRange.value && dateRange.value.length === 2) {
      params.start_date = dateRange.value[0];
      params.end_date = dateRange.value[1];
    }

    const { data } = await axios.get('/admin/v3/statistics/top-risk-users', { params });
    riskUsers.value = data.items;

    // 更新图表
    renderRiskUsersChart(data.items);
  } catch (error) {
    ElMessage.error('获取高风险用户失败');
    console.error(error);
  }
}

async function fetchAllStatistics() {
  await Promise.all([
    fetchErrorPoints(),
    fetchRiskUsers()
  ]);
}

function resetDateRange() {
  dateRange.value = null;
  fetchAllStatistics();
}

function renderErrorPointsChart(items) {
  if (!errorPointsChartInstance) {
    errorPointsChartInstance = echarts.init(errorPointsChart.value);
  }

  const option = {
    title: {
      text: '高频错误评查点统计'
    },
    tooltip: {
      trigger: 'axis',
      axisPointer: {
        type: 'shadow'
      }
    },
    xAxis: {
      type: 'value',
      name: '出错人数'
    },
    yAxis: {
      type: 'category',
      data: items.map(item => item.point_name).reverse(),
      axisLabel: {
        formatter: (value) => {
          return value.length > 10 ? value.substring(0, 10) + '...' : value;
        }
      }
    },
    series: [{
      name: '出错人数',
      type: 'bar',
      data: items.map(item => item.error_user_count).reverse(),
      itemStyle: {
        color: '#f56c6c'
      },
      label: {
        show: true,
        position: 'right'
      }
    }]
  };

  errorPointsChartInstance.setOption(option);
}

function renderRiskUsersChart(items) {
  if (!riskUsersChartInstance) {
    riskUsersChartInstance = echarts.init(riskUsersChart.value);
  }

  const option = {
    title: {
      text: '高风险用户统计'
    },
    tooltip: {
      trigger: 'axis',
      axisPointer: {
        type: 'shadow'
      }
    },
    legend: {
      data: ['累计出错数', '平均出错数']
    },
    xAxis: {
      type: 'category',
      data: items.map(item => item.user_name)
    },
    yAxis: [
      {
        type: 'value',
        name: '累计出错数'
      },
      {
        type: 'value',
        name: '平均出错数'
      }
    ],
    series: [
      {
        name: '累计出错数',
        type: 'bar',
        data: items.map(item => item.total_errors),
        itemStyle: {
          color: '#e6a23c'
        }
      },
      {
        name: '平均出错数',
        type: 'line',
        yAxisIndex: 1,
        data: items.map(item => item.avg_errors_per_doc),
        itemStyle: {
          color: '#f56c6c'
        }
      }
    ]
  };

  riskUsersChartInstance.setOption(option);
}

onMounted(() => {
  nextTick(() => {
    fetchAllStatistics();
  });

  // 响应式调整图表大小
  window.addEventListener('resize', () => {
    errorPointsChartInstance?.resize();
    riskUsersChartInstance?.resize();
  });
});
</script>

<style scoped>
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>

8. 最佳实践

8.1 定期统计分析

建议频率:

  • 周报: 每周一查看上周统计数据
  • 月报: 每月初查看上月统计数据
  • 季度报: 每季度末查看季度统计数据

Python 定时任务示例:

import requests
from datetime import datetime, timedelta
import schedule
import time

def weekly_statistics_report(jwt_token: str):
    """
    每周统计报告生成
    """
    # 计算上周的日期范围
    today = datetime.now()
    last_monday = today - timedelta(days=today.weekday() + 7)
    last_sunday = last_monday + timedelta(days=6)

    start_date = last_monday.strftime('%Y-%m-%d')
    end_date = last_sunday.strftime('%Y-%m-%d')

    print(f"\n=== 上周统计报告 ({start_date} ~ {end_date}) ===\n")

    # 获取高频错误评查点
    error_points_resp = requests.get(
        "http://localhost:8000/admin/v3/statistics/top-error-points",
        headers={"Authorization": f"Bearer {jwt_token}"},
        params={
            "limit": 10,
            "start_date": start_date,
            "end_date": end_date
        }
    )

    if error_points_resp.status_code == 200:
        error_points = error_points_resp.json()['items']
        print("高频错误评查点 Top 10:")
        for item in error_points:
            print(f"  {item['rank']}. {item['point_name']} - {item['error_user_count']} 人")

    # 获取高风险用户
    risk_users_resp = requests.get(
        "http://localhost:8000/admin/v3/statistics/top-risk-users",
        headers={"Authorization": f"Bearer {jwt_token}"},
        params={
            "limit": 5,
            "start_date": start_date,
            "end_date": end_date
        }
    )

    if risk_users_resp.status_code == 200:
        risk_users = risk_users_resp.json()['items']
        print("\n高风险用户 Top 5:")
        for item in risk_users:
            print(f"  {item['rank']}. {item['user_name']} ({item['department']}) - {item['total_errors']} 次")

    print("\n==========================================\n")


# 设置定时任务:每周一早上9点执行
schedule.every().monday.at("09:00").do(weekly_statistics_report, jwt_token="your_jwt_token")

while True:
    schedule.run_pending()
    time.sleep(60)

8.2 改进措施建议

基于高频错误评查点:

出错人数 改进措施 优先级
> 30人 全员培训、修订模板、优化流程 🔴
20-30人 针对性培训、发布指导文档 🟡
10-20人 内部分享、案例学习 🟢
< 10人 个别指导 -

基于高风险用户:

累计出错数 改进措施 优先级
> 40次 一对一培训、质量把控 🔴
30-40次 小组培训、定期复查 🟡
20-30次 案例分享、自我学习 🟢
< 20次 日常指导 -

8.3 统计数据导出

Python导出Excel示例:

import requests
import pandas as pd
from datetime import datetime

def export_statistics_to_excel(jwt_token: str, output_file: str):
    """
    导出统计数据到Excel
    """
    # 获取高频错误评查点
    error_points_resp = requests.get(
        "http://localhost:8000/admin/v3/statistics/top-error-points",
        headers={"Authorization": f"Bearer {jwt_token}"},
        params={"limit": 50}
    )
    error_points = error_points_resp.json()['items']

    # 获取高风险用户
    risk_users_resp = requests.get(
        "http://localhost:8000/admin/v3/statistics/top-risk-users",
        headers={"Authorization": f"Bearer {jwt_token}"},
        params={"limit": 20}
    )
    risk_users = risk_users_resp.json()['items']

    # 创建Excel文件
    with pd.ExcelWriter(output_file, engine='openpyxl') as writer:
        # 高频错误评查点
        df_error_points = pd.DataFrame(error_points)
        df_error_points.to_excel(
            writer,
            sheet_name='高频错误评查点',
            index=False
        )

        # 高风险用户
        df_risk_users = pd.DataFrame(risk_users)
        df_risk_users.to_excel(
            writer,
            sheet_name='高风险用户',
            index=False
        )

    print(f"统计数据已导出到: {output_file}")


# 使用示例
export_statistics_to_excel(
    jwt_token="your_jwt_token",
    output_file=f"统计报告_{datetime.now().strftime('%Y%m%d')}.xlsx"
)

相关文档


文档版本: v1.0 最后更新: 2025-11-20 维护者: DocAuditAI 开发团队