|
1 | 1 | import datetime
|
2 |
| - |
3 | 2 | import math
|
4 | 3 | from dataclasses import dataclass
|
5 | 4 | from typing import List, Optional, Dict, Union, Set, Tuple
|
|
8 | 7 | import s2sphere
|
9 | 8 | from loguru import logger
|
10 | 9 | from s2sphere import LatLng, LatLngRect
|
11 |
| - |
12 | 10 | from uas_standards.interuss.automated_testing.rid.v1.observation import (
|
13 | 11 | Flight,
|
14 | 12 | GetDisplayDataResponse,
|
@@ -371,12 +369,16 @@ def _evaluate_normal_observation(
|
371 | 369 | self._injected_flights, observation.flights
|
372 | 370 | )
|
373 | 371 |
|
374 |
| - self._evaluate_flight_presence( |
| 372 | + _evaluate_flight_presence( |
375 | 373 | observer.participant_id,
|
376 | 374 | [query],
|
377 | 375 | True,
|
378 | 376 | mapping_by_injection_id,
|
379 | 377 | verified_sps,
|
| 378 | + self._test_scenario, |
| 379 | + self._injected_flights, |
| 380 | + self._rid_version, |
| 381 | + self._config, |
380 | 382 | )
|
381 | 383 |
|
382 | 384 | # Check that altitudes match for any observed flights matching injected flights
|
@@ -492,101 +494,6 @@ def _evaluate_normal_observation(
|
492 | 494 | ],
|
493 | 495 | )
|
494 | 496 |
|
495 |
| - def _evaluate_flight_presence( |
496 |
| - self, |
497 |
| - observer_participant_id: str, |
498 |
| - observation_queries: List[Query], |
499 |
| - observer_participant_is_relevant: bool, |
500 |
| - mapping_by_injection_id: Dict[str, TelemetryMapping], |
501 |
| - verified_sps: Set[str], |
502 |
| - ): |
503 |
| - """Implements fragment documented in `display_data_evaluator_flight_presence.md`.""" |
504 |
| - |
505 |
| - query_timestamps = [q.request.timestamp for q in observation_queries] |
506 |
| - observer_participants = ( |
507 |
| - [observer_participant_id] if observer_participant_is_relevant else [] |
508 |
| - ) |
509 |
| - for expected_flight in self._injected_flights: |
510 |
| - t_initiated = min(q.request.timestamp for q in observation_queries) |
511 |
| - t_response = max(q.response.reported.datetime for q in observation_queries) |
512 |
| - timestamps = [ |
513 |
| - arrow.get(t.timestamp) for t in expected_flight.flight.telemetry |
514 |
| - ] |
515 |
| - t_min = min(timestamps).datetime |
516 |
| - t_max = max(timestamps).datetime |
517 |
| - |
518 |
| - if t_response < t_min: |
519 |
| - # This flight should definitely not have been observed (it starts in the future) |
520 |
| - with self._test_scenario.check( |
521 |
| - "Premature flight", [expected_flight.uss_participant_id] |
522 |
| - ) as check: |
523 |
| - if expected_flight.flight.injection_id in mapping_by_injection_id: |
524 |
| - check.record_failed( |
525 |
| - summary="Flight observed before it started", |
526 |
| - details=f"Flight {expected_flight.flight.injection_id} injected into {expected_flight.uss_participant_id} was observed by {observer_participant_id} at {t_response.isoformat()} before that flight should have started at {t_min.isoformat()}", |
527 |
| - severity=Severity.Medium, |
528 |
| - query_timestamps=query_timestamps |
529 |
| - + [expected_flight.query_timestamp], |
530 |
| - ) |
531 |
| - # TODO: attempt to observe flight details |
532 |
| - continue |
533 |
| - elif ( |
534 |
| - t_response |
535 |
| - > t_max |
536 |
| - + self._rid_version.realtime_period |
537 |
| - + self._config.max_propagation_latency.timedelta |
538 |
| - ): |
539 |
| - # This flight should not have been observed (it was too far in the past) |
540 |
| - participants = observer_participants |
541 |
| - if ( |
542 |
| - expected_flight.uss_participant_id not in verified_sps |
543 |
| - or not participants |
544 |
| - ): |
545 |
| - participants.append(expected_flight.uss_participant_id) |
546 |
| - with self._test_scenario.check( |
547 |
| - "Lingering flight", participants |
548 |
| - ) as check: |
549 |
| - if expected_flight.flight.injection_id in mapping_by_injection_id: |
550 |
| - check.record_failed( |
551 |
| - summary="Flight still observed long after it ended", |
552 |
| - details=f"Flight {expected_flight.flight.injection_id} injected into {expected_flight.uss_participant_id} was observed by {observer_participant_id} at {t_response.isoformat()} after it ended at {t_max.isoformat()}", |
553 |
| - severity=Severity.Medium, |
554 |
| - query_timestamps=query_timestamps |
555 |
| - + [expected_flight.query_timestamp], |
556 |
| - ) |
557 |
| - continue |
558 |
| - elif ( |
559 |
| - t_min + self._config.max_propagation_latency.timedelta |
560 |
| - < t_initiated |
561 |
| - < t_max |
562 |
| - + self._rid_version.realtime_period |
563 |
| - - self._config.max_propagation_latency.timedelta |
564 |
| - ): |
565 |
| - # This flight should definitely have been observed |
566 |
| - participants = observer_participants |
567 |
| - if ( |
568 |
| - expected_flight.uss_participant_id not in verified_sps |
569 |
| - or not participants |
570 |
| - ): |
571 |
| - participants.append(expected_flight.uss_participant_id) |
572 |
| - with self._test_scenario.check("Missing flight", participants) as check: |
573 |
| - if ( |
574 |
| - expected_flight.flight.injection_id |
575 |
| - not in mapping_by_injection_id |
576 |
| - ): |
577 |
| - check.record_failed( |
578 |
| - summary="Expected flight not observed", |
579 |
| - details=f"Flight {expected_flight.flight.injection_id} injected into {expected_flight.uss_participant_id} was not found in the observation by {observer_participant_id} at {t_response.isoformat()} even though it should have been active from {t_min.isoformat()} to {t_max.isoformat()}", |
580 |
| - severity=Severity.Medium, |
581 |
| - query_timestamps=query_timestamps |
582 |
| - + [expected_flight.query_timestamp], |
583 |
| - ) |
584 |
| - continue |
585 |
| - # TODO: observe flight details |
586 |
| - elif t_initiated > t_min: |
587 |
| - # If this flight was not observed, there may be propagation latency |
588 |
| - pass # TODO: findings propagation latency |
589 |
| - |
590 | 497 | def _evaluate_area_too_large_observation(
|
591 | 498 | self,
|
592 | 499 | observer: RIDSystemObserver,
|
@@ -882,12 +789,16 @@ def _evaluate_normal_sp_observation(
|
882 | 789 | mappings: Dict[str, TelemetryMapping],
|
883 | 790 | ) -> None:
|
884 | 791 |
|
885 |
| - self._evaluate_flight_presence( |
| 792 | + _evaluate_flight_presence( |
886 | 793 | "uss_qualifier, acting as Display Provider",
|
887 | 794 | sp_observation.queries,
|
888 | 795 | False,
|
889 | 796 | mappings,
|
890 | 797 | set(),
|
| 798 | + self._test_scenario, |
| 799 | + self._injected_flights, |
| 800 | + self._rid_version, |
| 801 | + self._config, |
891 | 802 | )
|
892 | 803 |
|
893 | 804 | for mapping in mappings.values():
|
@@ -1312,3 +1223,95 @@ def _sliding_triples(points: List[s2sphere.LatLng]) -> List[List[s2sphere.LatLng
|
1312 | 1223 | Returns a list of triples of consecutive positions in passed the list.
|
1313 | 1224 | """
|
1314 | 1225 | return [[points[i], points[i + 1], points[i + 2]] for i in range(len(points) - 2)]
|
| 1226 | + |
| 1227 | + |
| 1228 | +def _evaluate_flight_presence( |
| 1229 | + observer_participant_id: str, |
| 1230 | + observation_queries: List[Query], |
| 1231 | + observer_participant_is_relevant: bool, |
| 1232 | + mapping_by_injection_id: Dict[str, TelemetryMapping], |
| 1233 | + verified_sps: Set[str], |
| 1234 | + test_scenario: TestScenario, |
| 1235 | + injected_flights: List[InjectedFlight], |
| 1236 | + rid_version: RIDVersion, |
| 1237 | + evaluation_config: EvaluationConfiguration, |
| 1238 | +): |
| 1239 | + """Implements fragment documented in `display_data_evaluator_flight_presence.md`.""" |
| 1240 | + |
| 1241 | + query_timestamps = [q.request.timestamp for q in observation_queries] |
| 1242 | + observer_participants = ( |
| 1243 | + [observer_participant_id] if observer_participant_is_relevant else [] |
| 1244 | + ) |
| 1245 | + for expected_flight in injected_flights: |
| 1246 | + t_initiated = min(q.request.timestamp for q in observation_queries) |
| 1247 | + t_response = max(q.response.reported.datetime for q in observation_queries) |
| 1248 | + timestamps = [arrow.get(t.timestamp) for t in expected_flight.flight.telemetry] |
| 1249 | + t_min = min(timestamps).datetime |
| 1250 | + t_max = max(timestamps).datetime |
| 1251 | + |
| 1252 | + if t_response < t_min: |
| 1253 | + # This flight should definitely not have been observed (it starts in the future) |
| 1254 | + with test_scenario.check( |
| 1255 | + "Premature flight", [expected_flight.uss_participant_id] |
| 1256 | + ) as check: |
| 1257 | + if expected_flight.flight.injection_id in mapping_by_injection_id: |
| 1258 | + check.record_failed( |
| 1259 | + summary="Flight observed before it started", |
| 1260 | + details=f"Flight {expected_flight.flight.injection_id} injected into {expected_flight.uss_participant_id} was observed by {observer_participant_id} at {t_response.isoformat()} before that flight should have started at {t_min.isoformat()}", |
| 1261 | + severity=Severity.Medium, |
| 1262 | + query_timestamps=query_timestamps |
| 1263 | + + [expected_flight.query_timestamp], |
| 1264 | + ) |
| 1265 | + # TODO: attempt to observe flight details |
| 1266 | + continue |
| 1267 | + elif ( |
| 1268 | + t_response |
| 1269 | + > t_max |
| 1270 | + + rid_version.realtime_period |
| 1271 | + + evaluation_config.max_propagation_latency.timedelta |
| 1272 | + ): |
| 1273 | + # This flight should not have been observed (it was too far in the past) |
| 1274 | + participants = observer_participants |
| 1275 | + if ( |
| 1276 | + expected_flight.uss_participant_id not in verified_sps |
| 1277 | + or not participants |
| 1278 | + ): |
| 1279 | + participants.append(expected_flight.uss_participant_id) |
| 1280 | + with test_scenario.check("Lingering flight", participants) as check: |
| 1281 | + if expected_flight.flight.injection_id in mapping_by_injection_id: |
| 1282 | + check.record_failed( |
| 1283 | + summary="Flight still observed long after it ended", |
| 1284 | + details=f"Flight {expected_flight.flight.injection_id} injected into {expected_flight.uss_participant_id} was observed by {observer_participant_id} at {t_response.isoformat()} after it ended at {t_max.isoformat()}", |
| 1285 | + severity=Severity.Medium, |
| 1286 | + query_timestamps=query_timestamps |
| 1287 | + + [expected_flight.query_timestamp], |
| 1288 | + ) |
| 1289 | + continue |
| 1290 | + elif ( |
| 1291 | + t_min + evaluation_config.max_propagation_latency.timedelta |
| 1292 | + < t_initiated |
| 1293 | + < t_max |
| 1294 | + + rid_version.realtime_period |
| 1295 | + - evaluation_config.max_propagation_latency.timedelta |
| 1296 | + ): |
| 1297 | + # This flight should definitely have been observed |
| 1298 | + participants = observer_participants |
| 1299 | + if ( |
| 1300 | + expected_flight.uss_participant_id not in verified_sps |
| 1301 | + or not participants |
| 1302 | + ): |
| 1303 | + participants.append(expected_flight.uss_participant_id) |
| 1304 | + with test_scenario.check("Missing flight", participants) as check: |
| 1305 | + if expected_flight.flight.injection_id not in mapping_by_injection_id: |
| 1306 | + check.record_failed( |
| 1307 | + summary="Expected flight not observed", |
| 1308 | + details=f"Flight {expected_flight.flight.injection_id} injected into {expected_flight.uss_participant_id} was not found in the observation by {observer_participant_id} at {t_response.isoformat()} even though it should have been active from {t_min.isoformat()} to {t_max.isoformat()}", |
| 1309 | + severity=Severity.Medium, |
| 1310 | + query_timestamps=query_timestamps |
| 1311 | + + [expected_flight.query_timestamp], |
| 1312 | + ) |
| 1313 | + continue |
| 1314 | + # TODO: observe flight details |
| 1315 | + elif t_initiated > t_min: |
| 1316 | + # If this flight was not observed, there may be propagation latency |
| 1317 | + pass # TODO: findings propagation latency |
0 commit comments