作者:呱牛
发布日期:2026年3月28日
标签:FastAPI、绩效考核
🔥 今日亮点
2026年3月28日 - C101指标计算完成 & 代码优化
- ✅ 完成C101指标计算功能:实现完整的查询、聚合、更新流程
- ✅ 解决事务管理问题:修复"Can't operate on closed transaction"错误
- ✅ 解决重复计算问题:按指标编号去重,避免得分重复累加
- ✅ 解决类型转换问题:Decimal类型正确转换为float类型
- ✅ 解决字段缺失问题:动态检查字段是否存在,支持不同表结构
- ✅ 代码优化清理:删除冗余日志和注释代码
- ✅ 执行顺序优化:先插入员工信息,再更新指标得分
📋 文章目录
🎯 C101指标计算完成
1.1 功能需求
在上传考核期次页面的操作列表中增加"计算考核期次得分"按钮:
- 按钮状态与
*_pa_excel_upload_record表的calculation_status字段关联 - 状态默认为"未计算"(红色),计算完成后变为"已计算"(蓝色)
1.2 实现步骤
步骤1:查询员工基本信息
# 从*_user_staff_info表查询在职员工信息
stmt = select(
*UserStaffInfoModel.staff_no,
*UserStaffInfoModel.real_name,
*UserStaffInfoModel.position,
*UserStaffInfoModel.dept_level1_name,
*UserStaffInfoModel.dept_level2_name,
*UserStaffInfoModel.team_name
).where(
*UserStaffInfoModel.data_status == 1
).order_by(
*UserStaffInfoModel.id
)
# 执行查询
result = await auth.db.execute(stmt)
staff_infos = result.fetchall()
步骤2:查询C101指标数据
# 从*_pa_kpi_c101表查询指标数据
c101_select_columns = [
*PaKpiC101Model.staff_no,
*PaKpiC101Model.real_name,
*PaKpiC101Model.position,
*PaKpiC101Model.assessment_period,
*PaKpiC101Model.data_date,
*PaKpiC101Model.indicator_no,
*PaKpiC101Model.indicator_content,
*PaKpiC101Model.judgment_condition,
*PaKpiC101Model.indicator_score
]
c101_stmt = select(*c101_select_columns).where(
*PaKpiC101Model.assessment_period == assessment_period,
*PaKpiC101Model.data_date == data_date
).order_by(
*PaKpiC101Model.staff_no,
*PaKpiC101Model.indicator_no
)
# 执行查询
c101_result = await auth.db.execute(c101_stmt)
c101_data = c101_result.fetchall()
步骤3:按指标编号去重聚合得分
from collections import defaultdict
from decimal import Decimal
staff_scores = defaultdict(float)
staff_indicators = defaultdict(set) # 记录每个员工已处理的指标编号
for row in c101_data:
staff_no = row[0]
indicator_no = row[5]
indicator_score = row[8]
# 按指标编号去重
if indicator_score and indicator_no:
if indicator_no not in staff_indicators[staff_no]:
# 将Decimal类型转换为float类型
score_value = float(indicator_score) if isinstance(indicator_score, Decimal) else float(indicator_score)
staff_scores[staff_no] += score_value
staff_indicators[staff_no].add(indicator_no)
步骤4:插入员工信息到绩效考核结果表
# 使用CRUD创建员工记录
create_data = *PaPerformanceAssessmentCreateSchema(
status='0',
description=None,
staff_no=staff_no,
staff_name=real_name,
position=position,
dept_level1_name=dept_level1_name,
dept_level2_name=dept_level2_name,
team_name=team_name,
assessment_period=assessment_period,
data_timepoint=data_timepoint_date
)
await performance_crud.create_yurdmc_pa_performance_assessment_crud(data=create_data)
步骤5:执行UPDATE语句更新得分
from sqlalchemy import text
for staff_no, total_score in staff_scores.items():
update_sql = f"""
UPDATE `*_pa_performance_assessment`
SET `demand_score_c101` = {total_score:.2f},
`updated_time` = NOW()
WHERE `staff_no` = '{staff_no}'
AND `assessment_period` = '{assessment_period}'
AND `data_timepoint` = '{data_timepoint_date}';
"""
try:
await auth.db.execute(text(update_sql))
update_count += 1
except Exception as e:
log.error(f"更新员工 {staff_no} 的C101得分失败: {str(e)}")
# 使用flush而不是commit,让上下文管理器自动处理事务提交
await auth.db.flush()
🐛 问题排查与修复
2.1 事务管理问题
问题描述
InvalidRequestError: Can't operate on closed transaction inside context manager.
问题分析
问题根源:
1. 数据库连接使用了`async with session.begin()`上下文管理器
2. 手动调用`commit()`会导致事务被提前关闭
3. 后续操作无法在已关闭的事务中执行
4. 系统使用上下文管理器自动管理事务生命周期
解决方案
# 错误做法
await auth.db.commit()
# 正确做法
await auth.db.flush()
技术说明
flush()将更改同步到数据库,但不提交事务- 上下文管理器在请求结束时自动提交事务
- 避免手动干预事务生命周期
2.2 重复计算问题
问题描述
同一个指标编号在数据库中有多条记录,导致得分被重复累加。
问题数据示例
工号: 1113, 指标编号: 12344334343, 指标得分: 20.00
工号: 1113, 指标编号: 233233232323, 指标得分: 20.00
工号: 1113, 指标编号: 202323232323, 指标得分: 20.00
实际得分应该是:20.00
错误计算结果:60.00(重复累加了3次)
解决方案
# 使用字典记录已处理的指标编号
staff_indicators = defaultdict(set)
if indicator_no not in staff_indicators[staff_no]:
staff_scores[staff_no] += score_value
staff_indicators[staff_no].add(indicator_no)
2.3 执行顺序问题
问题描述
先执行UPDATE语句,后插入员工信息,导致UPDATE找不到匹配的记录。
问题分析
错误顺序:
1. 查询C101指标数据
2. 聚合得分
3. 执行UPDATE语句(此时员工记录还不存在)
4. 插入员工信息
正确顺序:
1. 查询C101指标数据
2. 聚合得分
3. 插入员工信息
4. 执行UPDATE语句(此时员工记录已存在)
解决方案
# 先插入员工信息
insert_count = await cls._insert_staff_to_performance_assessment(
auth=auth,
staff_infos=staff_infos,
assessment_period=assessment_period,
data_timepoint=data_timepoint_date
)
# 再执行UPDATE语句
for staff_no, total_score in staff_scores.items():
update_sql = f"UPDATE ..."
await auth.db.execute(text(update_sql))
2.4 类型转换问题
问题描述
TypeError: unsupported operand type(s) for +=: 'float' and 'decimal.Decimal'
问题分析
- 数据库查询出来的
indicator_score字段是Decimal类型 - 与float类型不兼容,无法直接进行累加操作
解决方案
# 将Decimal类型转换为float类型
score_value = float(indicator_score) if isinstance(indicator_score, Decimal) else float(indicator_score)
staff_scores[staff_no] += score_value
2.5 字段缺失问题
问题描述
AttributeError: type object '*PaKpiC106Model' has no attribute 'indicator_no'
问题分析
- 不同指标表的字段结构不同
- 通用处理函数假设所有模型都有相同字段
- C106模型缺少
indicator_no字段
解决方案
# 动态检查字段是否存在
if hasattr(model, 'indicator_no'):
c101_select_columns.append(model.indicator_no)
if hasattr(model, 'indicator_content'):
c101_select_columns.append(model.indicator_content)
if hasattr(model, 'judgment_condition'):
c101_select_columns.append(model.judgment_condition)
if hasattr(model, 'indicator_score'):
c101_select_columns.append(model.indicator_score)
📊 完整流程
4.1 C101指标得分计算流程图
开始
↓
点击"计算考核期次得分"按钮
↓
查询员工基本信息(*_user_staff_info)
↓
写入员工信息日志
↓
查询C101指标数据(*_pa_kpi_c101)
↓
写入C101指标数据日志
↓
按指标编号去重聚合得分
↓
插入员工信息到绩效考核结果表(*_pa_performance_assessment)
↓
执行UPDATE语句更新C101得分
↓
更新计算状态为"已生成"
↓
返回计算结果
↓
结束
4.2 数据流转图
┌─────────────────────────────────────────────────────────────┐
│ 数据源:Excel文件上传 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ *_pa_excel_upload_record │ │
│ │ - assessment_period: 202603 │ │
│ │ - data_date: 2026-03-23 │ │
│ │ - calculation_status: 未计算 → 已计算 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 员工信息表:*_user_staff_info │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ staff_no, real_name, position, dept_level1_name, │ │
│ │ dept_level2_name, team_name │ │
│ │ data_status = 1 (在职) │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 指标数据表:*_pa_kpi_c101 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ staff_no, indicator_no, indicator_score │ │
│ │ assessment_period, data_date │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 绩效考核结果表:*_pa_performance_assessment │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 员工基本信息 + 指标得分 │ │
│ │ demand_score_c101 = 汇总得分 │ │
│ │ assessment_period, data_timepoint │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
🚀 技术要点
5.1 SQLAlchemy异步操作
# 使用AsyncSession进行异步数据库操作
from sqlalchemy.ext.asyncio import AsyncSession
# 异步查询
result = await auth.db.execute(stmt)
data = result.fetchall()
# 异步执行SQL
await auth.db.execute(text(update_sql))
# 使用flush而不是commit
await auth.db.flush()
5.2 上下文管理器
# 数据库依赖注入
async def db_getter() -> AsyncGenerator[AsyncSession, None]:
async with async_db_session() as session:
async with session.begin(): # 自动事务管理
yield session
关键点:
- 使用
async with session.begin()自动管理事务 - 避免手动调用
commit()或rollback() - 上下文结束时自动提交事务
5.3 动态字段检查
# 检查模型字段是否存在
if hasattr(model, 'indicator_no'):
select_columns.append(model.indicator_no)
if hasattr(model, 'indicator_content'):
select_columns.append(model.indicator_content)
# 动态添加排序
if hasattr(model, 'staff_no'):
stmt = stmt.order_by(model.staff_no)
if hasattr(model, 'indicator_no'):
stmt = stmt.order_by(model.indicator_no)
优势:
- 支持不同结构的指标表
- 避免字段不存在导致的错误
- 提高代码的通用性和可维护性
5.4 去重聚合技巧
# 使用defaultdict和set实现去重
from collections import defaultdict
staff_scores = defaultdict(float)
staff_indicators = defaultdict(set)
# 按指标编号去重
if indicator_no not in staff_indicators[staff_no]:
staff_scores[staff_no] += score_value
staff_indicators[staff_no].add(indicator_no)
📈 待实现功能
6.1 其他指标处理
- C102指标得分计算
- C103指标得分计算
- C105指标得分计算
- C106指标得分计算
- C107指标得分计算
- C201-C214项目指标得分计算
- C301自主研发得分计算
- C401-C403运维指标得分计算
- C501-C503质量指标得分计算
- C601-C602安全指标得分计算
- C701-C703 OKR目标(研发岗)得分计算
- C801-C803 OKR目标(需求岗)得分计算
6.2 通用指标处理方法
@classmethod
async def _process_indicator_data(
cls,
auth: AuthSchema,
table_name: str,
field_name: str,
assessment_period: str,
data_date: str,
data_timepoint_date: str,
LOG_DIR: str
) -> None:
"""
处理指标数据,生成汇总得分和UPDATE SQL
参数:
- auth: AuthSchema - 认证信息
- table_name: str - 指标表名
- field_name: str - 目标字段名
- assessment_period: str - 考核期次
- data_date: str - 数据时点
- data_timepoint_date: str - 数据时点(date格式)
- LOG_DIR: str - 日志目录
"""
# 动态导入模型
model_name = f"*PaKpi{table_name.split('_')[-1].title()}Model"
module_path = f"app.plugin.module_gencode.{table_name}.model"
try:
import importlib
module = importlib.import_module(module_path)
model = getattr(module, model_name)
except Exception as e:
log.error(f"导入 {table_name} 模型失败: {str(e)}")
return
# 构建查询,动态检查字段是否存在
select_columns = [
model.staff_no,
model.real_name,
model.position,
model.assessment_period,
model.data_date,
]
# 检查可选字段是否存在
if hasattr(model, 'indicator_no'):
select_columns.append(model.indicator_no)
if hasattr(model, 'indicator_content'):
select_columns.append(model.indicator_content)
if hasattr(model, 'judgment_condition'):
select_columns.append(model.judgment_condition)
if hasattr(model, 'indicator_score'):
select_columns.append(model.indicator_score)
# 执行查询、聚合、更新...
🧪 测试验证
7.1 功能测试
- 按钮状态正确显示(未计算/已计算)
- 员工信息正确查询和插入
- C101指标数据正确查询
- 按指标编号去重正确聚合
- UPDATE语句正确执行
- 数据正确更新到数据库
- 日志文件正确生成
7.2 异常测试
- NULL值正确处理(不累加)
- 事务正确管理(不提前关闭)
- 类型转换正确处理(Decimal转float)
- 字段缺失正确处理(动态检查)
- 执行顺序正确(先插入后更新)
⚠️ 注意事项
8.1 数据一致性
- 确保assessment_period和data_timepoint字段在所有表中一致
- 使用相同的格式(assessment_period: '202603', data_timepoint: '2026-03-23')
- 避免因格式不一致导致UPDATE找不到匹配记录
8.2 性能优化
- 使用flush()而不是commit()避免事务开销
- 在循环外部创建CRUD实例,避免重复创建
- 使用批量操作减少数据库访问次数
- 合理使用索引提高查询效率
8.3 错误处理
- 捕获并记录UPDATE失败的情况
- 继续处理其他员工,不因单个错误而中断
- 使用详细的错误日志便于排查问题
- 提供友好的错误提示给用户
8.4 日志管理
- 日志文件按时间戳命名,避免覆盖
- 日志内容包含原始数据和汇总结果
- 日志级别合理使用(info、error)
- 避免日志文件过大影响性能
📝 今日总结
9.1 完成功能
- C101指标得分计算功能完整实现
- 员工信息查询和插入功能
- UPDATE语句执行功能
- 事务管理问题修复
- 重复计算问题解决
- 类型转换问题解决
- 字段缺失问题解决
- 执行顺序问题解决
- 代码优化和清理
9.2 技术收获
- 深入理解SQLAlchemy异步操作和事务管理
- 掌握动态字段检查和处理方法
- 学会了去重聚合的实现技巧
- 提高了代码质量和可维护性
- 理解了上下文管理器的工作原理
9.3 下一步计划
- 实现其他指标(C102、C103等)的处理逻辑
- 完善通用指标处理方法
- 添加数据验证和异常处理
- 优化性能和用户体验
- 完善单元测试和集成测试
编辑