297 lines
11 KiB
Python
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
|