diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.md b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.md new file mode 100644 index 0000000000..0bc9670d54 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.md @@ -0,0 +1,20 @@ +# Evaluation of Common Data Dictionary fields + +The `RIDCommonDictionaryEvaluator` only implements checks that are the responsibility of SPs and DPs. +Validation or checks that are the responsibility of test designers or uss_qualifier developers are to be implemented in the calling test scenario. + +## UA type +Note: C1, C2 & C4 are assumed to be implemented upstream of `RIDCommonDictionaryEvaluator`. + +| C | Data to inject | Injection API | SP API | Observation API | Responsible entity | Failure | Failed requirement | +|----|-----------------|-----------------------------------------------------------------|--------------------------------------------------|--------------------------------------------|--------------------|-------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------| +| 1 | [invalid] | [any] | [any] | [any] | Test designer | Invalid test data provided | Exception raised in scenario bootstrap | +| 2 | [none or valid] | [anything that doesn't match data to inject, including invalid] | [any] | [any] | uss_qualifier dev | Test data not injected accurately | `interuss.automated_testing.rid.injection.UpsertTestResult` | +| 3 | [none or valid] | [matching data to inject] | [none] | [any] | SP | API-required field not provided | `astm.f3411.v22a.NET0710,X`; `astm.f3411.v22a.NET0260,Table1,X` | +| 4 | [none] | NotDeclared | [any] | [any] | uss_qualifier dev | Test data not injected accurately | `interuss.automated_testing.rid.injection.UpsertTestResult` | +| 5 | [none or valid] | [matching data to inject] | [invalid] | [any] | SP | SP API contract violated | `astm.f3411.v22a.NET0710,X`; `astm.f3411.v22a.NET0260,Table1,X` | +| 6 | [none] | [none] | [anything other than NotDeclared] | [any] | SP | UA type not communicated correctly | `astm.f3411.v22a.NET0260,Table1,X` | +| 7 | [none or valid] | [matching data to inject] | [valid, but not corresponding to injected value] | [any] | SP | UA type not communicated correctly | `astm.f3411.v22a.NET0260,Table1,X` | +| 8 | [none or valid] | [matching data to inject] | [valid] | [none] | N/A | No failure; reporting UA type to observers is not required by API | N/A | +| 9 | [none or valid] | [matching data to inject] | [valid] | [invalid] | DP | Observation API contract violated | `interuss.automated_testing.rid.observation.ObservationSuccess`; `astm.f3411.v22a.NET0450`; `astm.f3411.v22a.NET0470,Table1,X` | +| 10 | [none or valid] | [matching data to inject] | [valid] | [valid, but not corresponding to SP value] | DP | SP information incorrectly reported to observer | `astm.f3411.v22a.NET0450`; `astm.f3411.v22a.NET0470,Table1,X` | diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py index d7752b6982..8740f2f3ba 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/common_dictionary_evaluator.py @@ -1,3 +1,4 @@ +import datetime import math from typing import List, Optional @@ -47,11 +48,21 @@ def __init__( def evaluate_sp_flight( self, + injected_flight: injection.TestFlight, observed_flight: Flight, participant_id: ParticipantID, + query_timestamp: datetime.datetime, ): """Implements fragment documented in `common_dictionary_evaluator_sp_flight.md`.""" + self._evaluate_ua_type( + injected_flight, + observed_flight, + None, + participant_id, + query_timestamp, + ) + self._evaluate_operational_status( observed_flight.operational_status, [participant_id], @@ -675,3 +686,115 @@ def _evaluate_operational_status( key="skip_reason", message=f"Unsupported version {self._rid_version}: skipping Operational Status evaluation", ) + + def _evaluate_ua_type( + self, + injected_flight: injection.TestFlight, + sp_observed_flight: Optional[Flight], + dp_observed_flight: Optional[observation_api.Flight], + participant: ParticipantID, + query_timestamp: datetime.datetime, + ): + """ + Evaluates UA type. Exactly one of sp_observed_flight or dp_observed_flight must be provided. + See as well `common_dictionary_evaluator.md`. + + Args: + injected_flight: injected flight as returned by the injection API. + sp_observed_flight: flight observed through the SP API. + dp_observed_flight: flight observed through the observation API. + participant: participant providing the API through which the value was observed. + query_timestamp: timestamp of the observation query. + + Raises: + ValueError: if a test operation wasn't performed correctly by uss_qualifier. + """ + + injected_val: Optional[injection.UAType] = injected_flight.get("aircraft_type") + if injected_val is not None: + try: + injected_val = injection.UAType(injected_val) + except ValueError as e: + raise ValueError(f"Invalid UA type {injected_val} injected", e) + + observed_val: Optional[injection.UAType] + if sp_observed_flight is not None: + observed_val = sp_observed_flight.aircraft_type + elif dp_observed_flight is not None: + observed_val = dp_observed_flight.get("aircraft_type") + else: + raise ValueError("No observed flight provided.") + + with self._test_scenario.check( + "UA type is exposed correctly", + participant, + ) as check: + if sp_observed_flight is not None: + if observed_val is None: # C3 + check.record_failed( + "UA type is missing", + details="SP did not return any UA type", + query_timestamps=[query_timestamp], + ) + + if observed_val is not None: # C5 / C9 + try: + injection.UAType(observed_val) + except ValueError: + check.record_failed( + "UA type is invalid", + details=f"USS returned an invalid UA type: {observed_val}.", + query_timestamps=[query_timestamp], + ) + + if ( + self._rid_version == RIDVersion.f3411_19 + and observed_val == injection.UAType.HybridLift + ) or ( + self._rid_version == RIDVersion.f3411_22a + and observed_val == injection.UAType.VTOL + ): + check.record_failed( + "UA type is inconsistent with RID version", + details=f"USS returned the UA type {observed_val} which is not supported by the RID version used ({self._rid_version}).", + query_timestamps=[query_timestamp], + ) + + with self._test_scenario.check( + "UA type is consistent with injected value", + participant, + ) as check: + equivalent = {injection.UAType.HybridLift, injection.UAType.VTOL} + + if injected_val is None: + if ( + sp_observed_flight is not None + and observed_val != injection.UAType.NotDeclared + ): # C6 + check.record_failed( + "UA type is inconsistent, expected 'NotDeclared' since no value was injected", + details=f"SP returned the UA type {observed_val}, yet no value was injected, which should have been mapped to 'NotDeclared'.", + query_timestamps=[query_timestamp], + ) + + if dp_observed_flight is not None and observed_val is not None: # C10 + check.record_failed( + "UA type is inconsistent, expected no value since none was injected", + details=f"DP returned the UA type {observed_val}, yet no value was injected.", + query_timestamps=[query_timestamp], + ) + + elif injected_val in equivalent: + if observed_val not in equivalent: # C7 / C10 + check.record_failed( + "UA type is inconsistent with injected value", + details=f"USS returned the UA type {observed_val}, yet the value {injected_val} was injected, given that {equivalent} are equivalent .", + query_timestamps=[query_timestamp], + ) + + elif injected_val != observed_val: # C7 / C10 + check.record_failed( + "UA type is inconsistent with injected value", + details=f"USS returned the UA type {observed_val}, yet the value {injected_val} was injected.", + query_timestamps=[query_timestamp], + ) diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py index 628645ec7a..fda2ffc773 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/display_data_evaluator.py @@ -2,7 +2,7 @@ import math from dataclasses import dataclass -from typing import List, Optional, Dict, Union, Set, Tuple, cast +from typing import List, Optional, Dict, Union, Set, Tuple import arrow import s2sphere @@ -890,6 +890,7 @@ def _evaluate_normal_sp_observation( for mapping in mappings.values(): participant_id = mapping.injected_flight.uss_participant_id + injected_flight = mapping.injected_flight.flight observed_flight = mapping.observed_flight.flight flights_queries = [ q @@ -901,6 +902,7 @@ def _evaluate_normal_sp_observation( f"Found {len(flights_queries)} flights queries (instead of the expected 1) for flight {mapping.observed_flight.id} corresponding to injection ID {mapping.injected_flight.flight.injection_id} for {participant_id}" ) flights_query = flights_queries[0] + query_timestamp = flights_query.query.request.timestamp # Verify that flights queries returned correctly-formatted data errors = schema_validation.validate( @@ -920,7 +922,7 @@ def _evaluate_normal_sp_observation( f"At {e.json_path} in the response: {e.message}" for e in errors ), - query_timestamps=[flights_query.query.request.timestamp], + query_timestamps=[query_timestamp], ) # Check recent positions timings @@ -935,8 +937,10 @@ def _evaluate_normal_sp_observation( # Check flight consistency with common data dictionary self._common_dictionary_evaluator.evaluate_sp_flight( + injected_flight, observed_flight, participant_id, + query_timestamp, ) # Check that required fields are present and match for any observed flights matching injected flights diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/common_dictionary_evaluator_sp_flight.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/common_dictionary_evaluator_sp_flight.md index e1c7da0830..2399f25dec 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v19/common_dictionary_evaluator_sp_flight.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v19/common_dictionary_evaluator_sp_flight.md @@ -2,6 +2,17 @@ This fragment is implemented in `common_dictionary_evaluator.py:RIDCommonDictionaryEvaluator.evaluate_sp_flight`. +## ⚠️ UA type is exposed correctly check + +If the UA type value exposed by the SP API is missing or invalid this check will fail per: +**[astm.f3411.v19.NET0710,1](../../../../requirements/astm/f3411/v19.md)** because the SP violates the SP API contract; +**[astm.f3411.v19.NET0260,Table1,3](../../../../requirements/astm/f3411/v19.md)** because the SP fails to expose data consistent with the Common Data Dictionary. + +## ⚠️ UA type is consistent with injected value check + +If the UA type value exposed by the SP API is inconsistent with the injected value this check will fail per: +**[astm.f3411.v19.NET0260,Table1,3](../../../../requirements/astm/f3411/v19.md)** because the SP fails to expose data consistent with the valid injected value. + ## Service Provider altitude check **[astm.f3411.v19.NET0260,Table1,11](../../../../requirements/astm/f3411/v19.md)** requires that relevant Remote ID data, consistent with the common data dictionary, be reported by the Service Provider. Injected flight data had known altitudes, but the altitude reported by the Service Provider did not match those known altitudes. diff --git a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/common_dictionary_evaluator_sp_flight.md b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/common_dictionary_evaluator_sp_flight.md index d4ffe022d9..14bfc5e3d1 100644 --- a/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/common_dictionary_evaluator_sp_flight.md +++ b/monitoring/uss_qualifier/scenarios/astm/netrid/v22a/common_dictionary_evaluator_sp_flight.md @@ -2,6 +2,17 @@ This fragment is implemented in `common_dictionary_evaluator.py:RIDCommonDictionaryEvaluator.evaluate_sp_flight`. +## ⚠️ UA type is exposed correctly check + +If the UA type value exposed by the SP API is missing or invalid this check will fail per: +**[astm.f3411.v22a.NET0710,1](../../../../requirements/astm/f3411/v22a.md)** because the SP violates the SP API contract; +**[astm.f3411.v22a.NET0260,Table1,2](../../../../requirements/astm/f3411/v22a.md)** because the SP fails to expose data consistent with the Common Data Dictionary. + +## ⚠️ UA type is consistent with injected value check + +If the UA type value exposed by the SP API is inconsistent with the injected value this check will fail per: +**[astm.f3411.v22a.NET0260,Table1,2](../../../../requirements/astm/f3411/v22a.md)** because the SP fails to expose data consistent with the valid injected value. + ## Service Provider altitude check **[astm.f3411.v22a.NET0260,Table1,12](../../../../requirements/astm/f3411/v22a.md)** requires that relevant Remote ID data, consistent with the common data dictionary, be reported by the Service Provider. Injected flight data had known altitudes, but the altitude reported by the Service Provider did not match those known altitudes. diff --git a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md index 11bcf644ac..2a7626467a 100644 --- a/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md +++ b/monitoring/uss_qualifier/suites/astm/netrid/f3411_19.md @@ -21,7 +21,7 @@