diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json index 2b1f837229..a2a6e44be4 100644 --- a/.basedpyright/baseline.json +++ b/.basedpyright/baseline.json @@ -19721,14 +19721,6 @@ "lineCount": 1 } }, - { - "code": "reportPossiblyUnboundVariable", - "range": { - "startColumn": 16, - "endColumn": 23, - "lineCount": 1 - } - }, { "code": "reportArgumentType", "range": { diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py b/monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py index 3c1b9fc219..27e9ab06d6 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/evaluation.py @@ -1,6 +1,12 @@ from datetime import timedelta +from urllib.parse import urlparse -from uas_standards.astm.f3548.v21.api import OperationalIntentDetails, Volume4D +from uas_standards.astm.f3548.v21.api import ( + OperationalIntentDetails, + OperationalIntentReference, + UssAvailabilityState, + Volume4D, +) from monitoring.monitorlib.geotemporal import Volume4DCollection from monitoring.monitorlib.scd import priority_of @@ -8,6 +14,65 @@ NUMERIC_PRECISION = 0.001 +def validate_op_intent_reference( + uss_oi: OperationalIntentReference, + dss_oi: OperationalIntentReference, +) -> str | None: + # this function assumes all fields required by the OpenAPI definition are present as the format validation + # should have been performed by OpIntentValidator._evaluate_op_intent_validation before + errors_text: list[str] = [] + + def append_err(name: str, uss_value: str, dss_value: str): + errors_text.append( + f"{name} reported by USS ({uss_value}) does not match the one published to the DSS ({dss_value})" + ) + return + + if uss_oi.version != dss_oi.version: + append_err("Version", str(uss_oi.version), str(dss_oi.version)) + if uss_oi.state != dss_oi.state: + append_err("State", uss_oi.state, dss_oi.state) + + # use str.lower() to tolerate case mismatch for string values + if uss_oi.id.lower() != dss_oi.id.lower(): + append_err("ID", uss_oi.id, dss_oi.id) + if uss_oi.manager.lower() != dss_oi.manager.lower(): + append_err("Manager", uss_oi.manager, dss_oi.manager) + if uss_oi.subscription_id.lower() != dss_oi.subscription_id.lower(): + append_err("Subscription ID", uss_oi.subscription_id, dss_oi.subscription_id) + if uss_oi.uss_availability.lower() != dss_oi.uss_availability.lower(): + # tolerate empty value if unknown + if ( + len(uss_oi.uss_availability) != 0 + or dss_oi.uss_availability != UssAvailabilityState.Unknown + ): + append_err( + "USS availability", uss_oi.uss_availability, dss_oi.uss_availability + ) + + if uss_oi.uss_base_url != dss_oi.uss_base_url: + # tolerate differences in URL that have no impact + uss_url = urlparse(uss_oi.uss_base_url) + dss_url = urlparse(dss_oi.uss_base_url) + if ( + uss_url.scheme != dss_url.scheme + or uss_url.netloc != dss_url.netloc + or uss_url.path != dss_url.path + ): + append_err("USS base URL", uss_oi.uss_base_url, dss_oi.uss_base_url) + + if abs( + uss_oi.time_start.value.datetime - dss_oi.time_start.value.datetime + ) > timedelta(seconds=NUMERIC_PRECISION): + append_err("Start time", uss_oi.time_start.value, dss_oi.time_start.value) + if abs(uss_oi.time_end.value.datetime - dss_oi.time_end.value.datetime) > timedelta( + seconds=NUMERIC_PRECISION + ): + append_err("End time", uss_oi.time_start.value, dss_oi.time_start.value) + + return "; ".join(errors_text) if errors_text else None + + def validate_op_intent_details( op_intent_details: OperationalIntentDetails, expected_priority: int, diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py b/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py index 001b93a0e4..25cad907f5 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/test_steps.py @@ -29,6 +29,7 @@ ) from monitoring.uss_qualifier.scenarios.astm.utm.evaluation import ( validate_op_intent_details, + validate_op_intent_reference, ) from monitoring.uss_qualifier.scenarios.scenario import ( ScenarioDidNotStopError, @@ -420,6 +421,7 @@ def _check_op_intent_details( details=f"Received status code {oi_full_query.status_code} from {self._flight_planner.participant_id} when querying for details of operational intent {oi_ref.id}; {e}", query_timestamps=[oi_full_query.request.timestamp], ) + return validation_failures = self._evaluate_op_intent_validation(oi_full_query) with self._scenario.check( @@ -444,6 +446,21 @@ def _check_op_intent_details( query_timestamps=[oi_full_query.request.timestamp], ) + with self._scenario.check( + "Operational intent reference reported by USS matches the one published to the DSS", + [self._flight_planner.participant_id], + ) as check: + error_text = validate_op_intent_reference( + oi_full.reference, + oi_ref, + ) + if error_text: + check.record_failed( + summary="Operational intent reference reported by USS does not match the one published to the DSS", + details=error_text, + query_timestamps=[oi_full_query.request.timestamp], + ) + with self._scenario.check( "Correct operational intent details", [self._flight_planner.participant_id] ) as check: diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/validate_shared_operational_intent.md b/monitoring/uss_qualifier/scenarios/astm/utm/validate_shared_operational_intent.md index c6e8756f76..5a356b3545 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/validate_shared_operational_intent.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/validate_shared_operational_intent.md @@ -29,6 +29,10 @@ If the operational intent details for the flight cannot be retrieved from the US If the operational intent details response does not validate against [the GetOperationalIntentDetailsResponse schema of the OpenAPI specification](https://github.com/astm-utm/Protocol/blob/v1.0.0/utm.yaml#L1120), this check fill fail per **[astm.f3548.v21.USS0105,1](../../../requirements/astm/f3548/v21.md)**. +## 🛑 Operational intent reference reported by USS matches the one published to the DSS check + +If any of the values in the operational intent reference reported by the USS do not match those values in the operational intent reference published to (and known by) the DSS, save for the OVN, this check will fail per **[astm.f3548.v21.USS0005](../../../requirements/astm/f3548/v21.md)** since the values reported by the USS were not made discoverable via the DSS. + ## 🛑 Correct operational intent details check If the operational intent details reported by the USS do not match the user's flight intent, this check will fail per **[interuss.automated_testing.flight_planning.ExpectedBehavior](../../../requirements/interuss/automated_testing/flight_planning.md)** and **[astm.f3548.v21.OPIN0025](../../../requirements/astm/f3548/v21.md)**.