Files
hospital_performance/backend/tests/test_dimension_weight_service.py
2026-02-28 15:06:52 +08:00

297 lines
11 KiB
Python

"""
科室类型BSC维度权重配置服务测试用例
测试覆盖:
1. 权重配置获取
2. 权重配置创建/更新
3. 维度加权得分计算
4. 默认权重初始化
"""
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from decimal import Decimal
from app.services.dimension_weight_service import DimensionWeightService, DEFAULT_WEIGHTS
from app.models.models import DeptType, DeptTypeDimensionWeight
class TestGetDimensionWeights:
"""获取权重配置测试"""
def test_get_default_weights_surgical(self):
"""测试获取手术临床科室默认权重"""
weights = DimensionWeightService.get_dimension_weights(DeptType.CLINICAL_SURGICAL)
assert weights["financial"] == 0.60
assert weights["customer"] == 0.15
assert weights["internal_process"] == 0.20
assert weights["learning_growth"] == 0.05
def test_get_default_weights_medical_tech(self):
"""测试获取医技科室默认权重"""
weights = DimensionWeightService.get_dimension_weights(DeptType.MEDICAL_TECH)
assert weights["financial"] == 0.40
assert weights["customer"] == 0.25
assert weights["internal_process"] == 0.30
assert weights["learning_growth"] == 0.05
def test_get_default_weights_nursing(self):
"""测试获取护理单元默认权重"""
weights = DimensionWeightService.get_dimension_weights(DeptType.NURSING)
assert weights["financial"] == 0.20
assert weights["customer"] == 0.15
assert weights["internal_process"] == 0.50
assert weights["learning_growth"] == 0.15
def test_get_default_weights_unknown_type(self):
"""测试获取未知类型的默认权重"""
weights = DimensionWeightService.get_dimension_weights("unknown_type")
# 应返回通用默认值
assert "financial" in weights
assert "customer" in weights
@pytest.mark.asyncio
async def test_get_by_dept_type(self):
"""测试根据科室类型获取数据库配置"""
mock_db = AsyncMock()
# 创建模拟配置
config = MagicMock()
config.dept_type = DeptType.CLINICAL_SURGICAL
config.financial_weight = 0.60
config.customer_weight = 0.15
config.internal_process_weight = 0.20
config.learning_growth_weight = 0.05
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = config
with patch('app.services.dimension_weight_service.select') as mock_select:
mock_db.execute.return_value = mock_result
result = await DimensionWeightService.get_by_dept_type(
mock_db, DeptType.CLINICAL_SURGICAL
)
assert result is not None
class TestCreateUpdateWeights:
"""创建/更新权重配置测试"""
@pytest.mark.asyncio
async def test_create_weight_config(self):
"""测试创建权重配置"""
mock_db = AsyncMock()
# 模拟不存在现有配置
with patch.object(DimensionWeightService, 'get_by_dept_type', return_value=None):
result = await DimensionWeightService.create_or_update(
mock_db,
dept_type=DeptType.CLINICAL_SURGICAL,
financial_weight=0.60,
customer_weight=0.15,
internal_process_weight=0.20,
learning_growth_weight=0.05,
description="手术临床科室"
)
mock_db.add.assert_called_once()
@pytest.mark.asyncio
async def test_update_weight_config(self):
"""测试更新权重配置"""
mock_db = AsyncMock()
# 创建模拟现有配置
existing = MagicMock()
existing.dept_type = DeptType.CLINICAL_SURGICAL
existing.financial_weight = 0.50
existing.customer_weight = 0.20
with patch.object(DimensionWeightService, 'get_by_dept_type', return_value=existing):
result = await DimensionWeightService.create_or_update(
mock_db,
dept_type=DeptType.CLINICAL_SURGICAL,
financial_weight=0.60,
customer_weight=0.15,
internal_process_weight=0.20,
learning_growth_weight=0.05
)
assert existing.financial_weight == 0.60
assert existing.customer_weight == 0.15
@pytest.mark.asyncio
async def test_create_weight_config_invalid_total(self):
"""测试创建权重配置总和不为100%"""
mock_db = AsyncMock()
with patch.object(DimensionWeightService, 'get_by_dept_type', return_value=None):
with pytest.raises(ValueError, match="权重总和必须为100%"):
await DimensionWeightService.create_or_update(
mock_db,
dept_type=DeptType.CLINICAL_SURGICAL,
financial_weight=0.50, # 50%
customer_weight=0.30, # 30%
internal_process_weight=0.10, # 10%
learning_growth_weight=0.05 # 5%
# 总计: 95% != 100%
)
class TestCalculateWeightedScore:
"""维度加权得分计算测试"""
@pytest.mark.asyncio
async def test_calculate_dimension_weighted_score_surgical(self):
"""测试手术临床科室维度加权得分计算"""
mock_db = AsyncMock()
# 模拟数据库返回权重配置
config = MagicMock()
config.financial_weight = Decimal("0.60")
config.customer_weight = Decimal("0.15")
config.internal_process_weight = Decimal("0.20")
config.learning_growth_weight = Decimal("0.05")
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = config
with patch.object(DimensionWeightService, 'get_by_dept_type', return_value=config):
result = await DimensionWeightService.calculate_dimension_weighted_score(
mock_db,
dept_type=DeptType.CLINICAL_SURGICAL,
financial_score=90,
customer_score=85,
internal_process_score=88,
learning_growth_score=92
)
# 验证结果
assert "weights" in result
assert "weighted_scores" in result
assert "total_score" in result
# 计算总分
# 财务: 90 * 0.60 = 54
# 客户: 85 * 0.15 = 12.75
# 流程: 88 * 0.20 = 17.6
# 学习: 92 * 0.05 = 4.6
# 总分: 54 + 12.75 + 17.6 + 4.6 = 88.95
assert result["total_score"] == 88.95
@pytest.mark.asyncio
async def test_calculate_dimension_weighted_score_nursing(self):
"""测试护理单元维度加权得分计算"""
mock_db = AsyncMock()
config = MagicMock()
config.financial_weight = Decimal("0.20")
config.customer_weight = Decimal("0.15")
config.internal_process_weight = Decimal("0.50")
config.learning_growth_weight = Decimal("0.15")
with patch.object(DimensionWeightService, 'get_by_dept_type', return_value=config):
result = await DimensionWeightService.calculate_dimension_weighted_score(
mock_db,
dept_type=DeptType.NURSING,
financial_score=80,
customer_score=90,
internal_process_score=85,
learning_growth_score=88
)
# 护理单元的内部流程权重最高(50%)
assert result["weights"]["internal_process"] == 0.50
assert result["weighted_scores"]["internal_process"] == 42.5 # 85 * 0.50
class TestInitDefaultWeights:
"""默认权重初始化测试"""
def test_default_weights_completeness(self):
"""测试默认权重覆盖所有科室类型"""
expected_types = [
DeptType.CLINICAL_SURGICAL,
DeptType.CLINICAL_NONSURGICAL_WARD,
DeptType.CLINICAL_NONSURGICAL_NOWARD,
DeptType.MEDICAL_TECH,
DeptType.MEDICAL_AUXILIARY,
DeptType.NURSING,
DeptType.ADMIN,
DeptType.FINANCE,
DeptType.LOGISTICS,
]
for dept_type in expected_types:
assert dept_type in DEFAULT_WEIGHTS, f"缺少科室类型 {dept_type} 的默认权重"
def test_default_weights_sum_to_one(self):
"""测试所有默认权重总和为1"""
for dept_type, weights in DEFAULT_WEIGHTS.items():
total = (
weights["financial"] +
weights["customer"] +
weights["internal_process"] +
weights["learning_growth"]
)
assert abs(total - 1.0) < 0.01, f"{dept_type} 权重总和为 {total},不为 1.0"
@pytest.mark.asyncio
async def test_init_default_weights(self):
"""测试初始化默认权重"""
mock_db = AsyncMock()
# 模拟不存在现有配置
with patch.object(DimensionWeightService, 'get_by_dept_type', return_value=None):
with patch.object(DimensionWeightService, 'create_or_update') as mock_create:
mock_create.return_value = MagicMock()
result = await DimensionWeightService.init_default_weights(mock_db)
# 应该为每个科室类型创建配置
assert len(result) == len(DEFAULT_WEIGHTS)
class TestDeleteWeight:
"""删除权重配置测试"""
@pytest.mark.asyncio
async def test_delete_weight_config(self):
"""测试删除权重配置(软删除)"""
mock_db = AsyncMock()
config = MagicMock()
config.id = 1
config.is_active = True
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = config
with patch('app.services.dimension_weight_service.select') as mock_select:
mock_db.execute.return_value = mock_result
result = await DimensionWeightService.delete(mock_db, 1)
assert result is True
assert config.is_active is False
@pytest.mark.asyncio
async def test_delete_nonexistent_config(self):
"""测试删除不存在的配置"""
mock_db = AsyncMock()
mock_result = MagicMock()
mock_result.scalar_one_or_none.return_value = None
with patch('app.services.dimension_weight_service.select') as mock_select:
mock_db.execute.return_value = mock_result
result = await DimensionWeightService.delete(mock_db, 999)
assert result is False