Files
hospital_performance/backend/app/services/dimension_weight_service.py
2026-02-28 15:06:52 +08:00

266 lines
9.8 KiB
Python

"""
科室类型BSC维度权重配置服务
根据详细设计文档中的考核维度权重总览:
| 科室类型 | 财务维度 | 顾客维度 | 内部流程 | 学习成长 | 合计 |
|----------|----------|----------|----------|----------|------|
| 手术临床科室 | 60% | 15% | 20% | 5% | 100% |
| 非手术有病房科室 | 60% | 15% | 20% | 5% | 100% |
| 非手术无病房科室 | 60% | 15% | 20% | 5% | 100% |
| 医技科室 | 40% | 25% | 30% | 5% | 100% |
| 医疗辅助/行政科室 | 40% | 25% | 30% | 5% | 100% |
| 护理单元 | 20% | 15% | 50% | 15% | 100% |
| 药学部门 | 30% | 15% | 55% | - | 100% |
"""
from typing import Optional, List, Dict, Any
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.models import DeptType, DeptTypeDimensionWeight
# 默认权重配置(根据详细设计文档)
DEFAULT_WEIGHTS = {
DeptType.CLINICAL_SURGICAL: {
"financial": 0.60,
"customer": 0.15,
"internal_process": 0.20,
"learning_growth": 0.05,
"description": "手术临床科室:外科、骨科、泌尿外科、心胸外科、神经外科等"
},
DeptType.CLINICAL_NONSURGICAL_WARD: {
"financial": 0.60,
"customer": 0.15,
"internal_process": 0.20,
"learning_growth": 0.05,
"description": "非手术有病房科室:内科、神经内科、呼吸内科、消化内科等"
},
DeptType.CLINICAL_NONSURGICAL_NOWARD: {
"financial": 0.60,
"customer": 0.15,
"internal_process": 0.20,
"learning_growth": 0.05,
"description": "非手术无病房科室:门诊科室、急诊科等"
},
DeptType.MEDICAL_TECH: {
"financial": 0.40,
"customer": 0.25,
"internal_process": 0.30,
"learning_growth": 0.05,
"description": "医技科室:放射科、检验科、超声科、病理科、功能检查科等"
},
DeptType.MEDICAL_AUXILIARY: {
"financial": 0.40,
"customer": 0.25,
"internal_process": 0.30,
"learning_growth": 0.05,
"description": "医疗辅助/行政科室:设备科、信息科、总务科、财务科、人事科、医务科等"
},
DeptType.NURSING: {
"financial": 0.20,
"customer": 0.15,
"internal_process": 0.50,
"learning_growth": 0.15,
"description": "护理单元:各病区护理单元"
},
DeptType.ADMIN: {
"financial": 0.40,
"customer": 0.25,
"internal_process": 0.30,
"learning_growth": 0.05,
"description": "行政科室"
},
DeptType.FINANCE: {
"financial": 0.40,
"customer": 0.25,
"internal_process": 0.30,
"learning_growth": 0.05,
"description": "财务科室"
},
DeptType.LOGISTICS: {
"financial": 0.40,
"customer": 0.25,
"internal_process": 0.30,
"learning_growth": 0.05,
"description": "后勤保障科室"
},
}
class DimensionWeightService:
"""科室类型BSC维度权重配置服务"""
@staticmethod
async def get_by_dept_type(db: AsyncSession, dept_type: DeptType) -> Optional[DeptTypeDimensionWeight]:
"""根据科室类型获取权重配置"""
result = await db.execute(
select(DeptTypeDimensionWeight)
.where(DeptTypeDimensionWeight.dept_type == dept_type)
.where(DeptTypeDimensionWeight.is_active == True)
)
return result.scalar_one_or_none()
@staticmethod
async def get_all(db: AsyncSession, active_only: bool = True) -> List[DeptTypeDimensionWeight]:
"""获取所有权重配置"""
query = select(DeptTypeDimensionWeight)
if active_only:
query = query.where(DeptTypeDimensionWeight.is_active == True)
query = query.order_by(DeptTypeDimensionWeight.dept_type)
result = await db.execute(query)
return result.scalars().all()
@staticmethod
async def create_or_update(
db: AsyncSession,
dept_type: DeptType,
financial_weight: float,
customer_weight: float,
internal_process_weight: float,
learning_growth_weight: float,
description: Optional[str] = None
) -> DeptTypeDimensionWeight:
"""创建或更新权重配置"""
# 验证权重总和为1
total = financial_weight + customer_weight + internal_process_weight + learning_growth_weight
if abs(total - 1.0) > 0.01:
raise ValueError(f"权重总和必须为100%,当前总和为{total * 100}%")
# 查找现有配置
existing = await DimensionWeightService.get_by_dept_type(db, dept_type)
if existing:
# 更新现有配置
existing.financial_weight = financial_weight
existing.customer_weight = customer_weight
existing.internal_process_weight = internal_process_weight
existing.learning_growth_weight = learning_growth_weight
if description:
existing.description = description
await db.flush()
await db.refresh(existing)
return existing
else:
# 创建新配置
config = DeptTypeDimensionWeight(
dept_type=dept_type,
financial_weight=financial_weight,
customer_weight=customer_weight,
internal_process_weight=internal_process_weight,
learning_growth_weight=learning_growth_weight,
description=description
)
db.add(config)
await db.flush()
await db.refresh(config)
return config
@staticmethod
async def init_default_weights(db: AsyncSession) -> List[DeptTypeDimensionWeight]:
"""初始化默认权重配置(根据详细设计文档)"""
configs = []
for dept_type, weights in DEFAULT_WEIGHTS.items():
config = await DimensionWeightService.create_or_update(
db,
dept_type=dept_type,
financial_weight=weights["financial"],
customer_weight=weights["customer"],
internal_process_weight=weights["internal_process"],
learning_growth_weight=weights["learning_growth"],
description=weights.get("description")
)
configs.append(config)
return configs
@staticmethod
def get_dimension_weights(dept_type: DeptType) -> Dict[str, float]:
"""
获取科室类型的维度权重(用于计算)
优先使用数据库配置,如果没有则使用默认配置
"""
default = DEFAULT_WEIGHTS.get(dept_type, {
"financial": 0.40,
"customer": 0.25,
"internal_process": 0.30,
"learning_growth": 0.05,
})
return {
"financial": default["financial"],
"customer": default["customer"],
"internal_process": default["internal_process"],
"learning_growth": default["learning_growth"],
}
@staticmethod
async def calculate_dimension_weighted_score(
db: AsyncSession,
dept_type: DeptType,
financial_score: float,
customer_score: float,
internal_process_score: float,
learning_growth_score: float
) -> Dict[str, Any]:
"""
根据科室类型计算维度加权得分
Args:
db: 数据库会话
dept_type: 科室类型
financial_score: 财务维度得分
customer_score: 客户维度得分
internal_process_score: 内部流程维度得分
learning_growth_score: 学习成长维度得分
Returns:
包含各维度加权得分和总分的字典
"""
# 获取权重配置
config = await DimensionWeightService.get_by_dept_type(db, dept_type)
if config:
weights = {
"financial": float(config.financial_weight),
"customer": float(config.customer_weight),
"internal_process": float(config.internal_process_weight),
"learning_growth": float(config.learning_growth_weight),
}
else:
weights = DimensionWeightService.get_dimension_weights(dept_type)
# 计算各维度加权得分
weighted_scores = {
"financial": financial_score * weights["financial"],
"customer": customer_score * weights["customer"],
"internal_process": internal_process_score * weights["internal_process"],
"learning_growth": learning_growth_score * weights["learning_growth"],
}
# 计算总分
total_score = sum(weighted_scores.values())
return {
"weights": weights,
"weighted_scores": weighted_scores,
"total_score": round(total_score, 2),
"raw_scores": {
"financial": financial_score,
"customer": customer_score,
"internal_process": internal_process_score,
"learning_growth": learning_growth_score,
}
}
@staticmethod
async def delete(db: AsyncSession, config_id: int) -> bool:
"""删除权重配置(软删除)"""
result = await db.execute(
select(DeptTypeDimensionWeight).where(DeptTypeDimensionWeight.id == config_id)
)
config = result.scalar_one_or_none()
if not config:
return False
config.is_active = False
await db.flush()
return True