From 4c2ec31cf3950cef9195c9e497f9d1ca33306527 Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Fri, 17 Apr 2026 16:58:37 +0000 Subject: [PATCH 1/7] Add clear-area prerequisite to conflict_higher_priority --- .../conflict_higher_priority.md | 6 ++++ .../conflict_higher_priority.py | 30 +++++++++++++++++-- .../uss_qualifier/suites/astm/utm/f3548_21.md | 2 +- .../suites/faa/uft/message_signing.md | 2 +- .../suites/uspace/flight_auth.md | 2 +- .../suites/uspace/required_services.md | 2 +- 6 files changed, 37 insertions(+), 7 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.md b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.md index a1bc95fa40..9056eaf9fc 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.md @@ -88,6 +88,12 @@ FlightPlannerResource that will manage conflicting Flight 2. ### dss DSSInstanceResource that provides access to a DSS instance where flight creation/sharing can be verified. +## Prerequisites check test case + +### [Verify area is clear test step](../../clear_area_validation.md) + +While this scenario assumes that the area used is already clear of any pre-existing flights (using, for instance, PrepareFlightPlanners scenario) in order to avoid a large number of area-clearing operations, the scenario will not proceed correctly if the area was left in a dirty state following a previous scenario that was supposed to leave the area clear. This test step verifies that the area is clear. + ## Attempt to plan flight in conflict test case ![Test case summary illustration](assets/attempt_to_plan_flight_into_conflict.svg) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py index 9ad274b87c..b82ffeef3e 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py @@ -21,13 +21,20 @@ from monitoring.uss_qualifier.resources.astm.f3548.v21 import DSSInstanceResource from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance from monitoring.uss_qualifier.resources.flight_planning import FlightIntentsResource +from monitoring.uss_qualifier.resources.flight_planning.flight_intent import ( + FlightIntentID, +) from monitoring.uss_qualifier.resources.flight_planning.flight_intent_validation import ( ExpectedFlightIntent, + estimate_scenario_execution_max_extents, validate_flight_intent_templates, ) from monitoring.uss_qualifier.resources.flight_planning.flight_planners import ( FlightPlannerResource, ) +from monitoring.uss_qualifier.scenarios.astm.utm.clear_area_validation import ( + validate_clear_area, +) from monitoring.uss_qualifier.scenarios.astm.utm.notifications_to_operator.notification_checker import ( NotificationChecker, ) @@ -65,6 +72,7 @@ class ConflictHigherPriority(TestScenario, NotificationChecker): tested_uss: FlightPlannerClient control_uss: FlightPlannerClient dss: DSSInstance + flight_intents_templates: dict[FlightIntentID, FlightInfoTemplate] def __init__( self, @@ -149,16 +157,18 @@ def __init__( ), ] - templates = flight_intents.get_flight_intents() + self.flight_intents_templates = flight_intents.get_flight_intents() try: - validate_flight_intent_templates(templates, expected_flight_intents) + validate_flight_intent_templates( + self.flight_intents_templates, expected_flight_intents + ) except ValueError as e: raise ValueError( f"`{self.me()}` TestScenario requirements for flight_intents not met: {e}" ) for efi in expected_flight_intents: - setattr(self, efi.intent_id, templates[efi.intent_id]) + setattr(self, efi.intent_id, self.flight_intents_templates[efi.intent_id]) def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: return flight_template.resolve(self.time_context.evaluate_now()) @@ -175,6 +185,20 @@ def run(self, context: ExecutionContext): f"{self.control_uss.participant_id}", ) + self.begin_test_case("Prerequisites check") + self.begin_test_step("Verify area is clear") + estimated_max_extents = estimate_scenario_execution_max_extents( + self.time_context, self.flight_intents_templates + ) + validate_clear_area( + self, + self.dss, + [estimated_max_extents], + ignore_self=False, + ) + self.end_test_step() + self.end_test_case() + self.begin_test_case("Attempt to plan flight in conflict") self._attempt_plan_flight_conflict() self.end_test_case() diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md index 51f7c5f4a2..59d9157a77 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md @@ -40,7 +40,7 @@ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM F3548-21 UTM DSS Operational Intent Reference State Transitions
ASTM SCD DSS: Implicit Subscription handling
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Operational Intent Explicit Subscription handling
ASTM SCD DSS: Operational Intent Reference Key Validation
ASTM SCD DSS: Operational Intent Reference Simple
ASTM SCD DSS: Operational Intent Reference Synchronization
ASTM SCD DSS: Subscription and entity deletion interaction
ASTM SCD DSS: Subscription and entity interaction
Nominal planning: not permitted conflict with equal priority
OVN Request Optional Extension to ASTM F3548-21
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM F3548-21 UTM DSS Operational Intent Reference State Transitions
ASTM SCD DSS: Implicit Subscription handling
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Operational Intent Explicit Subscription handling
ASTM SCD DSS: Operational Intent Reference Key Validation
ASTM SCD DSS: Operational Intent Reference Simple
ASTM SCD DSS: Operational Intent Reference Synchronization
ASTM SCD DSS: Subscription and entity deletion interaction
ASTM SCD DSS: Subscription and entity interaction
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
OVN Request Optional Extension to ASTM F3548-21
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0005,2 diff --git a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md index 13544a524f..2b21c90343 100644 --- a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md +++ b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md @@ -21,7 +21,7 @@ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM F3548-21 UTM DSS Operational Intent Reference State Transitions
ASTM SCD DSS: Implicit Subscription handling
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Operational Intent Explicit Subscription handling
ASTM SCD DSS: Operational Intent Reference Key Validation
ASTM SCD DSS: Operational Intent Reference Simple
ASTM SCD DSS: Operational Intent Reference Synchronization
ASTM SCD DSS: Subscription and entity deletion interaction
ASTM SCD DSS: Subscription and entity interaction
Nominal planning: not permitted conflict with equal priority
OVN Request Optional Extension to ASTM F3548-21
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM F3548-21 UTM DSS Operational Intent Reference State Transitions
ASTM SCD DSS: Implicit Subscription handling
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Operational Intent Explicit Subscription handling
ASTM SCD DSS: Operational Intent Reference Key Validation
ASTM SCD DSS: Operational Intent Reference Simple
ASTM SCD DSS: Operational Intent Reference Synchronization
ASTM SCD DSS: Subscription and entity deletion interaction
ASTM SCD DSS: Subscription and entity interaction
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
OVN Request Optional Extension to ASTM F3548-21
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0005,2 diff --git a/monitoring/uss_qualifier/suites/uspace/flight_auth.md b/monitoring/uss_qualifier/suites/uspace/flight_auth.md index 4e548a69c3..2551a2665a 100644 --- a/monitoring/uss_qualifier/suites/uspace/flight_auth.md +++ b/monitoring/uss_qualifier/suites/uspace/flight_auth.md @@ -22,7 +22,7 @@ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM F3548-21 UTM DSS Operational Intent Reference State Transitions
ASTM SCD DSS: Implicit Subscription handling
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Operational Intent Explicit Subscription handling
ASTM SCD DSS: Operational Intent Reference Key Validation
ASTM SCD DSS: Operational Intent Reference Simple
ASTM SCD DSS: Operational Intent Reference Synchronization
ASTM SCD DSS: Subscription and entity deletion interaction
ASTM SCD DSS: Subscription and entity interaction
Nominal planning: not permitted conflict with equal priority
OVN Request Optional Extension to ASTM F3548-21
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM F3548-21 UTM DSS Operational Intent Reference State Transitions
ASTM SCD DSS: Implicit Subscription handling
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Operational Intent Explicit Subscription handling
ASTM SCD DSS: Operational Intent Reference Key Validation
ASTM SCD DSS: Operational Intent Reference Simple
ASTM SCD DSS: Operational Intent Reference Synchronization
ASTM SCD DSS: Subscription and entity deletion interaction
ASTM SCD DSS: Subscription and entity interaction
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
OVN Request Optional Extension to ASTM F3548-21
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0005,2 diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index 2f6746a776..463a5c4d09 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -632,7 +632,7 @@ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM F3548-21 UTM DSS Operational Intent Reference State Transitions
ASTM SCD DSS: Implicit Subscription handling
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Operational Intent Explicit Subscription handling
ASTM SCD DSS: Operational Intent Reference Key Validation
ASTM SCD DSS: Operational Intent Reference Simple
ASTM SCD DSS: Operational Intent Reference Synchronization
ASTM SCD DSS: Subscription and entity deletion interaction
ASTM SCD DSS: Subscription and entity interaction
Nominal planning: not permitted conflict with equal priority
OVN Request Optional Extension to ASTM F3548-21
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM F3548-21 UTM DSS Operational Intent Reference State Transitions
ASTM SCD DSS: Implicit Subscription handling
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Operational Intent Explicit Subscription handling
ASTM SCD DSS: Operational Intent Reference Key Validation
ASTM SCD DSS: Operational Intent Reference Simple
ASTM SCD DSS: Operational Intent Reference Synchronization
ASTM SCD DSS: Subscription and entity deletion interaction
ASTM SCD DSS: Subscription and entity interaction
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
OVN Request Optional Extension to ASTM F3548-21
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0005,2 From d45a00a912f236eccb86ef9174f479b436d19462 Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Fri, 17 Apr 2026 17:45:21 +0000 Subject: [PATCH 2/7] Factor conflict scenario commonalities into PlanningSequenceScenario --- .basedpyright/baseline.json | 24 ---- .../conflict_equal_priority_not_permitted.py | 94 ++----------- .../conflict_higher_priority.py | 91 ++----------- .../planning_sequence_scenario.py | 128 ++++++++++++++++++ 4 files changed, 156 insertions(+), 181 deletions(-) create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index f6498cd062..c7f8f83347 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -18377,22 +18377,6 @@ } ], "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 15, - "endColumn": 28, - "lineCount": 1 - } - }, - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 23, - "endColumn": 35, - "lineCount": 1 - } - }, { "code": "reportArgumentType", "range": { @@ -18549,14 +18533,6 @@ } ], "./monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py": [ - { - "code": "reportAttributeAccessIssue", - "range": { - "startColumn": 23, - "endColumn": 35, - "lineCount": 1 - } - }, { "code": "reportArgumentType", "range": { diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py index 1a9c7c8604..73891c85aa 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py @@ -3,12 +3,8 @@ Scope, ) -from monitoring.monitorlib.clients.flight_planning.client import ( - FlightPlannerClient, -) from monitoring.monitorlib.clients.flight_planning.flight_info import ( AirspaceUsageState, - FlightInfo, UasState, ) from monitoring.monitorlib.clients.flight_planning.flight_info_template import ( @@ -18,22 +14,16 @@ FlightPlanStatus, PlanningActivityResult, ) -from monitoring.uss_qualifier.resources.astm.f3548.v21 import DSSInstanceResource -from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstanceResource from monitoring.uss_qualifier.resources.flight_planning import FlightIntentsResource -from monitoring.uss_qualifier.resources.flight_planning.flight_intent import ( - FlightIntentID, -) from monitoring.uss_qualifier.resources.flight_planning.flight_intent_validation import ( ExpectedFlightIntent, - estimate_scenario_execution_max_extents, - validate_flight_intent_templates, ) from monitoring.uss_qualifier.resources.flight_planning.flight_planners import ( FlightPlannerResource, ) -from monitoring.uss_qualifier.scenarios.astm.utm.clear_area_validation import ( - validate_clear_area, +from monitoring.uss_qualifier.scenarios.astm.utm.nominal_planning.planning_sequence_scenario import ( + PlanningSequenceScenario, ) from monitoring.uss_qualifier.scenarios.astm.utm.test_steps import OpIntentValidator from monitoring.uss_qualifier.scenarios.flight_planning.prioritization_test_steps import ( @@ -44,19 +34,17 @@ ) from monitoring.uss_qualifier.scenarios.flight_planning.test_steps import ( activate_flight, - cleanup_flights, delete_flight, plan_flight, submit_flight, ) from monitoring.uss_qualifier.scenarios.scenario import ( ScenarioCannotContinueError, - TestScenario, ) from monitoring.uss_qualifier.suites.suite import ExecutionContext -class ConflictEqualPriorityNotPermitted(TestScenario): +class ConflictEqualPriorityNotPermitted(PlanningSequenceScenario): flight1_id: str | None = None flight1_planned: FlightInfoTemplate flight1_activated: FlightInfoTemplate @@ -70,22 +58,13 @@ class ConflictEqualPriorityNotPermitted(TestScenario): flight2_activated: FlightInfoTemplate flight2_nonconforming: FlightInfoTemplate - tested_uss: FlightPlannerClient - control_uss: FlightPlannerClient - dss: DSSInstance - flight_intents_templates: dict[FlightIntentID, FlightInfoTemplate] - def __init__( self, tested_uss: FlightPlannerResource, control_uss: FlightPlannerResource, dss: DSSInstanceResource, - flight_intents: FlightIntentsResource | None = None, + flight_intents: FlightIntentsResource, ): - super().__init__() - self.tested_uss = tested_uss.client - self.control_uss = control_uss.client - scopes = { Scope.StrategicCoordination: "search for operational intent references to verify outcomes of planning activities and retrieve operational intent details" } @@ -94,8 +73,6 @@ def __init__( "query for telemetry for off-nominal operational intents" ) - self.dss = dss.get_instance(scopes) - expected_flight_intents = [ ExpectedFlightIntent( "flight1_planned", @@ -164,54 +141,16 @@ def __init__( ), # Note: this intent expected to produce Nonconforming state, but this is hard to verify without telemetry. UAS state is not actually off-nominal. ] - self.flight_intents_templates = ( - flight_intents.get_flight_intents() if flight_intents else {} - ) - try: - validate_flight_intent_templates( - self.flight_intents_templates, expected_flight_intents - ) - except ValueError as e: - raise ValueError( - f"`{self.me()}` TestScenario requirements for flight_intents not met: {e}" - ) - - for efi in expected_flight_intents: - setattr( - self, - efi.intent_id.replace("equal_prio_", ""), - self.flight_intents_templates[efi.intent_id], - ) - - def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: - return flight_template.resolve(self.time_context.evaluate_now()) - - def run(self, context: ExecutionContext): - self.begin_test_scenario(context) - - self.record_note( - "Tested USS", - f"{self.tested_uss.participant_id}", - ) - self.record_note( - "Control USS", - f"{self.control_uss.participant_id}", - ) - - self.begin_test_case("Prerequisites check") - self.begin_test_step("Verify area is clear") - estimated_max_extents = estimate_scenario_execution_max_extents( - self.time_context, self.flight_intents_templates - ) - validate_clear_area( - self, - self.dss, - [estimated_max_extents], - ignore_self=False, + super().__init__( + flight_intents=flight_intents, + expected_flight_intents=expected_flight_intents, + tested_uss=tested_uss, + control_uss=control_uss, + dss=dss, + scopes=scopes, ) - self.end_test_step() - self.end_test_case() + def run_planning_sequence(self, context: ExecutionContext): self.begin_test_case("Attempt to plan flight into conflict") self._attempt_plan_flight_conflict() self.end_test_case() @@ -234,8 +173,6 @@ def run(self, context: ExecutionContext): self._modify_activated_flight_preexisting_conflict(flight_1_oi_ref) self.end_test_case() - self.end_test_scenario() - def _attempt_plan_flight_conflict(self): self.begin_test_step("Plan Flight 2") flight2_planned = self.resolve_flight(self.flight2_planned) @@ -536,8 +473,3 @@ def _modify_activated_flight_preexisting_conflict( }: validator.expect_not_shared() self.end_test_step() - - def cleanup(self): - self.begin_cleanup() - cleanup_flights(self, (self.control_uss, self.tested_uss)) - self.end_cleanup() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py index b82ffeef3e..229c97cddb 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_higher_priority/conflict_higher_priority.py @@ -4,9 +4,6 @@ Scope, ) -from monitoring.monitorlib.clients.flight_planning.client import ( - FlightPlannerClient, -) from monitoring.monitorlib.clients.flight_planning.flight_info import ( AirspaceUsageState, FlightInfo, @@ -18,22 +15,16 @@ from monitoring.monitorlib.clients.flight_planning.planning import ( PlanningActivityResult, ) -from monitoring.uss_qualifier.resources.astm.f3548.v21 import DSSInstanceResource -from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstanceResource from monitoring.uss_qualifier.resources.flight_planning import FlightIntentsResource -from monitoring.uss_qualifier.resources.flight_planning.flight_intent import ( - FlightIntentID, -) from monitoring.uss_qualifier.resources.flight_planning.flight_intent_validation import ( ExpectedFlightIntent, - estimate_scenario_execution_max_extents, - validate_flight_intent_templates, ) from monitoring.uss_qualifier.resources.flight_planning.flight_planners import ( FlightPlannerResource, ) -from monitoring.uss_qualifier.scenarios.astm.utm.clear_area_validation import ( - validate_clear_area, +from monitoring.uss_qualifier.scenarios.astm.utm.nominal_planning.planning_sequence_scenario import ( + PlanningSequenceScenario, ) from monitoring.uss_qualifier.scenarios.astm.utm.notifications_to_operator.notification_checker import ( NotificationChecker, @@ -47,16 +38,14 @@ ) from monitoring.uss_qualifier.scenarios.flight_planning.test_steps import ( activate_flight, - cleanup_flights, delete_flight, modify_activated_flight, plan_flight, ) -from monitoring.uss_qualifier.scenarios.scenario import TestScenario from monitoring.uss_qualifier.suites.suite import ExecutionContext -class ConflictHigherPriority(TestScenario, NotificationChecker): +class ConflictHigherPriority(PlanningSequenceScenario, NotificationChecker): flight1_id: str | None = None flight1_planned: FlightInfoTemplate flight1m_planned: FlightInfoTemplate @@ -69,11 +58,6 @@ class ConflictHigherPriority(TestScenario, NotificationChecker): flight2_activated: FlightInfoTemplate flight2m_activated: FlightInfoTemplate - tested_uss: FlightPlannerClient - control_uss: FlightPlannerClient - dss: DSSInstance - flight_intents_templates: dict[FlightIntentID, FlightInfoTemplate] - def __init__( self, flight_intents: FlightIntentsResource, @@ -81,15 +65,9 @@ def __init__( control_uss: FlightPlannerResource, dss: DSSInstanceResource, ): - super().__init__() - self.tested_uss = tested_uss.client - self.control_uss = control_uss.client - self.dss = dss.get_instance( - { - Scope.StrategicCoordination: "search for operational intent references to verify outcomes of planning activities and retrieve operational intent details" - } - ) - + scopes = { + Scope.StrategicCoordination: "search for operational intent references to verify outcomes of planning activities and retrieve operational intent details" + } expected_flight_intents = [ ExpectedFlightIntent( "flight1_planned", @@ -157,48 +135,16 @@ def __init__( ), ] - self.flight_intents_templates = flight_intents.get_flight_intents() - try: - validate_flight_intent_templates( - self.flight_intents_templates, expected_flight_intents - ) - except ValueError as e: - raise ValueError( - f"`{self.me()}` TestScenario requirements for flight_intents not met: {e}" - ) - - for efi in expected_flight_intents: - setattr(self, efi.intent_id, self.flight_intents_templates[efi.intent_id]) - - def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: - return flight_template.resolve(self.time_context.evaluate_now()) - - def run(self, context: ExecutionContext): - self.begin_test_scenario(context) - - self.record_note( - "Tested USS", - f"{self.tested_uss.participant_id}", + super().__init__( + flight_intents=flight_intents, + expected_flight_intents=expected_flight_intents, + tested_uss=tested_uss, + control_uss=control_uss, + dss=dss, + scopes=scopes, ) - self.record_note( - "Control USS", - f"{self.control_uss.participant_id}", - ) - - self.begin_test_case("Prerequisites check") - self.begin_test_step("Verify area is clear") - estimated_max_extents = estimate_scenario_execution_max_extents( - self.time_context, self.flight_intents_templates - ) - validate_clear_area( - self, - self.dss, - [estimated_max_extents], - ignore_self=False, - ) - self.end_test_step() - self.end_test_case() + def run_planning_sequence(self, context: ExecutionContext): self.begin_test_case("Attempt to plan flight in conflict") self._attempt_plan_flight_conflict() self.end_test_case() @@ -230,8 +176,6 @@ def run(self, context: ExecutionContext): ) self.end_test_case() - self.end_test_scenario() - def _attempt_plan_flight_conflict(self): self.begin_test_step("Plan Flight 2") flight2_planned = self.resolve_flight(self.flight2_planned) @@ -554,8 +498,3 @@ def _attempt_modify_activated_flight_conflict( ) validator.expect_not_shared() self.end_test_step() - - def cleanup(self): - self.begin_cleanup() - cleanup_flights(self, (self.control_uss, self.tested_uss)) - self.end_cleanup() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py new file mode 100644 index 0000000000..a725caf0aa --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py @@ -0,0 +1,128 @@ +from abc import ABC + +from uas_standards.astm.f3548.v21.constants import Scope + +from monitoring.monitorlib.clients.flight_planning.client import FlightPlannerClient +from monitoring.monitorlib.clients.flight_planning.flight_info import FlightInfo +from monitoring.monitorlib.clients.flight_planning.flight_info_template import ( + FlightInfoTemplate, +) +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import ( + DSSInstance, + DSSInstanceResource, +) +from monitoring.uss_qualifier.resources.flight_planning import ( + FlightIntentsResource, + FlightPlannerResource, +) +from monitoring.uss_qualifier.resources.flight_planning.flight_intent import ( + FlightIntentID, +) +from monitoring.uss_qualifier.resources.flight_planning.flight_intent_validation import ( + ExpectedFlightIntent, + estimate_scenario_execution_max_extents, + validate_flight_intent_templates, +) +from monitoring.uss_qualifier.scenarios.astm.utm.clear_area_validation import ( + validate_clear_area, +) +from monitoring.uss_qualifier.scenarios.flight_planning.test_steps import ( + cleanup_flights, +) +from monitoring.uss_qualifier.scenarios.scenario import TestScenario +from monitoring.uss_qualifier.suites.suite import ExecutionContext + + +class PlanningSequenceScenario(TestScenario, ABC): + tested_uss: FlightPlannerClient + control_uss: FlightPlannerClient + dss: DSSInstance + flight_intents_templates: dict[FlightIntentID, FlightInfoTemplate] + + def __init__( + self, + tested_uss: FlightPlannerResource, + control_uss: FlightPlannerResource, + dss: DSSInstanceResource, + flight_intents: FlightIntentsResource, + expected_flight_intents: list[ExpectedFlightIntent], + scopes: dict[Scope, str], + ): + super().__init__() + self.tested_uss = tested_uss.client + self.control_uss = control_uss.client + self.dss = dss.get_instance({str(k): v for k, v in scopes.items()}) + + self.flight_intents_templates = flight_intents.get_flight_intents() + try: + validate_flight_intent_templates( + self.flight_intents_templates, expected_flight_intents + ) + except ValueError as e: + raise ValueError( + f"`{self.me()}` TestScenario requirements for flight_intents not met: {e}" + ) + + for efi in expected_flight_intents: + setattr(self, efi.intent_id, self.flight_intents_templates[efi.intent_id]) + + self.flight_intents_templates = ( + flight_intents.get_flight_intents() if flight_intents else {} + ) + try: + validate_flight_intent_templates( + self.flight_intents_templates, expected_flight_intents + ) + except ValueError as e: + raise ValueError( + f"`{self.me()}` TestScenario requirements for flight_intents not met: {e}" + ) + + for efi in expected_flight_intents: + setattr( + self, + efi.intent_id.replace("equal_prio_", ""), + self.flight_intents_templates[efi.intent_id], + ) + + def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: + return flight_template.resolve(self.time_context.evaluate_now()) + + def run_planning_sequence(self, context: ExecutionContext): + """Run the main planning sequence of the test scenario assuming the test scenario has already begun.""" + raise NotImplementedError() + + def run(self, context: ExecutionContext): + self.begin_test_scenario(context) + + self.record_note( + "Tested USS", + f"{self.tested_uss.participant_id}", + ) + self.record_note( + "Control USS", + f"{self.control_uss.participant_id}", + ) + + self.begin_test_case("Prerequisites check") + self.begin_test_step("Verify area is clear") + estimated_max_extents = estimate_scenario_execution_max_extents( + self.time_context, self.flight_intents_templates + ) + validate_clear_area( + self, + self.dss, + [estimated_max_extents], + ignore_self=False, + ) + self.end_test_step() + self.end_test_case() + + self.run_planning_sequence(context) + + self.end_test_scenario() + + def cleanup(self): + self.begin_cleanup() + cleanup_flights(self, (self.control_uss, self.tested_uss)) + self.end_cleanup() From 327515b3e69de1829299374ce70cad4a54d51880 Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Fri, 17 Apr 2026 18:36:44 +0000 Subject: [PATCH 3/7] Fix enum cast --- .../astm/utm/nominal_planning/planning_sequence_scenario.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py index a725caf0aa..df66e04ad8 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py @@ -51,7 +51,7 @@ def __init__( super().__init__() self.tested_uss = tested_uss.client self.control_uss = control_uss.client - self.dss = dss.get_instance({str(k): v for k, v in scopes.items()}) + self.dss = dss.get_instance({k.value: v for k, v in scopes.items()}) self.flight_intents_templates = flight_intents.get_flight_intents() try: From c3d122d36d7c3a8fb9d11aeff2ba51253b2b9833 Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Fri, 17 Apr 2026 18:52:13 +0000 Subject: [PATCH 4/7] Do not check abstract test scenarios for documentation --- .../astm/utm/nominal_planning/planning_sequence_scenario.py | 5 +++-- monitoring/uss_qualifier/scenarios/scenario.py | 6 +++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py index df66e04ad8..34423c734c 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py @@ -1,4 +1,4 @@ -from abc import ABC +from abc import ABC, abstractmethod from uas_standards.astm.f3548.v21.constants import Scope @@ -88,8 +88,9 @@ def __init__( def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: return flight_template.resolve(self.time_context.evaluate_now()) + @abstractmethod def run_planning_sequence(self, context: ExecutionContext): - """Run the main planning sequence of the test scenario assuming the test scenario has already begun.""" + """Run the main planning sequence of the test scenario, assuming the test scenario has already begun.""" raise NotImplementedError() def run(self, context: ExecutionContext): diff --git a/monitoring/uss_qualifier/scenarios/scenario.py b/monitoring/uss_qualifier/scenarios/scenario.py index 8ffdf0758d..b0ba71d633 100644 --- a/monitoring/uss_qualifier/scenarios/scenario.py +++ b/monitoring/uss_qualifier/scenarios/scenario.py @@ -764,7 +764,11 @@ def find_test_scenarios( for descendant in descendants: if descendant not in test_scenarios: test_scenarios.add(descendant) - elif inspect.isclass(member) and member is not TestScenario: + elif ( + inspect.isclass(member) + and member is not TestScenario + and not inspect.isabstract(member) + ): if issubclass(member, TestScenario): if member not in test_scenarios: test_scenarios.add(member) From 3210b8a3ec25adc48b446b449d8e8c9340042c51 Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Fri, 17 Apr 2026 18:59:39 +0000 Subject: [PATCH 5/7] Fix previously-abstract class in unit tests --- monitoring/uss_qualifier/scenarios/scenario_test/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monitoring/uss_qualifier/scenarios/scenario_test/utils.py b/monitoring/uss_qualifier/scenarios/scenario_test/utils.py index e2babe4d9a..b7bf82d1b6 100644 --- a/monitoring/uss_qualifier/scenarios/scenario_test/utils.py +++ b/monitoring/uss_qualifier/scenarios/scenario_test/utils.py @@ -65,7 +65,8 @@ def run(self, context): pass class TestScenarioB(_TestScenario): - pass + def run(self, context): + pass class NotATestScenarioC: pass From 2b471c47d6d875aec0f7219a96a2d86785746e45 Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Fri, 17 Apr 2026 19:15:07 +0000 Subject: [PATCH 6/7] Fix other abstract classes in unit tests --- .../uss_qualifier/scenarios/scenario_test/utils.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/scenario_test/utils.py b/monitoring/uss_qualifier/scenarios/scenario_test/utils.py index b7bf82d1b6..c04f92ae27 100644 --- a/monitoring/uss_qualifier/scenarios/scenario_test/utils.py +++ b/monitoring/uss_qualifier/scenarios/scenario_test/utils.py @@ -78,10 +78,12 @@ class NotATestScenarioC: fake_sub_module = _build_module("test.submodule") class TestScenarioSubA(_TestScenario): - pass + def run(self, context): + pass class TestScenarioSubB(_TestScenario): - pass + def run(self, context): + pass fake_sub_module.TestScenarioSubA = TestScenarioSubA fake_sub_module.TestScenarioSubB = TestScenarioSubB @@ -90,7 +92,8 @@ class TestScenarioSubB(_TestScenario): fake_subsub_module = _build_module("test.submodule.subsubmodule") class TestScenarioSubSubA(_TestScenario): - pass + def run(self, context): + pass fake_subsub_module.TestScenarioSubSubA = TestScenarioSubSubA From ffd6c340b6aa3d69dcff9114b05144ce909d2c1b Mon Sep 17 00:00:00 2001 From: Benjamin Pelletier Date: Thu, 23 Apr 2026 19:01:50 +0000 Subject: [PATCH 7/7] Simplify equal_prio_ usage --- .../conflict_equal_priority_not_permitted.py | 18 ++++++++++-------- .../planning_sequence_scenario.py | 14 +++++++------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py index 73891c85aa..3949dcd654 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/conflict_equal_priority_not_permitted/conflict_equal_priority_not_permitted.py @@ -53,10 +53,10 @@ class ConflictEqualPriorityNotPermitted(PlanningSequenceScenario): flight1c_activated: FlightInfoTemplate flight2_id: str | None = None - flight2m_planned: FlightInfoTemplate - flight2_planned: FlightInfoTemplate - flight2_activated: FlightInfoTemplate - flight2_nonconforming: FlightInfoTemplate + equal_prio_flight2m_planned: FlightInfoTemplate + equal_prio_flight2_planned: FlightInfoTemplate + equal_prio_flight2_activated: FlightInfoTemplate + equal_prio_flight2_nonconforming: FlightInfoTemplate def __init__( self, @@ -175,7 +175,7 @@ def run_planning_sequence(self, context: ExecutionContext): def _attempt_plan_flight_conflict(self): self.begin_test_step("Plan Flight 2") - flight2_planned = self.resolve_flight(self.flight2_planned) + flight2_planned = self.resolve_flight(self.equal_prio_flight2_planned) with OpIntentValidator( self, @@ -194,7 +194,7 @@ def _attempt_plan_flight_conflict(self): self.end_test_step() self.begin_test_step("Activate Flight 2") - flight2_activated = self.resolve_flight(self.flight2_activated) + flight2_activated = self.resolve_flight(self.equal_prio_flight2_activated) with OpIntentValidator( self, @@ -385,7 +385,7 @@ def _modify_activated_flight_preexisting_conflict( self.end_test_step() self.begin_test_step("Plan Flight 2m") - flight2m_planned = self.resolve_flight(self.flight2m_planned) + flight2m_planned = self.resolve_flight(self.equal_prio_flight2m_planned) with OpIntentValidator( self, @@ -405,7 +405,9 @@ def _modify_activated_flight_preexisting_conflict( self.end_test_step() self.begin_test_step("Declare Flight 2 non-conforming") - flight2_nonconforming = self.resolve_flight(self.flight2_nonconforming) + flight2_nonconforming = self.resolve_flight( + self.equal_prio_flight2_nonconforming + ) with OpIntentValidator( self, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py index 34423c734c..4222170b49 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/nominal_planning/planning_sequence_scenario.py @@ -34,10 +34,17 @@ class PlanningSequenceScenario(TestScenario, ABC): + """Abstracts a test scenario where a sequence of planning actions are performed. + + The provided flight_intents will be validated against expected_flight_intents and the flight intent templates + extracted from flight_intents will be added as attributes to this scenario object according to their intent_ids. + """ + tested_uss: FlightPlannerClient control_uss: FlightPlannerClient dss: DSSInstance flight_intents_templates: dict[FlightIntentID, FlightInfoTemplate] + # Note: FlightInfoTemplate attributes named according to flight_intents' intent_ids will also be present def __init__( self, @@ -78,13 +85,6 @@ def __init__( f"`{self.me()}` TestScenario requirements for flight_intents not met: {e}" ) - for efi in expected_flight_intents: - setattr( - self, - efi.intent_id.replace("equal_prio_", ""), - self.flight_intents_templates[efi.intent_id], - ) - def resolve_flight(self, flight_template: FlightInfoTemplate) -> FlightInfo: return flight_template.resolve(self.time_context.evaluate_now())