mirror of
				https://github.com/Akkudoktor-EOS/EOS.git
				synced 2025-10-30 22:36:21 +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) | ||
|  |         ) |