""" 科室类型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