作者:呱牛
发布日期:2026年3月30日
标签:FastAPI、绩效考核、任务明细
🔥 今日亮点
2026年3月30日 - 任务明细与技术指标计算完成
-
✅ 完成任务明细文件上传功能:新增独立上传按钮,支持《团队任务派单明细》Excel文件
-
✅ 实现任务明细得分汇总计算:按工号聚合task_score,更新到task_total_score字段
-
✅ 开发工作量天数计算功能:遍历C101-C403指标表,聚合work_days字段
-
✅ 实现功能点计算功能:从*_pa_kpi_c301表聚合function_points字段
-
✅ 开发代码行计算功能:从*_pa_kpi_c301表聚合code_lines字段
-
✅ 修复类方法调用错误:将self改为cls,确保方法正确调用
-
✅ 完善日志记录:生成详细的计算过程和结果日志
-
✅ 扩展绩效考核结果表:新增task_total_score、function_points、code_lines字段
📋 文章目录
-
[🎯 任务明细与技术指标计算完成](#🎯 任务明细与技术指标计算完成)
-
[🐛 问题排查与修复](#🐛 问题排查与修复)
-
[🔧 代码优化](#🔧 代码优化)
-
[📊 完整流程](#📊 完整流程)
-
[🚀 技术要点](#🚀 技术要点)
-
[📈 待实现功能](#📈 待实现功能)
-
[🧪 测试验证](#🧪 测试验证)
-
[⚠️ 注意事项](#⚠️ 注意事项)
-
[📝 今日总结](#📝 今日总结)
🎯 任务明细与技术指标计算完成
1.1 功能需求
扩展绩效考核系统功能,支持任务明细文件上传和技术指标计算:
-
新增任务明细文件上传按钮,支持《团队任务派单明细》Excel文件
-
实现任务明细得分汇总计算,按工号聚合task_score
-
开发工作量天数计算,遍历C101-C403指标表聚合work_days
-
实现功能点计算,从*_pa_kpi_c301表聚合function_points
-
开发代码行计算,从*_pa_kpi_c301表聚合code_lines
1.2 实现步骤
步骤1:前端页面添加任务明细上传按钮
<!-- 在绩效明细上传按钮下方添加任务明细上传按钮 -->
<el-form-item label="任务明细" prop="task_file">
<single-file-upload
v-model="formData.task_file"
:trigger-params="{
action: `${BASE_API}/gencode/*_pa_excel_upload_record/upload_task_file`,
headers: {
'Authorization': `Bearer ${token}`
}
}"
:accept=".xlsx,.xls"
:limit="1"
:file-size="50 * 1024"
:max-count="1"
:upload-type="'task'"
@change="handleTaskFileChange"
>
<el-button type="primary" plain>任务明细:选择Excel文件(《团队任务派单明细》)</el-button>
</single-file-upload>
</el-form-item>
步骤2:后端添加任务明细文件解析方法
@classmethod
async def parse_task_file(cls, auth, file_path, file_name, assessment_period, data_date):
"""
解析团队任务派单明细文件
参数:
- auth: 认证对象
- file_path: 文件路径
- file_name: 文件名
- assessment_period: 考核期次
- data_date: 数据时点
返回:
- dict: 解析结果
"""
from app.utils.excel_util import DynamicExcelParser
from app.utils.data_cleaning_util import DataCleaningUtil
from ..*_pa_task_assignment_score.model import *PaTaskAssignmentScoreModel
from ..*_pa_task_assignment_score.crud import *PaTaskAssignmentScoreCRUD
# 定义列映射
column_mapping = {
'工号': 'staff_no',
'姓名': 'real_name',
'岗位': 'position',
'部门': 'department',
'任务编号': 'task_no',
'任务名称': 'task_name',
'任务类型': 'task_type',
'任务状态': 'task_status',
'派单时间': 'dispatch_time',
'完成时间': 'completion_time',
'任务时长': 'task_duration',
'明细得分': 'task_score'
}
# 解析Excel文件
parser = DynamicExcelParser(file_path, column_mapping)
parsed_data = parser.parse()
# 数据清洗
cleaned_data = []
for row in parsed_data:
cleaned_row = DataCleaningUtil.clean_data(row)
cleaned_row['assessment_period'] = assessment_period
cleaned_row['data_date'] = data_date
cleaned_data.append(cleaned_row)
# 插入数据
crud = *PaTaskAssignmentScoreCRUD(model=*PaTaskAssignmentScoreModel, auth=auth)
inserted_count = 0
for data in cleaned_data:
try:
await crud.create_*_pa_task_assignment_score_crud(obj_in=data)
inserted_count += 1
except Exception as e:
log.error(f"插入任务明细数据失败: {str(e)}")
return {
'status': 'success',
'message': f'任务明细文件解析完成,共插入 {inserted_count} 条记录',
'inserted_count': inserted_count
}
步骤3:实现任务明细得分汇总计算
@classmethod
async def calculate_task_total_score(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
"""
计算任务明细汇总得分
参数:
- auth: 认证对象
- assessment_period: 考核期次
- data_date: 数据时点
- data_timepoint_date: 数据时点日期字符串
- staff_infos: 员工信息列表
返回:
- dict: 计算结果
"""
from sqlalchemy import select, func, text
from app.config.path_conf import LOG_DIR
from collections import defaultdict
import datetime
from ..*_pa_task_assignment_score.model import *PaTaskAssignmentScoreModel
# 初始化所有员工的任务得分为0
staff_scores = defaultdict(float)
for staff in staff_infos:
staff_no = staff[0]
staff_scores[staff_no] = 0.0
# 构建查询,按工号聚合task_score
stmt = select(
*PaTaskAssignmentScoreModel.staff_no,
func.sum(*PaTaskAssignmentScoreModel.task_score).label('total_score')
).where(
*PaTaskAssignmentScoreModel.assessment_period == assessment_period,
*PaTaskAssignmentScoreModel.data_date == data_date
).group_by(
*PaTaskAssignmentScoreModel.staff_no
)
# 执行查询
result = await auth.db.execute(stmt)
rows = result.all()
# 构建得分字典
task_scores = {}
for row in rows:
staff_no = row.staff_no
total_score = float(row.total_score or 0)
task_scores[staff_no] = total_score
# 累加到员工得分
for staff_no, score in task_scores.items():
staff_scores[staff_no] = score
# 执行UPDATE语句
update_count = 0
for staff_no, total_score in staff_scores.items():
update_sql = f"""
UPDATE `*_pa_performance_assessment`
SET `task_total_score` = {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} 的任务得分失败: {str(e)}")
# 使用flush而不是commit,让上下文管理器自动处理事务提交
await auth.db.flush()
return {
'status': 'success',
'message': f'任务明细汇总得分计算完成,共更新 {update_count} 条记录',
'updated_count': update_count
}
步骤4:实现工作量天数计算
@classmethod
async def _calculate_workload_days(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
"""
计算工作量天数汇总
参数:
- auth: 认证对象
- assessment_period: 考核期次
- data_date: 数据时点
- data_timepoint_date: 数据时点日期字符串
- staff_infos: 员工信息列表
返回:
- dict: 计算结果
"""
from datetime import datetime
from collections import defaultdict
from sqlalchemy import select, func, text
from app.config.path_conf import LOG_DIR
# 初始化所有员工的工作量天数为0
staff_workload = defaultdict(float)
for staff in staff_infos:
staff_no = staff[0]
staff_workload[staff_no] = 0.0
# 定义需要检查的指标表
indicator_tables = [
'*_pa_kpi_c101', '*_pa_kpi_c102', '*_pa_kpi_c103',
'*_pa_kpi_c105', '*_pa_kpi_c106', '*_pa_kpi_c107',
'*_pa_kpi_c201', '*_pa_kpi_c202', '*_pa_kpi_c203',
'*_pa_kpi_c204', '*_pa_kpi_c205', '*_pa_kpi_c206',
'*_pa_kpi_c207', '*_pa_kpi_c208', '*_pa_kpi_c209',
'*_pa_kpi_c210', '*_pa_kpi_c211', '*_pa_kpi_c212',
'*_pa_kpi_c213', '*_pa_kpi_c214', '*_pa_kpi_c301',
'*_pa_kpi_c401', '*_pa_kpi_c402', '*_pa_kpi_c403'
]
# 遍历每个指标表
for table_name in indicator_tables:
try:
# 动态导入模型
import importlib
module_path = f'app.plugin.module_gencode.{table_name}.model'
module = importlib.import_module(module_path)
# 生成正确的模型类名(驼峰命名)
model_name_parts = table_name.split('_')
model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
model_class = getattr(module, model_class_name)
# 检查模型是否有work_days字段
if hasattr(model_class, 'work_days'):
# 构建查询,按工号聚合work_days
stmt = select(
model_class.staff_no,
func.sum(model_class.work_days).label('total_work_days')
).where(
model_class.assessment_period == assessment_period,
model_class.data_date == data_date
).group_by(
model_class.staff_no
)
# 执行查询
result = await auth.db.execute(stmt)
rows = result.all()
# 累加到员工工作量
for row in rows:
staff_no = row.staff_no
total_work_days = float(row.total_work_days or 0)
staff_workload[staff_no] += total_work_days
except Exception as e:
log.error(f"处理表 {table_name} 失败: {str(e)}")
# 执行UPDATE语句
update_count = 0
for staff_no, total_work_days in staff_workload.items():
update_sql = f"""
UPDATE `*_pa_performance_assessment`
SET `workload_days` = {total_work_days:.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} 的工作量天数失败: {str(e)}")
# 使用flush而不是commit,让上下文管理器自动处理事务提交
await auth.db.flush()
return {
'status': 'success',
'message': f'工作量天数计算完成,共更新 {update_count} 条记录',
'update_count': update_count
}
步骤5:实现功能点计算
@classmethod
async def _calculate_function_points(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
"""
计算功能点汇总
参数:
- auth: 认证对象
- assessment_period: 考核期次
- data_date: 数据时点
- data_timepoint_date: 数据时点日期字符串
- staff_infos: 员工信息列表
返回:
- dict: 计算结果
"""
from sqlalchemy import select, func, text
from app.config.path_conf import LOG_DIR
from collections import defaultdict
import datetime
# 初始化所有员工的功能点为0
staff_function_points = defaultdict(int)
for staff in staff_infos:
staff_no = staff[0]
staff_function_points[staff_no] = 0
# 定义需要检查的指标表
indicator_tables = [
'*_pa_kpi_c301'
# 未来可以添加更多表
]
# 遍历每个指标表
for table_name in indicator_tables:
try:
# 动态导入模型
import importlib
module_path = f'app.plugin.module_gencode.{table_name}.model'
module = importlib.import_module(module_path)
# 生成正确的模型类名(驼峰命名)
model_name_parts = table_name.split('_')
model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
model_class = getattr(module, model_class_name)
# 检查模型是否有function_points字段
if hasattr(model_class, 'function_points'):
# 构建查询,按工号聚合function_points
stmt = select(
model_class.staff_no,
func.sum(model_class.function_points).label('total_function_points')
).where(
model_class.assessment_period == assessment_period,
model_class.data_date == data_date
).group_by(
model_class.staff_no
)
# 执行查询
result = await auth.db.execute(stmt)
rows = result.all()
# 累加到员工功能点
for row in rows:
staff_no = row.staff_no
total_function_points = int(row.total_function_points or 0)
staff_function_points[staff_no] += total_function_points
except Exception as e:
log.error(f"处理表 {table_name} 失败: {str(e)}")
# 执行UPDATE语句
update_count = 0
for staff_no, total_function_points in staff_function_points.items():
update_sql = f"""
UPDATE `*_pa_performance_assessment`
SET `function_points` = {total_function_points},
`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} 的功能点失败: {str(e)}")
# 使用flush而不是commit,让上下文管理器自动处理事务提交
await auth.db.flush()
return {
'status': 'success',
'message': f'功能点计算完成,共更新 {update_count} 条记录',
'update_count': update_count
}
步骤6:实现代码行计算
@classmethod
async def _calculate_code_lines(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
"""
计算代码行汇总
参数:
- auth: 认证对象
- assessment_period: 考核期次
- data_date: 数据时点
- data_timepoint_date: 数据时点日期字符串
- staff_infos: 员工信息列表
返回:
- dict: 计算结果
"""
from sqlalchemy import select, func, text
from app.config.path_conf import LOG_DIR
from collections import defaultdict
import datetime
# 初始化所有员工的代码行为0
staff_code_lines = defaultdict(int)
for staff in staff_infos:
staff_no = staff[0]
staff_code_lines[staff_no] = 0
# 定义需要检查的指标表
indicator_tables = [
'*_pa_kpi_c301'
# 未来可以添加更多表
]
# 遍历每个指标表
for table_name in indicator_tables:
try:
# 动态导入模型
import importlib
module_path = f'app.plugin.module_gencode.{table_name}.model'
module = importlib.import_module(module_path)
# 生成正确的模型类名(驼峰命名)
model_name_parts = table_name.split('_')
model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
model_class = getattr(module, model_class_name)
# 检查模型是否有code_lines字段
if hasattr(model_class, 'code_lines'):
# 构建查询,按工号聚合code_lines
stmt = select(
model_class.staff_no,
func.sum(model_class.code_lines).label('total_code_lines')
).where(
model_class.assessment_period == assessment_period,
model_class.data_date == data_date
).group_by(
model_class.staff_no
)
# 执行查询
result = await auth.db.execute(stmt)
rows = result.all()
# 累加到员工代码行
for row in rows:
staff_no = row.staff_no
total_code_lines = int(row.total_code_lines or 0)
staff_code_lines[staff_no] += total_code_lines
except Exception as e:
log.error(f"处理表 {table_name} 失败: {str(e)}")
# 执行UPDATE语句
update_count = 0
for staff_no, total_code_lines in staff_code_lines.items():
update_sql = f"""
UPDATE `*_pa_performance_assessment`
SET `code_lines` = {total_code_lines},
`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} 的代码行失败: {str(e)}")
# 使用flush而不是commit,让上下文管理器自动处理事务提交
await auth.db.flush()
return {
'status': 'success',
'message': f'代码行计算完成,共更新 {update_count} 条记录',
'update_count': update_count
}
步骤7:集成到考核期次得分计算流程
@classmethod
async def calculate_*_pa_excel_upload_record_service(cls, auth, id):
"""
计算考核期次得分
参数:
- auth: 认证对象
- id: 上传记录ID
返回:
- dict: 计算结果
"""
# 省略部分代码...
# 计算任务明细汇总得分
task_result = await cls.calculate_task_total_score(
auth=auth,
assessment_period=assessment_period,
data_date=data_date,
data_timepoint_date=data_timepoint_date,
staff_infos=staff_infos
)
log.info(f"任务明细汇总得分计算结果: {task_result}")
# 计算工作量天数
workload_result = await cls._calculate_workload_days(
auth=auth,
assessment_period=assessment_period,
data_date=data_date,
data_timepoint_date=data_timepoint_date,
staff_infos=staff_infos
)
log.info(f"工作量天数计算结果: {workload_result}")
# 计算功能点汇总
function_points_result = await cls._calculate_function_points(
auth=auth,
assessment_period=assessment_period,
data_date=data_date,
data_timepoint_date=data_timepoint_date,
staff_infos=staff_infos
)
log.info(f"功能点计算结果: {function_points_result}")
# 计算代码行汇总
code_lines_result = await cls._calculate_code_lines(
auth=auth,
assessment_period=assessment_period,
data_date=data_date,
data_timepoint_date=data_timepoint_date,
staff_infos=staff_infos
)
log.info(f"代码行计算结果: {code_lines_result}")
# 计算分类汇总得分
await cls._calculate_category_totals(
auth=auth,
assessment_period=assessment_period,
data_timepoint_date=data_timepoint_date,
staff_infos=staff_infos
)
# 更新计算状态
update_data = {
'calculation_status': '已生成'
}
await *PaExcelUploadRecordCRUD(auth).update_*_pa_excel_upload_record_crud(id=id, data=update_data)
# 省略部分代码...
🐛 问题排查与修复
2.1 类方法调用错误
问题描述
NameError: name 'self' is not defined
问题分析
-
calculate_*_pa_excel_upload_record_service是一个类方法,使用了self来调用其他方法 -
类方法应该使用
cls而不是self -
_calculate_workload_days和_calculate_function_points方法未定义为类方法
解决方案
# 1. 修改方法调用
workload_result = await cls._calculate_workload_days(...)
function_points_result = await cls._calculate_function_points(...)
code_lines_result = await cls._calculate_code_lines(...)
# 2. 修改方法定义
@classmethod
async def _calculate_workload_days(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
pass
@classmethod
async def _calculate_function_points(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
pass
@classmethod
async def _calculate_code_lines(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
pass
2.2 模型类名生成错误
问题描述
AttributeError: module 'app.plugin.module_gencode.*_pa_kpi_c102.model' has no attribute '*_pa_kpi_c102Model'
问题分析
-
模型类名生成错误,使用了
*_pa_kpi_c102Model而非正确的*PaKpiC102Model -
模型类名应该使用驼峰命名法
解决方案
# 生成正确的模型类名(驼峰命名)
model_name_parts = table_name.split('_')
model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
model_class = getattr(module, model_class_name)
2.3 字段缺失处理
问题描述
当指标表没有特定字段时(如work_days、function_points、code_lines),会导致错误。
解决方案
# 检查模型是否有特定字段
if hasattr(model_class, 'work_days'):
# 处理work_days字段
pass
if hasattr(model_class, 'function_points'):
# 处理function_points字段
pass
if hasattr(model_class, 'code_lines'):
# 处理code_lines字段
pass
🔧 代码优化
3.1 统一指标处理方法
优化前
# 为每个指标编写单独的处理逻辑
def calculate_workload_days():
# 处理逻辑
pass
def calculate_function_points():
# 处理逻辑
pass
def calculate_code_lines():
# 处理逻辑
pass
优化后
# 统一的指标处理方法
@classmethod
async def _calculate_metric(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos, metric_name, field_name):
"""
通用指标计算方法
参数:
- auth: 认证对象
- assessment_period: 考核期次
- data_date: 数据时点
- data_timepoint_date: 数据时点日期字符串
- staff_infos: 员工信息列表
- metric_name: 指标名称(如'work_days'、'function_points'、'code_lines')
- field_name: 目标字段名(如'workload_days'、'function_points'、'code_lines')
返回:
- dict: 计算结果
"""
# 统一处理逻辑
pass
3.2 日志记录优化
优化前
# 简单日志记录
log.info(f"计算完成,共更新 {update_count} 条记录")
优化后
# 详细日志记录,包括原始数据和计算结果
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_path = LOG_DIR / f"{metric_name}计算{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.log"
with open(log_path, 'w', encoding='utf-8') as f:
f.write(f"查询时间: {current_time}\n")
f.write(f"考核期次: {assessment_period}\n")
f.write(f"数据时点: {data_date}\n\n")
f.write("=" * 80 + "\n")
f.write(f"{metric_name}汇总:\n")
f.write("=" * 80 + "\n")
for staff_no, total_value in staff_values.items():
f.write(f"工号: {staff_no}, {metric_name}: {total_value}\n")
3.3 异常处理优化
优化前
# 简单异常处理
try:
# 处理逻辑
except Exception as e:
log.error(f"处理失败: {str(e)}")
优化后
# 详细异常处理,继续处理其他员工
try:
# 处理逻辑
except Exception as e:
log.error(f"处理员工 {staff_no} 失败: {str(e)}")
# 继续处理其他员工,不因单个错误而中断
📊 完整流程
4.1 任务明细与技术指标计算流程图
开始
↓
点击"计算考核期次得分"按钮
↓
查询员工基本信息(*_user_staff_info)
↓
写入员工信息日志
↓
计算任务明细汇总得分
↓
↓ 从*_pa_task_assignment_score表查询数据
↓ 按工号聚合task_score
↓ 更新task_total_score字段
↓
计算工作量天数
↓
↓ 遍历C101-C403指标表
↓ 检查work_days字段
↓ 按工号聚合work_days
↓ 更新workload_days字段
↓
计算功能点汇总
↓
↓ 从*_pa_kpi_c301表查询数据
↓ 检查function_points字段
↓ 按工号聚合function_points
↓ 更新function_points字段
↓
计算代码行汇总
↓
↓ 从*_pa_kpi_c301表查询数据
↓ 检查code_lines字段
↓ 按工号聚合code_lines
↓ 更新code_lines字段
↓
计算分类汇总得分
↓
↓ 需求工作汇总得分
↓ 项目工作汇总得分
↓ 自主研发汇总得分
↓ 运维工作汇总得分
↓ 汇总总得分
↓
更新计算状态为"已生成"
↓
返回计算结果
↓
结束
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_task_assignment_score │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ staff_no, task_no, task_name, task_score, │ │
│ │ assessment_period, data_date │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 指标数据表:*_pa_kpi_* │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ C101-C107, C201-C214, C301, C401-C403 │ │
│ │ staff_no, work_days, function_points, code_lines │ │
│ │ assessment_period, data_date │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 绩效考核结果表:*_pa_performance_assessment │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ 员工基本信息 + 指标得分 │ │
│ │ task_total_score = 任务明细汇总得分 │ │
│ │ workload_days = 工作量天数汇总 │ │
│ │ function_points = 功能点汇总 │ │
│ │ code_lines = 代码行汇总 │ │
│ │ demand_total_score = 需求工作汇总 │ │
│ │ project_total_score = 项目工作汇总 │ │
│ │ self_dev_total_score = 自主研发汇总 │ │
│ │ ops_total_score = 运维工作汇总 │ │
│ │ total_score = 总得分 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
🚀 技术要点
5.1 动态模型导入与字段检查
# 动态导入模型
import importlib
module_path = f'app.plugin.module_gencode.{table_name}.model'
module = importlib.import_module(module_path)
# 生成正确的模型类名(驼峰命名)
model_name_parts = table_name.split('_')
model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
model_class = getattr(module, model_class_name)
# 检查模型是否有特定字段
if hasattr(model_class, 'work_days'):
# 处理work_days字段
pass
优势:
-
支持任意指标表,无需为每个指标编写单独的处理逻辑
-
提高代码的通用性和可维护性
-
便于扩展新的指标类型
5.2 数据聚合与分组
# 构建查询,按工号聚合
stmt = select(
model_class.staff_no,
func.sum(model_class.function_points).label('total_function_points')
).where(
model_class.assessment_period == assessment_period,
model_class.data_date == data_date
).group_by(
model_class.staff_no
)
# 执行查询
result = await auth.db.execute(stmt)
rows = result.all()
优势:
-
使用SQL聚合函数提高查询效率
-
减少数据传输和处理开销
-
代码简洁易读
5.3 批量更新优化
# 执行UPDATE语句
update_count = 0
for staff_no, total_value in staff_values.items():
update_sql = f"""
UPDATE `*_pa_performance_assessment`
SET `{field_name}` = {total_value},
`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} 失败: {str(e)}")
# 使用flush而不是commit,让上下文管理器自动处理事务提交
await auth.db.flush()
优势:
-
减少事务开销
-
提高更新效率
-
便于错误处理和日志记录
5.4 日志管理
# 写入日志
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_path = LOG_DIR / f"{metric_name}计算{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.log"
with open(log_path, 'w', encoding='utf-8') as f:
f.write(f"查询时间: {current_time}\n")
f.write(f"考核期次: {assessment_period}\n")
f.write(f"数据时点: {data_date}\n\n")
f.write("=" * 80 + "\n")
f.write(f"{metric_name}汇总:\n")
f.write("=" * 80 + "\n")
for staff_no, total_value in staff_values.items():
f.write(f"工号: {staff_no}, {metric_name}: {total_value}\n")
优势:
-
详细记录计算过程和结果
-
便于排查问题
-
提供审计跟踪
📝 今日总结
6.1 完成功能
-
任务明细文件上传功能完整实现
-
任务明细得分汇总计算功能实现
-
工作量天数计算功能实现
-
功能点计算功能实现
-
代码行计算功能实现
-
类方法调用错误修复
-
模型类名生成错误修复
-
字段缺失处理优化
-
日志记录完善
-
绩效考核结果表扩展
6.2 技术收获
-
掌握动态模型导入和字段检查技术
-
学会使用SQL聚合函数进行数据分组
-
理解批量更新优化策略
-
掌握事务管理和错误处理技巧
-
提高代码的通用性和可维护性
-
理解NULL值处理和类型转换