mirror of
				https://github.com/Akkudoktor-EOS/EOS.git
				synced 2025-11-03 16:26:20 +00:00 
			
		
		
		
	
		
			
	
	
		
			236 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			236 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| 
								 | 
							
								from typing import Optional
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import pytest
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from akkudoktoreos.core.emplan import (
							 | 
						||
| 
								 | 
							
								    BaseInstruction,
							 | 
						||
| 
								 | 
							
								    CommodityQuantity,
							 | 
						||
| 
								 | 
							
								    DDBCInstruction,
							 | 
						||
| 
								 | 
							
								    EnergyManagementPlan,
							 | 
						||
| 
								 | 
							
								    FRBCInstruction,
							 | 
						||
| 
								 | 
							
								    OMBCInstruction,
							 | 
						||
| 
								 | 
							
								    PEBCInstruction,
							 | 
						||
| 
								 | 
							
								    PEBCPowerEnvelope,
							 | 
						||
| 
								 | 
							
								    PEBCPowerEnvelopeElement,
							 | 
						||
| 
								 | 
							
								    PPBCEndInterruptionInstruction,
							 | 
						||
| 
								 | 
							
								    PPBCScheduleInstruction,
							 | 
						||
| 
								 | 
							
								    PPBCStartInterruptionInstruction,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								from akkudoktoreos.utils.datetimeutil import Duration, to_datetime, to_duration
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@pytest.fixture
							 | 
						||
| 
								 | 
							
								def fixed_now():
							 | 
						||
| 
								 | 
							
								    return to_datetime("2025-06-01T12:00:00")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class TestEnergyManagementPlan:
							 | 
						||
| 
								 | 
							
								    def test_add_instruction_and_time_range(self, fixed_now):
							 | 
						||
| 
								 | 
							
								        plan = EnergyManagementPlan(
							 | 
						||
| 
								 | 
							
								            id="plan-123",
							 | 
						||
| 
								 | 
							
								            generated_at=fixed_now,
							 | 
						||
| 
								 | 
							
								            instructions=[]
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        instr1 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-1",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now,
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        instr2 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-2",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now.add(minutes=5),
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        plan.add_instruction(instr1)
							 | 
						||
| 
								 | 
							
								        plan.add_instruction(instr2)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # Check that valid_from matches the earliest execution_time
							 | 
						||
| 
								 | 
							
								        assert plan.valid_from == fixed_now
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        # instr2 has infinite duration so valid_until must be None
							 | 
						||
| 
								 | 
							
								        assert plan.valid_until is None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        assert plan.instructions == [instr1, instr2]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def test_clear(self, fixed_now):
							 | 
						||
| 
								 | 
							
								        plan = EnergyManagementPlan(
							 | 
						||
| 
								 | 
							
								            id="plan-123",
							 | 
						||
| 
								 | 
							
								            generated_at=fixed_now,
							 | 
						||
| 
								 | 
							
								            instructions=[
							 | 
						||
| 
								 | 
							
								                OMBCInstruction(
							 | 
						||
| 
								 | 
							
								                    resource_id="dev-1",
							 | 
						||
| 
								 | 
							
								                    execution_time=fixed_now,
							 | 
						||
| 
								 | 
							
								                    operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								                    operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								                )
							 | 
						||
| 
								 | 
							
								            ],
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        plan.clear()
							 | 
						||
| 
								 | 
							
								        assert plan.instructions == []
							 | 
						||
| 
								 | 
							
								        assert plan.valid_until is None
							 | 
						||
| 
								 | 
							
								        assert plan.valid_from is not None
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def test_get_active_instructions(self, fixed_now):
							 | 
						||
| 
								 | 
							
								        instr1 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-1",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now.subtract(minutes=1),
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        instr2 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-2",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now.add(minutes=1),
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        instr3 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-3",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now.subtract(minutes=10),
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        plan = EnergyManagementPlan(
							 | 
						||
| 
								 | 
							
								            id="plan-123",
							 | 
						||
| 
								 | 
							
								            generated_at=fixed_now,
							 | 
						||
| 
								 | 
							
								            instructions=[instr1, instr2, instr3],
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        plan._update_time_range()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        active = plan.get_active_instructions(now=fixed_now)
							 | 
						||
| 
								 | 
							
								        ids = {i.resource_id for i in active}
							 | 
						||
| 
								 | 
							
								        assert ids == {"dev-1", "dev-3"}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def test_get_next_instruction(self, fixed_now):
							 | 
						||
| 
								 | 
							
								        instr1 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-1",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now.subtract(minutes=1),
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        instr2 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-2",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now.add(minutes=10),
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        instr3 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-3",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now.add(minutes=5),
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        plan = EnergyManagementPlan(
							 | 
						||
| 
								 | 
							
								            id="plan-123",
							 | 
						||
| 
								 | 
							
								            generated_at=fixed_now,
							 | 
						||
| 
								 | 
							
								            instructions=[instr1, instr2, instr3],
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        plan._update_time_range()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        next_instr = plan.get_next_instruction(now=fixed_now)
							 | 
						||
| 
								 | 
							
								        assert next_instr is not None
							 | 
						||
| 
								 | 
							
								        assert next_instr.resource_id == "dev-3"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def test_get_instructions_for_resource(self, fixed_now):
							 | 
						||
| 
								 | 
							
								        instr1 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-1",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now,
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        instr2 = OMBCInstruction(
							 | 
						||
| 
								 | 
							
								            resource_id="dev-2",
							 | 
						||
| 
								 | 
							
								            execution_time=fixed_now,
							 | 
						||
| 
								 | 
							
								            operation_mode_id="mymode1",
							 | 
						||
| 
								 | 
							
								            operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        plan = EnergyManagementPlan(
							 | 
						||
| 
								 | 
							
								            id="plan-123",
							 | 
						||
| 
								 | 
							
								            generated_at=fixed_now,
							 | 
						||
| 
								 | 
							
								            instructions=[instr1, instr2],
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								        dev1_instructions = plan.get_instructions_for_resource("dev-1")
							 | 
						||
| 
								 | 
							
								        assert len(dev1_instructions) == 1
							 | 
						||
| 
								 | 
							
								        assert dev1_instructions[0].resource_id == "dev-1"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    def test_add_various_instructions(self, fixed_now):
							 | 
						||
| 
								 | 
							
								        plan = EnergyManagementPlan(
							 | 
						||
| 
								 | 
							
								            id="plan-123",
							 | 
						||
| 
								 | 
							
								            generated_at=fixed_now,
							 | 
						||
| 
								 | 
							
								            instructions=[]
							 | 
						||
| 
								 | 
							
								        )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        instrs = [
							 | 
						||
| 
								 | 
							
								            DDBCInstruction(
							 | 
						||
| 
								 | 
							
								                id="actuatorA@123",
							 | 
						||
| 
								 | 
							
								                execution_time=fixed_now,
							 | 
						||
| 
								 | 
							
								                actuator_id="actuatorA",
							 | 
						||
| 
								 | 
							
								                operation_mode_id="mode123",
							 | 
						||
| 
								 | 
							
								                operation_mode_factor=0.5,
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								            FRBCInstruction(
							 | 
						||
| 
								 | 
							
								                id="actuatorB@456",
							 | 
						||
| 
								 | 
							
								                execution_time=fixed_now.add(minutes=1),
							 | 
						||
| 
								 | 
							
								                actuator_id="actuatorB",
							 | 
						||
| 
								 | 
							
								                operation_mode_id="FRBC_Mode_1",
							 | 
						||
| 
								 | 
							
								                operation_mode_factor=1.0,
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								            OMBCInstruction(
							 | 
						||
| 
								 | 
							
								                id="controller@789",
							 | 
						||
| 
								 | 
							
								                execution_time=fixed_now.add(minutes=2),
							 | 
						||
| 
								 | 
							
								                operation_mode_id="OMBC_Mode_42",
							 | 
						||
| 
								 | 
							
								                operation_mode_factor=0.8,
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								            PPBCEndInterruptionInstruction(
							 | 
						||
| 
								 | 
							
								                id="end_int@001",
							 | 
						||
| 
								 | 
							
								                execution_time=fixed_now.add(minutes=3),
							 | 
						||
| 
								 | 
							
								                power_profile_id="profile-123",
							 | 
						||
| 
								 | 
							
								                sequence_container_id="container-456",
							 | 
						||
| 
								 | 
							
								                power_sequence_id="seq-789",
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								            PPBCStartInterruptionInstruction(
							 | 
						||
| 
								 | 
							
								                id="start_int@002",
							 | 
						||
| 
								 | 
							
								                execution_time=fixed_now.add(minutes=4),
							 | 
						||
| 
								 | 
							
								                power_profile_id="profile-321",
							 | 
						||
| 
								 | 
							
								                sequence_container_id="container-654",
							 | 
						||
| 
								 | 
							
								                power_sequence_id="seq-987",
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								            PPBCScheduleInstruction(
							 | 
						||
| 
								 | 
							
								                id="schedule@003",
							 | 
						||
| 
								 | 
							
								                execution_time=fixed_now.add(minutes=5),
							 | 
						||
| 
								 | 
							
								                power_profile_id="profile-999",
							 | 
						||
| 
								 | 
							
								                sequence_container_id="container-888",
							 | 
						||
| 
								 | 
							
								                power_sequence_id="seq-777",
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								            PEBCInstruction(
							 | 
						||
| 
								 | 
							
								                id="pebc@004",
							 | 
						||
| 
								 | 
							
								                execution_time=fixed_now.add(minutes=6),
							 | 
						||
| 
								 | 
							
								                power_constraints_id="pc-123",
							 | 
						||
| 
								 | 
							
								                power_envelopes=[
							 | 
						||
| 
								 | 
							
								                    PEBCPowerEnvelope(
							 | 
						||
| 
								 | 
							
								                        id="pebcpe@1234",
							 | 
						||
| 
								 | 
							
								                        commodity_quantity=CommodityQuantity.ELECTRIC_POWER_L1,
							 | 
						||
| 
								 | 
							
								                        power_envelope_elements = [
							 | 
						||
| 
								 | 
							
								                            PEBCPowerEnvelopeElement(
							 | 
						||
| 
								 | 
							
								                                duration = to_duration(10),
							 | 
						||
| 
								 | 
							
								                                upper_limit = 1010.0,
							 | 
						||
| 
								 | 
							
								                                lower_limit = 990.0,
							 | 
						||
| 
								 | 
							
								                            ),
							 | 
						||
| 
								 | 
							
								                        ],
							 | 
						||
| 
								 | 
							
								                    ),
							 | 
						||
| 
								 | 
							
								                ],
							 | 
						||
| 
								 | 
							
								            ),
							 | 
						||
| 
								 | 
							
								        ]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        for instr in instrs:
							 | 
						||
| 
								 | 
							
								            plan.add_instruction(instr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        assert len(plan.instructions) == len(instrs)
							 | 
						||
| 
								 | 
							
								        # Check that get_instructions_for_device returns the right instructions
							 | 
						||
| 
								 | 
							
								        assert any(
							 | 
						||
| 
								 | 
							
								            instr for instr in plan.get_instructions_for_resource("actuatorA")
							 | 
						||
| 
								 | 
							
								            if isinstance(instr, DDBCInstruction)
							 | 
						||
| 
								 | 
							
								        )
							 |